fineshopdesign / cf-wasm

⛅ A collection of WASM packages those work on Cloudflare workers
50 stars 2 forks source link

Crash when loading or resizing large image in nextjs or cloudflare workers #11

Closed yoeven closed 5 months ago

yoeven commented 5 months ago

If you try to load a large image into Photon using the PhotonImage.new_from_byteslice() function it would crash when hosted on nextjs or cloudflare worker. Seems like there is a memory or CPU leak somewhere. However, it works when running locally.

Tried based on the examples provided.

Example image: https://dummyimage.com/5000x5000/000/fff

What I've tested is if the height pixels are more than 5000 it the worker would throw an error.

nasso commented 5 months ago

when hosted on nextjs

what does this mean?

Example image: https://dummyimage.com/5000x5000/000/fff

What I've tested is if the height pixels are more than 5000 it the worker would throw an error.

quick math: 5000 * 5000 * 4 = 100 000 000 bytes = 100 mb. and that's just for the image! cloudflare workers memory is limited to 128 mb. even without a leak, chances are, you are going to hit that limit anyway with images that large.

fineshop commented 5 months ago

As @nasso said, you are probably hitting the cloudflare workers memory limit (128 mb). If you still think there is leak somewhere, you can open an issue in silvia-odwyer/photon repository.

l0gical commented 5 months ago

Max I could do with it was 2625x2625 res

Using the code below, @2650x2650 it breaks with:

{
  "exceptions": [
    {
      "name": "Error",
      "message": "Promise will never complete.",
      "timestamp": 1714160493051
    }
  ]
}

Maths wise, is 2650x2650 x24bits 165,375,000 bits aka 20.6MB correct?

import * as photon from "@cf-wasm/photon";

export type Env = Readonly<{}>;

const workers: ExportedHandler<Env> = {
  async fetch() {
    // url of image to fetch
    const imageUrl = "https://dummyimage.com/2625x2625/000/fff";

    // fetch image and get the Uint8Array instance
    const inputBytes = await fetch(imageUrl)
      .then((res) => res.arrayBuffer())
      .then((buffer) => new Uint8Array(buffer));

    // create a photon instance
    const inputImage = photon.PhotonImage.new_from_byteslice(inputBytes);

    // resize image using photon
    const outputImage = photon.resize(
      inputImage,
      inputImage.get_width() * 0.5,
      inputImage.get_height() * 0.5,
      1
    );

    // get webp bytes
    const outputBytes = outputImage.get_bytes_webp();

    // for other formats
    // png  : outputImage.get_bytes();
    // jpeg : outputImage.get_bytes_jpeg(quality);

    // call free() method to free memory
    inputImage.free();
    outputImage.free();

    // return the Response instance
    return new Response(outputBytes, {
      headers: {
        "Content-Type": "image/webp"
      }
    });
  }
};

export default workers;