Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cloudflare workers error when caching large response: TypeError: ReadableStream.tee() buffer limit exceeded #3612

Open
JustJoostNL opened this issue Nov 1, 2024 · 1 comment
Labels

Comments

@JustJoostNL
Copy link

JustJoostNL commented Nov 1, 2024

What version of Hono are you using?

4.6.8

What runtime/platform is your app running on?

Cloudflare Workers

What steps can reproduce the bug?

You can use the below code to reproduce

const passthroughHeaders = [
  "content-disposition",
  "content-type",
  "content-length",
];

export const downloads = new Hono();

async function fetchAsset(owner: string, repo: string, assetId: number) {
  const url = `https://api.github.com/repos/${owner}/${repo}/releases/assets/${assetId}`;

  const response = await fetch(url, {
    headers: {
      Authorization: `token ${githubToken}`,
      Accept: "application/octet-stream",
      "User-Agent": userAgent,
    },
  });

  return response;
}

downloads.get(
  "/download/:assetId",
  cache({
    cacheName: "downloads",
    cacheControl: `public, max-age=${cacheTtl}, immutable`,
  }),
  async (c) => {
    const { assetId } = c.req.param();

    const originalResponse = await fetchAsset(
      "xxx",
      "xxx",
      parseInt(assetId),
    );

    if (originalResponse.status === 404) return c.notFound();
    if (!originalResponse.ok) {
      return c.json({ message: "Failed to fetch asset", status: 500 });
    }

    const headers = new Headers();
    passthroughHeaders.forEach((header) => {
      const value = originalResponse.headers.get(header);
      if (value) headers.set(header, value);
    });

    return new Response(originalResponse.body as BodyInit, {
      status: originalResponse.status,
      statusText: originalResponse.statusText,
      headers,
      cf: { cacheEverything: true, cacheTtl },
    });
  },
);

What is the expected behavior?

The asset will download and cache correctly, without any error.

What do you see instead?

For assets smaller than ~130MB, it does download and cache correctly.

For assets bigger than ~130MB, it downloads until almost the end, and then errors (probably while trying to cache):

TypeError: ReadableStream.tee() buffer limit exceeded. This error usually occurs when a Request or Response with a large body is cloned, then only one of the clones is read, forcing the Workers runtime to buffer the entire body in memory. To fix this issue, remove unnecessary calls to Request/Response.clone() and ReadableStream.tee(), and always read clones/tees in parallel.

Additional information

I think the 130MB is related to the Cloudflare Workers memory limit, which is 128MB.

I think it should still be possible to cache assets bigger than 128MB though. as the cache limit is 512MB. But this probably requires changes in the current cache middleware.

@yusukebe
Copy link
Member

yusukebe commented Nov 4, 2024

Hi @JustJoostNL

I think it should still be possible to cache assets bigger than 128MB though. as the cache limit is 512MB. But this probably requires changes in the current cache middleware.

I've never investigated the details, but my understanding is that Cache Middleware uses the Cache API in the Workers runtime. So, the limit is 128MB. Cache Middleware can't support more than 128MB.

But, you can use "Cache using fetch":

https://developers.cloudflare.com/workers/examples/cache-using-fetch/

Please use this method instead of the Cache Middleware.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants