Creates a blocking web server
reggi opened this issue Β· comments
Given this code implemented in a deno server, it creates a server request handler that blocks. Meaning if you open up CURL and make two successive requests, the second request doesn't start until the first one finishes.
Is there any way to prevent this? Or is it just not feasible to have it run within a web-server like this? I can imagine that the wasm bundle code can only handle one image in memory at a time, which is why it blocks?
import { reservedPath } from "./code/cloud_asset/cloud_api/utils/reserved_path/mod.ts";
import { ImageMagick, initialize } from "./code/utils/imagemagick/mod.ts";
const file = await Deno.readFile('./my-image.png')
await initialize()
Deno.serve((_req) => {
console.log(`request made`)
if (reservedPath(_req.url)) {
return new Response("Not Found", { status: 404 });
}
const headers = new Headers();
headers.set("Content-Type", "image/png");
const i = ImageMagick.read(file, (v) => {
return v.write(g => g)
})
console.log(`request served`)
return new Response(i, { headers });
})
I've tried awaiting everything, I've tried wrapping this ImageMagick.read
in a promise, running of them and not awaiting, the first still needs to finish before the second starts. The fundamental code is sync and it seems that there is no way around it. I'm assuming there's a reason for this, something WASM related?
I have no clue why this is blocking, The ImageMagick code has locks for coders that don't support multithreading but that should not be an issue here. Maybe the wasm code can only run in a single thread? Don't think I can help you much because I don't have deno experience.
Another attempt with no luck
import { Gravity, ImageMagick, Point, initializeImageMagick } from "https://deno.land/x/imagemagick_deno@0.0.19/mod.ts";
const file = await Deno.readFile('./image.png')
await initializeImageMagick()
let streamController: any;
function startStream(controller: ReadableStreamDefaultController) {
streamController = controller;
// You can start your stream empty
}
function updateStream() {
ImageMagick.read(file, (v) => {
v.compositeGravity(v, Gravity.North, new Point(1000, 1000))
v.write(g => {
streamController.enqueue(g)
streamController.close()
console.log(`request served π`)
})
})
}
function handler(_req: Request): Response {
console.log(`request started β
`)
const body = new ReadableStream({
start: startStream
});
updateStream()
return new Response(body, {
headers: {
"content-type": "image/png",
"x-content-type-options": "nosniff",
},
});
}
Deno.serve(handler);
@oscarotero do you have any thoughts on this? How is Lume currently using this in a non-blocking way?
Also to clarify non-blocking, requests appear to always be queued, and are not concurrent.
What I'm seeing:
request started β
request served π
request started β
request served π
What I want:
request started β
request started β
request served π
request served π
Note browsers will queue request to the same domains, you need to use curl to test.
more findings:
import { Gravity, ImageMagick, Point, initializeImageMagick } from "https://deno.land/x/imagemagick_deno@0.0.19/mod.ts";
const file = await Deno.readFile('./image.png')
await initializeImageMagick()
console.log('a')
ImageMagick.read(file, (v) => {
v.compositeGravity(v, Gravity.North, new Point(1000, 1000))
v.write(g => {
console.log(`done 1`)
})
})
console.log('b')
ImageMagick.read(file, (v) => {
v.compositeGravity(v, Gravity.North, new Point(1000, 1000))
v.write(g => {
console.log(`done 2`)
})
})
console.log('c')
a
done 1
b
done 2
c
and this seems not to block
import { Gravity, ImageMagick, Point, initializeImageMagick } from "https://deno.land/x/imagemagick_deno@0.0.19/mod.ts";
const file = await Deno.readFile('./image.png')
await initializeImageMagick()
console.log('a')
ImageMagick.read(file, async (v) => {
await Promise.resolve()
v.compositeGravity(v, Gravity.North, new Point(1000, 1000))
v.write(g => {
console.log(`done 1`)
})
})
console.log('b')
ImageMagick.read(file, async (v) => {
await Promise.resolve()
v.compositeGravity(v, Gravity.North, new Point(1000, 1000))
v.write(g => {
console.log(`done 2`)
})
})
console.log('c')
a
b
c
done 1
done 2
this seems to work as I'd expect
import { Gravity, ImageMagick, Point, initializeImageMagick } from "https://deno.land/x/imagemagick_deno@0.0.19/mod.ts";
const file = await Deno.readFile('./image.png')
await initializeImageMagick()
ImageMagick.read(file, async (v) => {
console.log('start 1')
await Promise.resolve()
v.compositeGravity(v, Gravity.North, new Point(1000, 1000))
v.write(g => {
console.log(`done 1`)
})
})
ImageMagick.read(file, async (v) => {
console.log('start 2')
await Promise.resolve()
v.compositeGravity(v, Gravity.North, new Point(1000, 1000))
v.write(g => {
console.log(`done 2`)
})
})
start 1
start 2
done 1
done 2
Can we conclude this is something in Deno.serve
then and close the issue here?
if anyone is interested https://stackoverflow.com/questions/76791229/wasm-imagemagick-based-code-blocks-deno-server
@reggi The problem is JavaScript is single-threaded language, it means that only can run one thing at a time. Async functions and promises don't allow to run things concurrently, but they are added to the event loop to be executed later. If you measure the total time spend to run the code, no matter if you use promises or not, the time will be the same. Just place this code at the end of your script to measure the time and you will see how logs similar values, no matter how many promises you use:
addEventListener('unload', () => {
console.log(performance.now());
});
The only way to run code in parallel in JavaScript is with Workers.
const myWorker1 = new Worker(import.meta.resolve("./worker.ts"), { type: "module" });
const myWorker2 = new Worker(import.meta.resolve("./worker.ts"), { type: "module" });
const myWorker3 = new Worker(import.meta.resolve("./worker.ts"), { type: "module" });
This code will open tree workers that are run at the same time.
@oscarotero thanks for your comment. I implemented this worker
solution in the comments here https://stackoverflow.com/questions/76791229/wasm-imagemagick-based-code-blocks-deno-server, unfortunately I was trying to use Deno Deploy for my project and it doesn't support worker
. Opened up a ticket with Deno Deploy (denoland/deploy_feedback#458) to check and see if the blocking issue here will also be an issue using Deno Deploy as it's on the edge, and instances should be spun up and handled when needed but I have batching concerns, anyway thanks for your comment. Good to know we're on the same page.