2. can the BEAM scheduler pre-empt the JS processes?
3. How is memory garbage collected? Do the JS processes garbage collect for each individual process?
4. Are values within JS immutable?
5. If they are not immutable, are there risk for memory errors? And if there is a memory error, would it crash the JS process without crashing the rest of the system?
2. No. JS runs on a dedicated OS thread, outside the BEAM scheduler. But there's an interrupt handler (JS_SetInterruptHandler) that checks a deadline on every JS opcode boundary — pass timeout: 1000 to eval and it interrupts after 1s, runtime stays usable. For contexts there's also max_reductions — QuickJS-NG counts JS operations and interrupts when the budget runs out, closest analog to BEAM reductions.
3. QuickJS-NG uses refcounting with cycle detection. Each runtime/context has its own GC — one collecting doesn't touch another. When a Runtime GenServer terminates, JS_FreeContext + JS_FreeRuntime release everything.
4. No, standard JS mutability. But the JS↔Erlang boundary copies values — no shared mutable state across that boundary.
5. QuickJS-NG enforces JS_SetMemoryLimit per-runtime (default 256 MB) and JS_SetContextMemoryLimit per-context. Exceeding the limit raises a JS exception, not a segfault. It propagates as {:error, ...} to the caller. Since each runtime is a supervised GenServer, the supervisor restarts it. There are tests for OOM in one context not crashing the pool, and one runtime crashing not affecting siblings.
I'm interested to hear about your sandboxing approach running untrusted JS code. So you are setting an memory/reduction limit to the process which 100% is a good idea. What other defense-in-depth strategies are you using? possible support for seccomp in the future?
https://github.com/ityonemo/yavascript
glad to see someone do a fuller implementation!