Here are instructions for making a custom build of the deno
executable that supports heap memory limits on web workers via an environment variable called WEB_WORKER_HEAP_LIMIT_BYTES
.
Note that, unfortunately, this does not limit "Large Object Space" allocations, since V8 makes those allocations outside the heap. This includes e.g. large ArrayBuffers, Typed Arrays, and potentially other stuff. I've opened an issue about this here: denoland/deno#26202
git clone --recurse-submodules --branch v2.0.0 https://github.com/denoland/deno.git
cd deno
You only need to edit web_worker.rs
. Here's the edited and original version of the file:
The changes are:
- Add this code above this line.
// Attempt to read the `WEB_WORKER_HEAP_LIMIT_BYTES` environment variable
let web_worker_heap_limit_bytes = std::env::var("WEB_WORKER_HEAP_LIMIT_BYTES")
.ok()
.and_then(|val| val.parse::<usize>().ok())
.unwrap_or(1024*1024*128); // Fallback to 128MB as default if parsing fails or the variable is not set
// set memory limits with v8::CreateParams
let mut create_params = v8::CreateParams::default();
create_params = create_params.heap_limits(0, web_worker_heap_limit_bytes);
- And add this line directly below the above-linked line:
create_params: Some(create_params),
- And add add this code above this line.
let mut internal_handle_clone = internal_handle.clone();
js_runtime.add_near_heap_limit_callback(move |current_heap_limit, _initial_heap_limit| {
println!(">>> Isolate near_heap_limit_callback triggered: {} {}", current_heap_limit, _initial_heap_limit);
internal_handle_clone.terminate();
current_heap_limit * 5 // terminating the worker isn't instant, so we must increase the heap limit to prevent the whole Deno runtime from crashing in the meantime
});
# (Optional) Do all built stuff from within Docker image do it doesn't mess with your environment:
docker run -it --rm -v $(pwd):/deno -w /deno ubuntu:22.04 bash
# install required build tooling, per official docs: https://docs.deno.com/runtime/contributing/building_from_source/
apt-get update && apt-get install --install-recommends -y git curl wget python-is-python3 g++ lsb-release software-properties-common gnupg cmake libglib2.0-dev protobuf-compiler
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
. "$HOME/.cargo/env"
wget https://apt.llvm.org/llvm.sh && chmod +x llvm.sh && ./llvm.sh 16
# now edit the code and build:
cargo build -vv
# make a test.js in code editor (see next codeblock below for example), then run:
./target/debug/deno run --unstable-worker-options --allow-env test.js
# build optimized binary and test:
cargo build -vv --release
./target/release/deno run --unstable-worker-options --allow-env test.js
The "Worker:" logs will stop after a while becuase the worker has been terminated due to exceeding the memory limit:
Deno.env.set("WEB_WORKER_HEAP_LIMIT_BYTES", (1024*1024*30).toString());
let worker = new Worker(URL.createObjectURL(new Blob([`
let strings = [];
let typedArrays = [];
self.onmessage = async function(e) {
setInterval(() => {
strings.push(new Array(1000000).fill(0).map(_ => Math.random().toString()).join(""));
typedArrays.push(new Uint8Array(1024*1024*10).fill(1))
console.log("Worker:", strings.length, JSON.stringify(Deno.memoryUsage()));
}, 1000);
}
`])), {type:"module", deno:{permissions:"none"}});
worker.onmessage = function(e) {
console.log('Received from worker:', e.data);
};
worker.onerror = function(e) {
console.error('Error from worker:', e.message);
};
worker.postMessage('Hello from main thread!');
setInterval(() => {
console.log("MAIN:", JSON.stringify(Deno.memoryUsage()));
}, 1000);