@nimbus-sh/worker 0.2.3 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_shared/session-router.d.ts +8 -0
- package/dist/_shared/session-router.d.ts.map +1 -1
- package/dist/_shared/session-router.js +8 -0
- package/dist/esbuild-wasm-bundle.generated.d.ts +1 -1
- package/dist/esbuild-wasm-bundle.generated.js +1 -1
- package/dist/facets/cirrus-real.js +1 -1
- package/dist/facets/manager.d.ts +43 -129
- package/dist/facets/manager.d.ts.map +1 -1
- package/dist/facets/manager.js +107 -318
- package/dist/facets/opencode-staging.d.ts +1 -1
- package/dist/facets/opencode-staging.d.ts.map +1 -1
- package/dist/facets/opencode-staging.js +3 -0
- package/dist/facets/real-vite-hmr.js +1 -1
- package/dist/facets/vite-dev-server.d.ts +4 -4
- package/dist/facets/vite-dev-server.js +5 -5
- package/dist/git/commands.d.ts +8 -0
- package/dist/git/commands.d.ts.map +1 -1
- package/dist/git/commands.js +41 -9
- package/dist/git/network-facet.d.ts +1 -1
- package/dist/git/network-facet.d.ts.map +1 -1
- package/dist/git/network-facet.js +39 -15
- package/dist/git-bundle.generated.d.ts +2 -2
- package/dist/git-bundle.generated.d.ts.map +1 -1
- package/dist/git-bundle.generated.js +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -1
- package/dist/loaders/child-process/spawn-facet.js +1 -1
- package/dist/loaders/child-process/spawn-pool.d.ts.map +1 -1
- package/dist/loaders/child-process/spawn-pool.js +2 -2
- package/dist/loaders/generated-workers.d.ts +2 -2
- package/dist/loaders/generated-workers.d.ts.map +1 -1
- package/dist/loaders/generated-workers.js +3 -3
- package/dist/loaders/npm-resolve-preamble.d.ts +3 -3
- package/dist/loaders/npm-resolve-preamble.js +3 -3
- package/dist/loaders/pre-bundle-preamble.d.ts +5 -5
- package/dist/loaders/pre-bundle-preamble.js +6 -6
- package/dist/loaders/process-host.d.ts +6 -107
- package/dist/loaders/process-host.d.ts.map +1 -1
- package/dist/loaders/process-host.js +6 -405
- package/dist/npm/install-batch-facet.d.ts +1 -1
- package/dist/npm/install-batch-facet.js +1 -1
- package/dist/npm/installer.d.ts +6 -6
- package/dist/npm/installer.d.ts.map +1 -1
- package/dist/npm/installer.js +28 -27
- package/dist/npm/pre-bundle-facet.d.ts +2 -2
- package/dist/npm/pre-bundle-facet.js +5 -5
- package/dist/npm/r2-cache.d.ts +1 -1
- package/dist/npm/resolve-facet.d.ts +1 -1
- package/dist/npm/resolve-facet.js +1 -1
- package/dist/npm/resolve-one-facet.d.ts +5 -5
- package/dist/npm/resolve-one-facet.js +5 -5
- package/dist/router/index.js +2 -2
- package/dist/router/remote-api.js +9 -1
- package/dist/runtime/bun-repl.d.ts +1 -1
- package/dist/runtime/bun-repl.js +3 -3
- package/dist/runtime/esbuild-wasm-bytes.js +1 -1
- package/dist/runtime/facet-loader-host.d.ts +5 -8
- package/dist/runtime/facet-loader-host.d.ts.map +1 -1
- package/dist/runtime/facet-loader-host.js +7 -11
- package/dist/runtime/node-repl.js +2 -2
- package/dist/runtime/node-shims-artifact.js +1 -1
- package/dist/runtime/node-shims.js +2 -1
- package/dist/runtime/opencode-artifact.js +1 -1
- package/dist/runtime/opentui-wasm-bytes.js +1 -1
- package/dist/runtime/python-repl.js +3 -3
- package/dist/runtime/ruby-repl.js +2 -2
- package/dist/runtime/ruby-resident.js +1 -1
- package/dist/runtime/sqlite-wasm-bytes.js +1 -1
- package/dist/session/diag.js +1 -1
- package/dist/session/hibernation.d.ts +20 -53
- package/dist/session/hibernation.d.ts.map +1 -1
- package/dist/session/hibernation.js +57 -194
- package/dist/session/init-phases.d.ts +2 -3
- package/dist/session/init-phases.d.ts.map +1 -1
- package/dist/session/init-phases.js +3 -2
- package/dist/session/init.d.ts.map +1 -1
- package/dist/session/init.js +8 -4
- package/dist/session/keys.d.ts +14 -37
- package/dist/session/keys.d.ts.map +1 -1
- package/dist/session/keys.js +18 -37
- package/dist/session/nimbus-session.d.ts +10 -15
- package/dist/session/nimbus-session.d.ts.map +1 -1
- package/dist/session/nimbus-session.js +28 -31
- package/dist/session/port-capability.d.ts +43 -0
- package/dist/session/port-capability.d.ts.map +1 -0
- package/dist/session/port-capability.js +49 -0
- package/dist/session/programmatic.d.ts +39 -5
- package/dist/session/programmatic.d.ts.map +1 -1
- package/dist/session/programmatic.js +150 -19
- package/dist/session/routes.d.ts +2 -0
- package/dist/session/routes.d.ts.map +1 -1
- package/dist/session/routes.js +96 -20
- package/dist/session/rpc.d.ts +36 -9
- package/dist/session/rpc.d.ts.map +1 -1
- package/dist/session/rpc.js +84 -24
- package/dist/session/start-real-vite.d.ts.map +1 -1
- package/dist/session/start-real-vite.js +3 -1
- package/dist/session/state-store.d.ts +3 -2
- package/dist/session/state-store.d.ts.map +1 -1
- package/dist/session/state-store.js +3 -2
- package/dist/session/supervisor-rpc.d.ts.map +1 -1
- package/dist/session/supervisor-rpc.js +7 -4
- package/dist/session/ws.d.ts +1 -3
- package/dist/session/ws.d.ts.map +1 -1
- package/dist/session/ws.js +4 -3
- package/dist/wrangler/nimbus-wrangler.js +1 -1
- package/package.json +4 -2
- package/scripts/bundle-esbuild-wasm.mjs +2 -2
- package/scripts/bundle-facet-workers.mjs +5 -7
- package/scripts/bundle-sqlite-wasm.mjs +1 -1
- package/dist/facets/inner-do-registry.d.ts +0 -41
- package/dist/facets/inner-do-registry.d.ts.map +0 -1
- package/dist/facets/inner-do-registry.js +0 -51
- package/dist/facets/launch-pacer.d.ts +0 -111
- package/dist/facets/launch-pacer.d.ts.map +0 -1
- package/dist/facets/launch-pacer.js +0 -130
- package/dist/loaders/fanout-pool.d.ts +0 -207
- package/dist/loaders/fanout-pool.d.ts.map +0 -1
- package/dist/loaders/fanout-pool.js +0 -363
- package/dist/loaders/loader-pool.d.ts +0 -294
- package/dist/loaders/loader-pool.d.ts.map +0 -1
- package/dist/loaders/loader-pool.js +0 -642
- package/dist/loaders/process-fabric.d.ts +0 -509
- package/dist/loaders/process-fabric.d.ts.map +0 -1
- package/dist/loaders/process-fabric.js +0 -360
- package/dist/loaders/vendor/errors.d.ts +0 -24
- package/dist/loaders/vendor/errors.d.ts.map +0 -1
- package/dist/loaders/vendor/errors.js +0 -46
- package/dist/loaders/vendor/serialize.d.ts +0 -3
- package/dist/loaders/vendor/serialize.d.ts.map +0 -1
- package/dist/loaders/vendor/serialize.js +0 -25
- package/dist/loaders/vendor/types.d.ts +0 -69
- package/dist/loaders/vendor/types.d.ts.map +0 -1
- package/dist/loaders/vendor/types.js +0 -4
- package/dist/loaders/workerd-facet-host.d.ts +0 -141
- package/dist/loaders/workerd-facet-host.d.ts.map +0 -1
- package/dist/loaders/workerd-facet-host.js +0 -284
- package/dist/session/bindings.d.ts +0 -209
- package/dist/session/bindings.d.ts.map +0 -1
- package/dist/session/bindings.js +0 -680
- package/dist/session/ctx-exports.d.ts +0 -16
- package/dist/session/ctx-exports.d.ts.map +0 -1
- package/dist/session/ctx-exports.js +0 -22
- package/dist/session/ws-hibernation-config.d.ts +0 -64
- package/dist/session/ws-hibernation-config.d.ts.map +0 -1
- package/dist/session/ws-hibernation-config.js +0 -89
|
@@ -14,7 +14,96 @@ import { PortRegistry } from '@nimbus-sh/core/runtime/port-registry.js';
|
|
|
14
14
|
import { CRED_KERNEL } from '@nimbus-sh/core/runtime/os-contracts.js';
|
|
15
15
|
import { endProcessInput, resizeProcess, signalProcess, writeProcessInput, } from '@nimbus-sh/core/runtime/process-input-routing.js';
|
|
16
16
|
import { z } from 'zod/v4';
|
|
17
|
-
import { SESSION_DESTROYED_KEY,
|
|
17
|
+
import { SESSION_DESTROYED_KEY, SHELL_STATE_KEY_PREFIX, VITE_CONFIG_KEY } from './keys.js';
|
|
18
|
+
import { clearPortCapability, persistPortCapability, restorePortCapability, } from './port-capability.js';
|
|
19
|
+
import { GENERATION_KEY, assumeGeneration, generation } from '@nimbus-sh/fabric/generation.js';
|
|
20
|
+
import { HeadlessTerminal, Shell } from '@nimbus-sh/core/substrate/lifo/index.js';
|
|
21
|
+
const ShellIdSchema = z.string().min(1).max(160).regex(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/);
|
|
22
|
+
const ShellStateSchema = z.object({
|
|
23
|
+
cwd: z.string().startsWith('/'),
|
|
24
|
+
env: z.record(z.string(), z.string()),
|
|
25
|
+
}).strict();
|
|
26
|
+
/**
|
|
27
|
+
* A second Shell over the session's own kernel, filesystem and command
|
|
28
|
+
* registry — the same objects the interactive shell uses, so a named shell is
|
|
29
|
+
* not a second filesystem or a second process table. Only cwd and environment
|
|
30
|
+
* are its own, which is exactly what makes `cd` stick between calls.
|
|
31
|
+
*/
|
|
32
|
+
export function createProgrammaticShell(self, pid, state) {
|
|
33
|
+
const parent = self.shell;
|
|
34
|
+
if (!parent)
|
|
35
|
+
throw new Error('Nimbus shell did not initialize');
|
|
36
|
+
const shell = new Shell(new HeadlessTerminal(), parent.getVfs(), parent.getRegistry(), { ...parent.getEnv(), ...state.env, $: String(pid) }, parent.getProcessRegistry(), {
|
|
37
|
+
pid,
|
|
38
|
+
get cred() { return self.processes.cred(pid); },
|
|
39
|
+
setUmask: (mask) => self.processes.setUmask(pid, mask),
|
|
40
|
+
runAs: parent.getRunAsHost(),
|
|
41
|
+
});
|
|
42
|
+
shell.setCwd(state.cwd);
|
|
43
|
+
return shell;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Run `body` against a named shell's durable state, or against nothing when no
|
|
47
|
+
* `shellId` was given.
|
|
48
|
+
*
|
|
49
|
+
* Serialized per id: two concurrent calls naming one shell would otherwise
|
|
50
|
+
* read the same cwd and race to write it back, and the loser's `cd` would
|
|
51
|
+
* vanish. A background job reads the state but does not write it back — its
|
|
52
|
+
* shell outlives the call, so what it would persist is a snapshot of a moment
|
|
53
|
+
* nobody asked about.
|
|
54
|
+
*/
|
|
55
|
+
async function withShellState(self, options, background, run) {
|
|
56
|
+
if (options.shellId === undefined)
|
|
57
|
+
return run(null);
|
|
58
|
+
const id = ShellIdSchema.parse(options.shellId);
|
|
59
|
+
self._programmaticShellQueues ??= new Map();
|
|
60
|
+
const queues = self._programmaticShellQueues;
|
|
61
|
+
const previous = queues.get(id) ?? Promise.resolve();
|
|
62
|
+
let release = () => { };
|
|
63
|
+
const gate = new Promise((resolve) => { release = resolve; });
|
|
64
|
+
const tail = previous.catch(() => undefined).then(() => gate);
|
|
65
|
+
queues.set(id, tail);
|
|
66
|
+
await previous.catch(() => undefined);
|
|
67
|
+
try {
|
|
68
|
+
const key = `${SHELL_STATE_KEY_PREFIX}${id}`;
|
|
69
|
+
const stored = await self.ctx.storage.get(key);
|
|
70
|
+
const root = typeof options.shellRoot === 'string' && options.shellRoot.startsWith('/')
|
|
71
|
+
? options.shellRoot
|
|
72
|
+
: getHome(self);
|
|
73
|
+
const state = stored === undefined
|
|
74
|
+
? { cwd: root, env: {} }
|
|
75
|
+
: ShellStateSchema.parse(stored);
|
|
76
|
+
let shell = null;
|
|
77
|
+
try {
|
|
78
|
+
return await run({
|
|
79
|
+
cwd: state.cwd,
|
|
80
|
+
create: (pid) => {
|
|
81
|
+
shell = createProgrammaticShell(self, pid, state);
|
|
82
|
+
return shell;
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
const captured = background || shell === null ? state : capturedShellState(shell);
|
|
88
|
+
await self.ctx.storage.put(key, captured);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
release();
|
|
93
|
+
if (queues.get(id) === tail)
|
|
94
|
+
queues.delete(id);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function capturedShellState(shell) {
|
|
98
|
+
const env = { ...shell.getEnv() };
|
|
99
|
+
// `$` is the pid of the call that just ended, not state of the shell.
|
|
100
|
+
delete env.$;
|
|
101
|
+
return { cwd: shell.getCwd(), env };
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* The durable half of the port capability lives in `./port-capability.js`, so
|
|
105
|
+
* the facet manager can retire a stale one without importing this module.
|
|
106
|
+
*/
|
|
18
107
|
const ProcessLogsOptionsSchema = z.object({
|
|
19
108
|
cursor: z.number().int().nonnegative().optional(),
|
|
20
109
|
lines: z.number().int().nonnegative().optional(),
|
|
@@ -101,13 +190,19 @@ export async function ensureProgrammaticReady(self, options = {}) {
|
|
|
101
190
|
}
|
|
102
191
|
return { ok: true, preinstalled: preinstall };
|
|
103
192
|
}
|
|
104
|
-
function startShellJob(self, command, options, job) {
|
|
105
|
-
const
|
|
106
|
-
if (!
|
|
193
|
+
function startShellJob(self, command, options, job, scoped) {
|
|
194
|
+
const parentShell = self.shell;
|
|
195
|
+
if (!parentShell)
|
|
107
196
|
throw new Error('Nimbus shell did not initialize');
|
|
108
197
|
const line = String(command);
|
|
109
|
-
const cwd = options.cwd ??
|
|
110
|
-
const entry = self.processes.spawn(line, [line], cwd, {
|
|
198
|
+
const cwd = options.cwd ?? scoped?.cwd ?? parentShell.getCwd?.() ?? '/home/user';
|
|
199
|
+
const entry = self.processes.spawn(line, [line], cwd, {
|
|
200
|
+
longRunning: job.background,
|
|
201
|
+
cred: options.cred,
|
|
202
|
+
});
|
|
203
|
+
// The scoped shell is built around the pid, so its `$` and its credential
|
|
204
|
+
// are the ones this command actually runs under.
|
|
205
|
+
const shell = scoped?.create(entry.pid) ?? parentShell;
|
|
111
206
|
const pid = entry.pid;
|
|
112
207
|
if (job.background)
|
|
113
208
|
self.processes.openInput(pid);
|
|
@@ -129,8 +224,11 @@ function startShellJob(self, command, options, job) {
|
|
|
129
224
|
sink?.(text);
|
|
130
225
|
};
|
|
131
226
|
const run = shell.execute(line, {
|
|
132
|
-
cwd
|
|
133
|
-
|
|
227
|
+
// A named shell already holds its own cwd and env; passing them again
|
|
228
|
+
// would pin it to the values this call started with and `cd` would not
|
|
229
|
+
// survive the call, which is the whole point of naming one.
|
|
230
|
+
cwd: scoped ? options.cwd : cwd,
|
|
231
|
+
env: scoped ? options.env : { ...(shell.getEnv?.() ?? {}), ...(options.env ?? {}) },
|
|
134
232
|
onStdout: emit('stdout', job.onStdout),
|
|
135
233
|
onStderr: emit('stderr', job.onStderr),
|
|
136
234
|
signal: controller.signal,
|
|
@@ -161,6 +259,9 @@ function startShellJob(self, command, options, job) {
|
|
|
161
259
|
}
|
|
162
260
|
export async function rpcExec(self, command, options = {}) {
|
|
163
261
|
await ensureProgrammaticReady(self, options);
|
|
262
|
+
return withShellState(self, options, false, (scoped) => execOnShell(self, command, options, scoped));
|
|
263
|
+
}
|
|
264
|
+
async function execOnShell(self, command, options, scoped) {
|
|
164
265
|
const stdout = [];
|
|
165
266
|
const stderr = [];
|
|
166
267
|
const started = Date.now();
|
|
@@ -170,7 +271,7 @@ export async function rpcExec(self, command, options = {}) {
|
|
|
170
271
|
background: false,
|
|
171
272
|
onStdout: (d) => stdout.push(d),
|
|
172
273
|
onStderr: (d) => stderr.push(d),
|
|
173
|
-
});
|
|
274
|
+
}, scoped);
|
|
174
275
|
let result;
|
|
175
276
|
try {
|
|
176
277
|
result = options.timeoutMs && options.timeoutMs > 0
|
|
@@ -249,7 +350,10 @@ function collectJobOutput(self, pid) {
|
|
|
249
350
|
*/
|
|
250
351
|
export async function rpcStartProcess(self, command, options = {}) {
|
|
251
352
|
await ensureProgrammaticReady(self, options);
|
|
252
|
-
|
|
353
|
+
return withShellState(self, options, true, (scoped) => startOnShell(self, command, options, scoped));
|
|
354
|
+
}
|
|
355
|
+
async function startOnShell(self, command, options, scoped) {
|
|
356
|
+
const job = startShellJob(self, command, options, { background: true }, scoped);
|
|
253
357
|
const line = String(command);
|
|
254
358
|
notifyTerminalEvent(self.terminal ?? null, {
|
|
255
359
|
type: 'spawn', pid: job.pid, command: line, longRunning: true, attachedTty: false,
|
|
@@ -411,24 +515,51 @@ export async function rpcProcessLogs(self, pid, options = {}) {
|
|
|
411
515
|
}
|
|
412
516
|
export async function rpcListPorts(self) {
|
|
413
517
|
await ensureProgrammaticReady(self);
|
|
414
|
-
|
|
518
|
+
const entries = self.portRegistry.getAll();
|
|
519
|
+
// Persisted at the moment the embedder is told the value, not at
|
|
520
|
+
// registration: a capability nobody has been handed does not need to
|
|
521
|
+
// survive anything.
|
|
522
|
+
await Promise.all(entries.map((entry) => persistPortCapability(self, entry.port, entry.capability)));
|
|
523
|
+
return entries.map(serializePort);
|
|
415
524
|
}
|
|
416
525
|
export async function rpcExposePort(self, port) {
|
|
417
526
|
await ensureProgrammaticReady(self);
|
|
418
527
|
const n = Number(port);
|
|
419
528
|
const entry = self.portRegistry.get(n);
|
|
529
|
+
if (entry)
|
|
530
|
+
await persistPortCapability(self, n, entry.capability);
|
|
420
531
|
return {
|
|
421
532
|
port: n,
|
|
422
533
|
listening: !!entry,
|
|
423
534
|
pid: entry?.pid ?? null,
|
|
424
535
|
registeredAt: entry?.registeredAt ?? null,
|
|
536
|
+
capability: entry?.capability ?? null,
|
|
425
537
|
};
|
|
426
538
|
}
|
|
427
539
|
export async function rpcUnexposePort(self, port) {
|
|
428
540
|
await ensureProgrammaticReady(self);
|
|
429
541
|
const n = Number(port);
|
|
542
|
+
// Before the unregister, so a crash between the two leaves a dead token
|
|
543
|
+
// rather than a live one.
|
|
544
|
+
await clearPortCapability(self, n);
|
|
430
545
|
return { port: n, ok: self.portRegistry.unregister(n) };
|
|
431
546
|
}
|
|
547
|
+
/**
|
|
548
|
+
* Route an embedder request that carries a port capability. The embedder has
|
|
549
|
+
* authenticated the capability at its edge and stripped its own credentials,
|
|
550
|
+
* so the guest's `Authorization` is preserved through this path and no other.
|
|
551
|
+
*/
|
|
552
|
+
export async function rpcRouteCapabilityPort(self, port, capability, request, pathname) {
|
|
553
|
+
await ensureProgrammaticReady(self);
|
|
554
|
+
await restorePortCapability(self, port);
|
|
555
|
+
if (request.headers.get('upgrade')?.toLowerCase() === 'websocket') {
|
|
556
|
+
// A 101 cannot cross the DO RPC boundary this entrypoint is reached
|
|
557
|
+
// through; the session fetch route is the one that keeps fetch semantics.
|
|
558
|
+
return new Response('WebSocket upgrades must use the session fetch route', { status: 409 });
|
|
559
|
+
}
|
|
560
|
+
const routed = await self.portRegistry.routeCapabilityRequest(Number(port), String(capability), request, pathname);
|
|
561
|
+
return routed ?? new Response('Not found', { status: 404 });
|
|
562
|
+
}
|
|
432
563
|
export async function rpcDeleteFile(self, path, options = {}) {
|
|
433
564
|
await ensureProgrammaticReady(self);
|
|
434
565
|
const p = String(path).replace(/^\/+/, '');
|
|
@@ -546,7 +677,7 @@ export async function rpcDestroy(self, options = {}) {
|
|
|
546
677
|
// current-generation (pid > pidBase) — landing its output on the
|
|
547
678
|
// destroyed/recreated session. Keep {tombstone, isolateGen} consistent.
|
|
548
679
|
try {
|
|
549
|
-
await self.ctx.storage.put(
|
|
680
|
+
await self.ctx.storage.put(GENERATION_KEY, generation(self.ctx));
|
|
550
681
|
}
|
|
551
682
|
catch { /* best-effort */ }
|
|
552
683
|
resetInMemorySessionState(self);
|
|
@@ -582,10 +713,10 @@ async function quiesceInMemorySessionState(self) {
|
|
|
582
713
|
* The generation the NEXT boot of this session will run as.
|
|
583
714
|
*
|
|
584
715
|
* rpcDestroy re-persists the pre-destroy generation after wiping storage,
|
|
585
|
-
* so `
|
|
716
|
+
* so `adoptGeneration` reads it back and bumps once — landing here.
|
|
586
717
|
*/
|
|
587
718
|
function successorGeneration(self) {
|
|
588
|
-
return (self.
|
|
719
|
+
return generation(self.ctx) + 1;
|
|
589
720
|
}
|
|
590
721
|
/**
|
|
591
722
|
* Install the empty process/port state a destroyed session leaves behind.
|
|
@@ -679,12 +810,11 @@ function resetInMemorySessionState(self) {
|
|
|
679
810
|
// Adopt the generation the next boot will derive from the counter
|
|
680
811
|
// rpcDestroy just re-persisted, so the in-memory pid floor and the
|
|
681
812
|
// persisted one agree. Deliberately left unpersisted: storage keeps the
|
|
682
|
-
// pre-destroy value, and
|
|
683
|
-
const
|
|
684
|
-
installEmptyProcessState(self,
|
|
813
|
+
// pre-destroy value, and adoptGeneration re-derives this one from it.
|
|
814
|
+
const successor = successorGeneration(self);
|
|
815
|
+
installEmptyProcessState(self, successor);
|
|
685
816
|
self._w9SchemaInit = false;
|
|
686
|
-
self.
|
|
687
|
-
self._w9IsolateGenPersisted = false;
|
|
817
|
+
assumeGeneration(self.ctx, successor);
|
|
688
818
|
try {
|
|
689
819
|
self._w9WireProcessLogPersist?.();
|
|
690
820
|
}
|
|
@@ -714,6 +844,7 @@ function serializePort(p) {
|
|
|
714
844
|
port: Number(p.port),
|
|
715
845
|
pid: Number(p.pid),
|
|
716
846
|
registeredAt: Number(p.registeredAt),
|
|
847
|
+
capability: String(p.capability),
|
|
717
848
|
};
|
|
718
849
|
}
|
|
719
850
|
function shellQuote(s) {
|
package/dist/session/routes.d.ts
CHANGED
|
@@ -24,6 +24,8 @@
|
|
|
24
24
|
* (~30 sites). RoutesHost = any pragmatic deviation, like InitHost in S6.
|
|
25
25
|
*/
|
|
26
26
|
type RoutesHost = any;
|
|
27
|
+
/** Route a capability-authenticated embedder request to a guest HTTP server. */
|
|
28
|
+
export declare function routeCapabilityPort(self: RoutesHost, port: number, capability: string, request: Request, innerPath: string): Promise<Response>;
|
|
27
29
|
export declare function handleFetch(self: RoutesHost, request: Request): Promise<Response>;
|
|
28
30
|
export {};
|
|
29
31
|
//# sourceMappingURL=routes.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/session/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/session/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAqDH,KAAK,UAAU,GAAG,GAAG,CAAC;AA6LtB,gFAAgF;AAChF,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,QAAQ,CAAC,CAEnB;AAyGD,wBAAsB,WAAW,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CA8+BvF"}
|
package/dist/session/routes.js
CHANGED
|
@@ -27,12 +27,12 @@ import { handleReplicaPreflight as _w12HandleReplicaPreflight } from '../replica
|
|
|
27
27
|
import { replicasSuspended as _w12ReplicasSuspended } from '../replica/suspension.js';
|
|
28
28
|
import { sanitizeUntrustedRequest } from '@nimbus-sh/core/_shared/untrusted-request.js';
|
|
29
29
|
import { matchLogsPath, handleLogsWebSocketRequest, handleProcessesListRequest, } from '../runtime/process-logs-api.js';
|
|
30
|
-
import { readDiagCounters } from '@nimbus-sh/
|
|
31
|
-
import { getFailures, getLastRpcFrame, getLastFacetId, getRecoveryEvents, recordRecoveryEvent, resetRecoveryEvents, } from '@nimbus-sh/
|
|
30
|
+
import { readDiagCounters } from '@nimbus-sh/platform/diag-counters.js';
|
|
31
|
+
import { getFailures, getLastRpcFrame, getLastFacetId, getRecoveryEvents, recordRecoveryEvent, resetRecoveryEvents, } from '@nimbus-sh/platform/oom-discriminator.js';
|
|
32
32
|
import { DEFAULT_VITE_PORT, LRU_MAX_ENTRIES } from '@nimbus-sh/core/constants.js';
|
|
33
33
|
import { BASE_PATH_HEADER } from '../_shared/session-router.js';
|
|
34
34
|
import { VITE_CONFIG_KEY } from './keys.js';
|
|
35
|
-
import { estimateSupervisorHeap, WORKERD_EVICTION_LABELS } from '@nimbus-sh/
|
|
35
|
+
import { estimateSupervisorHeap, WORKERD_EVICTION_LABELS } from '@nimbus-sh/platform/heap-estimate.js';
|
|
36
36
|
import { loadShellState, loadKernelMounts, getScrollbackStats, clearSessionState, appendScrollback, loadScrollback } from './state-store.js';
|
|
37
37
|
import { classifyWsUpgrade, joinExistingSession } from './init-phases.js';
|
|
38
38
|
import { EsbuildService } from '@nimbus-sh/core/runtime/esbuild-service.js';
|
|
@@ -40,7 +40,14 @@ import { ViteDevServer } from '../facets/vite-dev-server.js';
|
|
|
40
40
|
import { notifyTerminalEvent, wireProcessLogSocketBroadcast } from '../runtime/process-logs-api.js';
|
|
41
41
|
import { makeLongRunningPortStub } from '@nimbus-sh/core/runtime/long-running-handle.js';
|
|
42
42
|
import { startRealVite } from './start-real-vite.js';
|
|
43
|
-
import { getLoadedCodesStats } from '
|
|
43
|
+
import { getLoadedCodesStats } from '@nimbus-sh/fabric/bindings.js';
|
|
44
|
+
import { generation } from '@nimbus-sh/fabric/generation.js';
|
|
45
|
+
import { facetIdBudget } from '@nimbus-sh/fabric/budgets.js';
|
|
46
|
+
import { loaderLedgerStats } from '@nimbus-sh/fabric/budgets.js';
|
|
47
|
+
import { HOSTED_WEBSOCKET_CAPABILITY_HEADER, HOSTED_WEBSOCKET_KEY_HEADER, } from '@nimbus-sh/fabric/process-host.js';
|
|
48
|
+
import { routeHostedWebSocket } from './rpc.js';
|
|
49
|
+
import { clearPortCapability, persistPortCapability, readPortCapability, restorePortCapability, } from './port-capability.js';
|
|
50
|
+
import { PREVIEW_CAPABILITY_HEADER } from '../_shared/session-router.js';
|
|
44
51
|
import { renderNoDevServerHtml } from './helpers.js';
|
|
45
52
|
import { handleAgentRequest } from './agent.js';
|
|
46
53
|
import { captureSessionAiCredential } from './ai.js';
|
|
@@ -50,7 +57,7 @@ import { CRED_KERNEL } from '@nimbus-sh/core/runtime/os-contracts.js';
|
|
|
50
57
|
// uses (they did: the packument purge used a stale `/p/` segment).
|
|
51
58
|
import { R2CacheClient, packumentL2Url, tarballL2Url, parseTarballAddress } from '../npm/r2-cache.js';
|
|
52
59
|
import { fetchEsbuildWasmBytes, ESBUILD_WASM_L2_KEY } from '../runtime/esbuild-wasm-bytes.js';
|
|
53
|
-
import {
|
|
60
|
+
import { Fanout, IN_DO_THRESHOLD, MAX_PEER_FANOUT } from '@nimbus-sh/fabric/fanout.js';
|
|
54
61
|
import { z } from 'zod/v4';
|
|
55
62
|
const TestSpawnEmitterBodySchema = z.object({
|
|
56
63
|
lines: z.coerce.number().optional(),
|
|
@@ -105,6 +112,9 @@ async function restorePersistedDevServer(self, onlyPort) {
|
|
|
105
112
|
const port = (config.port && Number.isFinite(config.port)) ? config.port : DEFAULT_VITE_PORT;
|
|
106
113
|
if (onlyPort != null && onlyPort !== port)
|
|
107
114
|
return;
|
|
115
|
+
// Read BEFORE the restore: bringing the server back re-registers the port,
|
|
116
|
+
// and a fresh registration mints a capability nobody was ever handed.
|
|
117
|
+
const persistedCapability = await readPortCapability(self, port);
|
|
108
118
|
// A cirrus-real session rebuilds the real-vite facet, not the Cirrus shim —
|
|
109
119
|
// same key, same starter the `vite` builtin uses, so a woken real-vite
|
|
110
120
|
// session comes back as real-vite rather than degrading to the shim.
|
|
@@ -125,6 +135,7 @@ async function restorePersistedDevServer(self, onlyPort) {
|
|
|
125
135
|
configDir: config.configDir || config.root,
|
|
126
136
|
}).finally(() => { self._realViteRestore = null; });
|
|
127
137
|
await self._realViteRestore;
|
|
138
|
+
await readoptCapability(self, port, persistedCapability);
|
|
128
139
|
return;
|
|
129
140
|
}
|
|
130
141
|
self.ensureSqliteFs();
|
|
@@ -155,7 +166,9 @@ async function restorePersistedDevServer(self, onlyPort) {
|
|
|
155
166
|
// restored server across hibernation cycles.
|
|
156
167
|
try {
|
|
157
168
|
self.portRegistry.bindFacetStub(entry.pid, makeLongRunningPortStub(self.viteDevServer));
|
|
169
|
+
await clearPortCapability(self, port);
|
|
158
170
|
self.portRegistry.register(port, entry.pid);
|
|
171
|
+
await readoptCapability(self, port, persistedCapability);
|
|
159
172
|
self._viteShimPid = entry.pid;
|
|
160
173
|
self._viteShimPort = port;
|
|
161
174
|
}
|
|
@@ -179,8 +192,18 @@ async function restorePersistedDevServer(self, onlyPort) {
|
|
|
179
192
|
* the untrusted-code boundary and cannot carry it, and a plain user server on
|
|
180
193
|
* any other port is mounted at root and needs no rewriting.
|
|
181
194
|
*/
|
|
182
|
-
async function routeToSessionPort(self, port, request, innerPath, mountBase) {
|
|
195
|
+
async function routeToSessionPort(self, port, request, innerPath, mountBase, capability) {
|
|
183
196
|
await restorePersistedDevServer(self, port);
|
|
197
|
+
if (capability !== undefined) {
|
|
198
|
+
// A rebuilt supervisor holds a capability nobody was handed; the durable
|
|
199
|
+
// one is the value in circulation, so it wins before the check.
|
|
200
|
+
await restorePortCapability(self, port);
|
|
201
|
+
if (!self.portRegistry.hasCapability(port, capability)) {
|
|
202
|
+
// 404, not 403: a wrong capability must not confirm that the port is
|
|
203
|
+
// listening at all.
|
|
204
|
+
return new Response('Not found', { status: 404 });
|
|
205
|
+
}
|
|
206
|
+
}
|
|
184
207
|
if (port === self._viteShimPort) {
|
|
185
208
|
// The preview HMR WebSocket can't cross the port-registry RPC, so it is
|
|
186
209
|
// accepted in-DO for both dev servers — the same handling `/preview/` uses.
|
|
@@ -193,11 +216,28 @@ async function routeToSessionPort(self, port, request, innerPath, mountBase) {
|
|
|
193
216
|
return self.viteDevServer.handleRequest(request, innerPath, mountBase);
|
|
194
217
|
}
|
|
195
218
|
}
|
|
196
|
-
const proxied =
|
|
219
|
+
const proxied = capability === undefined
|
|
220
|
+
? await self.portRegistry.routeRequest(port, request, innerPath)
|
|
221
|
+
: await self.portRegistry.routeCapabilityRequest(port, capability, request, innerPath);
|
|
197
222
|
if (proxied)
|
|
198
223
|
return proxied;
|
|
199
224
|
return new Response(`No process listening on port ${port}`, { status: 502 });
|
|
200
225
|
}
|
|
226
|
+
/**
|
|
227
|
+
* Re-adopt a preview capability the embedder already holds, after a restore
|
|
228
|
+
* re-registered the port under a freshly minted one.
|
|
229
|
+
*/
|
|
230
|
+
async function readoptCapability(self, port, capability) {
|
|
231
|
+
if (!capability)
|
|
232
|
+
return;
|
|
233
|
+
if (self.portRegistry.restoreCapability(port, capability)) {
|
|
234
|
+
await persistPortCapability(self, port, capability);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/** Route a capability-authenticated embedder request to a guest HTTP server. */
|
|
238
|
+
export function routeCapabilityPort(self, port, capability, request, innerPath) {
|
|
239
|
+
return routeToSessionPort(self, port, request, normalizeForwardedHttpPath(innerPath), '', capability);
|
|
240
|
+
}
|
|
201
241
|
/**
|
|
202
242
|
* The public URL prefix a port-routed request is mounted at. `<port>--<sid>`
|
|
203
243
|
* hosts arrive with an empty base header (mounted at the origin root); the
|
|
@@ -294,6 +334,23 @@ async function parseJsonBody(request, schema) {
|
|
|
294
334
|
}
|
|
295
335
|
export async function handleFetch(self, request) {
|
|
296
336
|
const url = new URL(request.url);
|
|
337
|
+
// The peer end of the fetch-semantic WebSocket hop, before anything else:
|
|
338
|
+
// this request is a sibling coordinator's, not a browser's, and it names
|
|
339
|
+
// the hosted process rather than a route on this session. Both headers are
|
|
340
|
+
// stripped so the process never sees the transport that carried it.
|
|
341
|
+
const hostedWebSocket = request.headers.get(HOSTED_WEBSOCKET_KEY_HEADER);
|
|
342
|
+
if (hostedWebSocket) {
|
|
343
|
+
if (request.headers.get('upgrade')?.toLowerCase() !== 'websocket') {
|
|
344
|
+
return new Response('Expected WebSocket', { status: 426 });
|
|
345
|
+
}
|
|
346
|
+
const capability = request.headers.get(HOSTED_WEBSOCKET_CAPABILITY_HEADER);
|
|
347
|
+
if (!capability)
|
|
348
|
+
return new Response('Not found', { status: 404 });
|
|
349
|
+
const headers = new Headers(request.headers);
|
|
350
|
+
headers.delete(HOSTED_WEBSOCKET_KEY_HEADER);
|
|
351
|
+
headers.delete(HOSTED_WEBSOCKET_CAPABILITY_HEADER);
|
|
352
|
+
return routeHostedWebSocket(self, hostedWebSocket, capability, new Request(request.url, { method: request.method, headers }));
|
|
353
|
+
}
|
|
297
354
|
// Capture session basePath from the routing header (if forwarded by the
|
|
298
355
|
// Worker's session-router). Threaded through to ViteDevServer so the
|
|
299
356
|
// served app's module URLs, HMR paths, <base href>, and router basename
|
|
@@ -584,7 +641,7 @@ export async function handleFetch(self, request) {
|
|
|
584
641
|
// process.memoryUsage() and they were always zero — useless.
|
|
585
642
|
//
|
|
586
643
|
// C'.1 replaces the zero-everywhere readout with a deterministic
|
|
587
|
-
// estimator (src/
|
|
644
|
+
// estimator (src/@nimbus-sh/platform/heap-estimate.js) that sums known
|
|
588
645
|
// supervisor allocations from runtime counters. Every byte has a
|
|
589
646
|
// named contributor.
|
|
590
647
|
//
|
|
@@ -666,11 +723,19 @@ export async function handleFetch(self, request) {
|
|
|
666
723
|
// (32 entries) with FIFO eviction. The counters here let
|
|
667
724
|
// ops dashboards visualise the bound + the eviction rate.
|
|
668
725
|
loadedCodes: getLoadedCodesStats(),
|
|
726
|
+
// Per-DO Worker Loader accounting: distinct loader ids ever gotten
|
|
727
|
+
// (each permanently holds one of the ~5-6 dynamic-worker slots) and
|
|
728
|
+
// live/peak concurrent Loader fetches.
|
|
729
|
+
loader: loaderLedgerStats(self.ctx),
|
|
669
730
|
rpc: {
|
|
670
731
|
lastFrame: getLastRpcFrame(),
|
|
671
732
|
},
|
|
672
733
|
facet: {
|
|
673
734
|
lastDispatch: getLastFacetId(),
|
|
735
|
+
// Facet IDs consumed over this DO's LIFETIME against the 65,536 the
|
|
736
|
+
// platform will ever grant it. Append-only and never reclaimed;
|
|
737
|
+
// crossing the wall is unrecoverable for the object.
|
|
738
|
+
idBudget: await facetIdBudget(self.ctx),
|
|
674
739
|
},
|
|
675
740
|
// ── v3 / C' observability foundation ──────────────────────
|
|
676
741
|
heap,
|
|
@@ -685,7 +750,7 @@ export async function handleFetch(self, request) {
|
|
|
685
750
|
// `autoResponseConfigured` reports the runtime's actual
|
|
686
751
|
// capability (older workerd builds report false).
|
|
687
752
|
hib: {
|
|
688
|
-
isolateGen: self.
|
|
753
|
+
isolateGen: generation(self.ctx),
|
|
689
754
|
autoResponseConfigured: self._w9WsConfig?.autoResponseConfigured ?? false,
|
|
690
755
|
autoResponseError: self._w9WsConfig?.autoResponseError ?? null,
|
|
691
756
|
hibernationEventTimeoutMs: self._w9WsConfig?.timeoutSetMs ?? null,
|
|
@@ -852,7 +917,7 @@ export async function handleFetch(self, request) {
|
|
|
852
917
|
fromState: body.fromState,
|
|
853
918
|
toState: body.toState,
|
|
854
919
|
trigger: body.trigger,
|
|
855
|
-
isolateGen: body.isolateGen || self.
|
|
920
|
+
isolateGen: body.isolateGen || generation(self.ctx),
|
|
856
921
|
dataLoss: body.dataLoss === true,
|
|
857
922
|
snapshotKeysRehydrated: body.snapshotKeysRehydrated || 0,
|
|
858
923
|
notes: body.notes,
|
|
@@ -890,7 +955,7 @@ export async function handleFetch(self, request) {
|
|
|
890
955
|
}
|
|
891
956
|
// ── two-tier-fanout primitive probe ──────────────────────────────
|
|
892
957
|
// in-DO fanout in-DO and peer-DO fanout peer-DO speedups via the
|
|
893
|
-
//
|
|
958
|
+
// Fanout primitive. Independent of any specific
|
|
894
959
|
// production site (install-batch, pre-bundle, etc.) so the
|
|
895
960
|
// primitive's behavior can be measured cleanly without
|
|
896
961
|
// confounders.
|
|
@@ -1016,6 +1081,7 @@ export async function handleFetch(self, request) {
|
|
|
1016
1081
|
try {
|
|
1017
1082
|
const apiViteStub = makeLongRunningPortStub(self.viteDevServer);
|
|
1018
1083
|
self.portRegistry.bindFacetStub(apiViteEntry.pid, apiViteStub);
|
|
1084
|
+
await clearPortCapability(self, apiVitePort);
|
|
1019
1085
|
self.portRegistry.register(apiVitePort, apiViteEntry.pid);
|
|
1020
1086
|
self._viteShimPid = apiViteEntry.pid;
|
|
1021
1087
|
self._viteShimPort = apiVitePort;
|
|
@@ -1220,7 +1286,10 @@ export async function handleFetch(self, request) {
|
|
|
1220
1286
|
if (portMatch) {
|
|
1221
1287
|
const port = parseInt(portMatch[1]);
|
|
1222
1288
|
const path = normalizeForwardedHttpPath(portMatch[2] || '/');
|
|
1223
|
-
|
|
1289
|
+
// An embedder that authenticated a preview capability at its own edge
|
|
1290
|
+
// forwards it here; the guest's Authorization then survives the hop.
|
|
1291
|
+
const capability = request.headers.get(PREVIEW_CAPABILITY_HEADER);
|
|
1292
|
+
return routeToSessionPort(self, port, request, path, portRouteMountBase(request, port), capability ?? undefined);
|
|
1224
1293
|
}
|
|
1225
1294
|
// ── /api/_diag/cache — per-tier cache observability ───────────────
|
|
1226
1295
|
//
|
|
@@ -1240,7 +1309,12 @@ export async function handleFetch(self, request) {
|
|
|
1240
1309
|
const { readExecTelemetry } = await import('../facets/exec-telemetry.js');
|
|
1241
1310
|
return Response.json({ records: readExecTelemetry() });
|
|
1242
1311
|
}
|
|
1312
|
+
// Write surfaces gated like /api/_test/*: zeroing telemetry from prod
|
|
1313
|
+
// would erase the evidence an investigation is standing on, so without
|
|
1314
|
+
// NIMBUS_DEBUG these 404 rather than being a free reset vector.
|
|
1243
1315
|
if (url.pathname === '/api/_diag/exec/reset' && request.method === 'POST') {
|
|
1316
|
+
if (!self.nimbusDebug)
|
|
1317
|
+
return new Response('not found', { status: 404 });
|
|
1244
1318
|
const { resetExecTelemetry } = await import('../facets/exec-telemetry.js');
|
|
1245
1319
|
resetExecTelemetry();
|
|
1246
1320
|
return new Response(null, { status: 204 });
|
|
@@ -1251,6 +1325,8 @@ export async function handleFetch(self, request) {
|
|
|
1251
1325
|
return Response.json(snap);
|
|
1252
1326
|
}
|
|
1253
1327
|
if (url.pathname === '/api/_diag/cache/reset' && request.method === 'POST') {
|
|
1328
|
+
if (!self.nimbusDebug)
|
|
1329
|
+
return new Response('not found', { status: 404 });
|
|
1254
1330
|
const { reset } = await import('@nimbus-sh/core/_shared/cache-stats.js');
|
|
1255
1331
|
reset();
|
|
1256
1332
|
return new Response(null, { status: 204 });
|
|
@@ -1396,7 +1472,7 @@ async function handleCacheTestEndpoint(self, url, request) {
|
|
|
1396
1472
|
}
|
|
1397
1473
|
// ── two-tier-fanout primitive benchmark endpoint ────────────────────────
|
|
1398
1474
|
//
|
|
1399
|
-
// Routes under /api/_test/fanout/* exercise
|
|
1475
|
+
// Routes under /api/_test/fanout/* exercise Fanout's two
|
|
1400
1476
|
// topologies (in-DO fanout in-DO + peer-DO fanout peer-DO) via a synthetic workload
|
|
1401
1477
|
// that's independent of the production install-batch / pre-bundle
|
|
1402
1478
|
// sites. The probe measures speedup, peer-DO routing determinism,
|
|
@@ -1425,7 +1501,7 @@ async function handleFanoutTestEndpoint(self, url, request) {
|
|
|
1425
1501
|
const path = url.pathname;
|
|
1426
1502
|
if (path === '/api/_test/fanout/topology' && request.method === 'GET') {
|
|
1427
1503
|
const n = Math.max(0, parseInt(url.searchParams.get('n') || '0', 10));
|
|
1428
|
-
const pool = new
|
|
1504
|
+
const pool = new Fanout(env, self.ctx, {
|
|
1429
1505
|
tag: 'fanout-bench',
|
|
1430
1506
|
timeoutMs: 60_000,
|
|
1431
1507
|
});
|
|
@@ -1440,7 +1516,7 @@ async function handleFanoutTestEndpoint(self, url, request) {
|
|
|
1440
1516
|
const keysRaw = url.searchParams.get('keys') || '';
|
|
1441
1517
|
const keys = keysRaw.split(',').map((k) => k.trim()).filter(Boolean);
|
|
1442
1518
|
const peerCount = Math.max(1, Math.min(parseInt(url.searchParams.get('n') || String(keys.length), 10), MAX_PEER_FANOUT));
|
|
1443
|
-
const pool = new
|
|
1519
|
+
const pool = new Fanout(env, self.ctx, {
|
|
1444
1520
|
tag: 'fanout-bench',
|
|
1445
1521
|
timeoutMs: 60_000,
|
|
1446
1522
|
});
|
|
@@ -1454,7 +1530,7 @@ async function handleFanoutTestEndpoint(self, url, request) {
|
|
|
1454
1530
|
const body = await parseJsonBody(request, FanoutBenchBodySchema);
|
|
1455
1531
|
const n = Math.max(1, Math.min(64, body.n || 8));
|
|
1456
1532
|
const sleepMs = Math.max(0, Math.min(2000, body.sleepMs || 100));
|
|
1457
|
-
const pool = new
|
|
1533
|
+
const pool = new Fanout(env, self.ctx, {
|
|
1458
1534
|
tag: 'fanout-bench',
|
|
1459
1535
|
timeoutMs: 60_000,
|
|
1460
1536
|
});
|
|
@@ -1470,7 +1546,7 @@ async function handleFanoutTestEndpoint(self, url, request) {
|
|
|
1470
1546
|
const results = await pool.submitMany(tasks, async (item, env) => {
|
|
1471
1547
|
const startMs = Date.now();
|
|
1472
1548
|
// Identify which env we're running in. SUPERVISOR is the
|
|
1473
|
-
// RPC stub auto-injected by
|
|
1549
|
+
// RPC stub auto-injected by IsolatePool; its presence
|
|
1474
1550
|
// tells us we're inside a loader isolate (not the supervisor).
|
|
1475
1551
|
const loaderEnvKeys = Object.keys(env || {}).sort();
|
|
1476
1552
|
// Sleep entirely inside the isolate — no external network.
|
|
@@ -1510,11 +1586,11 @@ async function handleFanoutTestEndpoint(self, url, request) {
|
|
|
1510
1586
|
const n = Math.max(1, Math.min(64, body.n || 8));
|
|
1511
1587
|
const sleepMs = Math.max(0, Math.min(2000, body.sleepMs || 100));
|
|
1512
1588
|
// Same workload, but FORCE serial dispatch by using a single
|
|
1513
|
-
//
|
|
1589
|
+
// IsolatePool with concurrency=1 and submitting one task
|
|
1514
1590
|
// at a time. This is the T_serial reference for the 5× speedup
|
|
1515
1591
|
// assertion.
|
|
1516
|
-
const {
|
|
1517
|
-
const pool = new
|
|
1592
|
+
const { IsolatePool } = await import('@nimbus-sh/fabric/isolate-pool.js');
|
|
1593
|
+
const pool = new IsolatePool(env, self.ctx, {
|
|
1518
1594
|
concurrency: 1,
|
|
1519
1595
|
timeoutMs: 60_000,
|
|
1520
1596
|
tag: 'fanout-serial',
|