Summary
A single NodeRuntime VM cannot run a long-lived server in one exec() and reach it from a separate exec(). Concurrent exec() processes in the same VM do not share a reachable loopback, so a client started in a second exec() cannot connect to a server started in the first.
This came up while documenting the dev-servers use case: the natural API a user reaches for is "run the server script, then run the client script," and it does not work today.
Observed behavior
Two ways to split a server and client:
-
Sequentially (await rt.exec(server) then await rt.exec(client)): impossible. exec() is run-to-completion, so the server exec() never returns (it blocks until the guest process exits), and you never reach the client call.
-
Concurrently (fire the server exec() without awaiting, then await a separate client exec()): the client cannot reach the server. In a repro, the client polled http://127.0.0.1:3000/ 40x over ~4s and got no-response, even though the server process was still alive (it was killed by dispose() with exit 143). The two exec() processes ran concurrently but did not share a reachable loopback.
Repro
import { NodeRuntime } from "secure-exec";
const rt = await NodeRuntime.create({ permissions: { network: "allow" } });
// Server in exec #1 (do not await; never exits on its own).
rt.exec(`
import http from "node:http";
const app = http.createServer((req, res) => { res.writeHead(200); res.end("hi"); });
await new Promise(r => app.listen(3000, "127.0.0.1", r));
await new Promise(() => {});
`, { timeout: 20000 });
// Client in exec #2 (separate process, same VM).
const client = await rt.exec(`
let out = "no-response";
for (let i = 0; i < 40; i++) {
try { const r = await fetch("http://127.0.0.1:3000/"); out = r.status + " " + await r.text(); break; }
catch { await new Promise(r => setTimeout(r, 100)); }
}
console.log("client got:", out);
`, { timeout: 12000 });
console.log(client.stdout); // "client got: no-response"
await rt.dispose();
Expected
Two exec() calls in the same VM should be able to talk to each other over the VM's loopback (one VM = one shared network namespace), so a server in one exec() is reachable from a client in another. At minimum the limitation should be intentional and documented.
Notes / workarounds
- Today the working pattern is to keep the server and its request loop inside a single
exec() (same process = shared loopback), which is what the dev-servers doc example does.
- The host-to-guest path exists at the wire-protocol level via
vmFetch (addresses a running guest server by port and proxies a host request into it through the kernel socket table), but it is not yet surfaced on the public NodeRuntime API.
Worth deciding: should concurrent exec() processes in a VM share loopback (fix), or is per-exec() network isolation intentional (document + point users to vmFetch)?
Summary
A single
NodeRuntimeVM cannot run a long-lived server in oneexec()and reach it from a separateexec(). Concurrentexec()processes in the same VM do not share a reachable loopback, so a client started in a secondexec()cannot connect to a server started in the first.This came up while documenting the dev-servers use case: the natural API a user reaches for is "run the server script, then run the client script," and it does not work today.
Observed behavior
Two ways to split a server and client:
Sequentially (
await rt.exec(server)thenawait rt.exec(client)): impossible.exec()is run-to-completion, so the serverexec()never returns (it blocks until the guest process exits), and you never reach the client call.Concurrently (fire the server
exec()without awaiting, thenawaita separate clientexec()): the client cannot reach the server. In a repro, the client polledhttp://127.0.0.1:3000/40x over ~4s and gotno-response, even though the server process was still alive (it was killed bydispose()with exit 143). The twoexec()processes ran concurrently but did not share a reachable loopback.Repro
Expected
Two
exec()calls in the same VM should be able to talk to each other over the VM's loopback (one VM = one shared network namespace), so a server in oneexec()is reachable from a client in another. At minimum the limitation should be intentional and documented.Notes / workarounds
exec()(same process = shared loopback), which is what the dev-servers doc example does.vmFetch(addresses a running guest server by port and proxies a host request into it through the kernel socket table), but it is not yet surfaced on the publicNodeRuntimeAPI.Worth deciding: should concurrent
exec()processes in a VM share loopback (fix), or is per-exec()network isolation intentional (document + point users tovmFetch)?