@volter/browser-wali 0.1.166 → 0.1.167
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/LICENSE +8 -0
- package/README.md +9 -0
- package/dist/browser-wali-command.d.ts +7 -0
- package/dist/browser-wali-command.d.ts.map +1 -1
- package/dist/browser-wali-command.js +70 -18
- package/dist/browser-wali-command.js.map +1 -1
- package/dist/bulk-memory.d.ts +4 -2
- package/dist/bulk-memory.d.ts.map +1 -1
- package/dist/bulk-memory.js +32 -9
- package/dist/bulk-memory.js.map +1 -1
- package/dist/capabilities.d.ts +1 -1
- package/dist/capabilities.d.ts.map +1 -1
- package/dist/capabilities.js +5 -2
- package/dist/capabilities.js.map +1 -1
- package/dist/child-processes.d.ts +2 -0
- package/dist/child-processes.d.ts.map +1 -1
- package/dist/child-processes.js +9 -1
- package/dist/child-processes.js.map +1 -1
- package/dist/clocks.d.ts +3 -0
- package/dist/clocks.d.ts.map +1 -0
- package/dist/clocks.js +44 -0
- package/dist/clocks.js.map +1 -0
- package/dist/coherent-file-locks.d.ts.map +1 -1
- package/dist/coherent-file-locks.js +2 -1
- package/dist/coherent-file-locks.js.map +1 -1
- package/dist/credentials.d.ts +4 -0
- package/dist/credentials.d.ts.map +1 -0
- package/dist/credentials.js +46 -0
- package/dist/credentials.js.map +1 -0
- package/dist/descriptors.d.ts +14 -0
- package/dist/descriptors.d.ts.map +1 -1
- package/dist/descriptors.js +45 -5
- package/dist/descriptors.js.map +1 -1
- package/dist/environment.d.ts +4 -0
- package/dist/environment.d.ts.map +1 -0
- package/dist/environment.js +23 -0
- package/dist/environment.js.map +1 -0
- package/dist/epoll-ready.d.ts +27 -0
- package/dist/epoll-ready.d.ts.map +1 -0
- package/dist/epoll-ready.js +88 -0
- package/dist/epoll-ready.js.map +1 -0
- package/dist/events.d.ts +1 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +55 -24
- package/dist/events.js.map +1 -1
- package/dist/filesystem-host-session.d.ts +65 -0
- package/dist/filesystem-host-session.d.ts.map +1 -0
- package/dist/filesystem-host-session.js +951 -0
- package/dist/filesystem-host-session.js.map +1 -0
- package/dist/filesystem-host.d.ts +13 -81
- package/dist/filesystem-host.d.ts.map +1 -1
- package/dist/filesystem-host.js +14 -882
- package/dist/filesystem-host.js.map +1 -1
- package/dist/filesystem-protocol.d.ts +3 -1
- package/dist/filesystem-protocol.d.ts.map +1 -1
- package/dist/filesystem-remote.d.ts +8 -3
- package/dist/filesystem-remote.d.ts.map +1 -1
- package/dist/filesystem-remote.js +42 -20
- package/dist/filesystem-remote.js.map +1 -1
- package/dist/filesystem-service-client.d.ts +4 -0
- package/dist/filesystem-service-client.d.ts.map +1 -0
- package/dist/filesystem-service-client.js +16 -0
- package/dist/filesystem-service-client.js.map +1 -0
- package/dist/filesystem.d.ts +25 -4
- package/dist/filesystem.d.ts.map +1 -1
- package/dist/filesystem.js +548 -169
- package/dist/filesystem.js.map +1 -1
- package/dist/fork-memory.d.ts +23 -0
- package/dist/fork-memory.d.ts.map +1 -0
- package/dist/fork-memory.js +93 -0
- package/dist/fork-memory.js.map +1 -0
- package/dist/fork-worker.js +32 -11
- package/dist/fork-worker.js.map +1 -1
- package/dist/jumps.d.ts +7 -0
- package/dist/jumps.d.ts.map +1 -1
- package/dist/jumps.js +19 -1
- package/dist/jumps.js.map +1 -1
- package/dist/kernel-filesystem-worker.d.ts +2 -0
- package/dist/kernel-filesystem-worker.d.ts.map +1 -0
- package/dist/kernel-filesystem-worker.js +28 -0
- package/dist/kernel-filesystem-worker.js.map +1 -0
- package/dist/kernel-filesystem.d.ts +12 -0
- package/dist/kernel-filesystem.d.ts.map +1 -0
- package/dist/kernel-filesystem.js +45 -0
- package/dist/kernel-filesystem.js.map +1 -0
- package/dist/kernel-network-worker.d.ts +2 -0
- package/dist/kernel-network-worker.d.ts.map +1 -0
- package/dist/kernel-network-worker.js +14 -0
- package/dist/kernel-network-worker.js.map +1 -0
- package/dist/kernel-network.d.ts +14 -0
- package/dist/kernel-network.d.ts.map +1 -0
- package/dist/kernel-network.js +50 -0
- package/dist/kernel-network.js.map +1 -0
- package/dist/kernel-process-client.d.ts +6 -1
- package/dist/kernel-process-client.d.ts.map +1 -1
- package/dist/kernel-process-client.js +10 -1
- package/dist/kernel-process-client.js.map +1 -1
- package/dist/module-layout.d.ts +8 -0
- package/dist/module-layout.d.ts.map +1 -1
- package/dist/module-layout.js +263 -2
- package/dist/module-layout.js.map +1 -1
- package/dist/network-host.d.ts +6 -0
- package/dist/network-host.d.ts.map +1 -0
- package/dist/network-host.js +1582 -0
- package/dist/network-host.js.map +1 -0
- package/dist/network-protocol.d.ts +9 -2
- package/dist/network-protocol.d.ts.map +1 -1
- package/dist/network-protocol.js +3 -0
- package/dist/network-protocol.js.map +1 -1
- package/dist/network-service-client.d.ts +4 -0
- package/dist/network-service-client.d.ts.map +1 -0
- package/dist/network-service-client.js +16 -0
- package/dist/network-service-client.js.map +1 -0
- package/dist/network-worker.js +5 -1150
- package/dist/network-worker.js.map +1 -1
- package/dist/network.d.ts +13 -1
- package/dist/network.d.ts.map +1 -1
- package/dist/network.js +175 -48
- package/dist/network.js.map +1 -1
- package/dist/paths.d.ts +11 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +97 -0
- package/dist/paths.js.map +1 -0
- package/dist/process-bridge.d.ts +12 -4
- package/dist/process-bridge.d.ts.map +1 -1
- package/dist/process-bridge.js +53 -17
- package/dist/process-bridge.js.map +1 -1
- package/dist/program.d.ts.map +1 -1
- package/dist/program.js +8 -0
- package/dist/program.js.map +1 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +150 -92
- package/dist/runtime.js.map +1 -1
- package/dist/shared-buffer-pool.d.ts +18 -5
- package/dist/shared-buffer-pool.d.ts.map +1 -1
- package/dist/shared-buffer-pool.js +86 -14
- package/dist/shared-buffer-pool.js.map +1 -1
- package/dist/shared-file-layer.d.ts +3 -1
- package/dist/shared-file-layer.d.ts.map +1 -1
- package/dist/shared-file-layer.js +150 -35
- package/dist/shared-file-layer.js.map +1 -1
- package/dist/shared-pipes.d.ts +3 -1
- package/dist/shared-pipes.d.ts.map +1 -1
- package/dist/shared-pipes.js +105 -33
- package/dist/shared-pipes.js.map +1 -1
- package/dist/signal-interruption.d.ts +9 -0
- package/dist/signal-interruption.d.ts.map +1 -0
- package/dist/signal-interruption.js +6 -0
- package/dist/signal-interruption.js.map +1 -0
- package/dist/signals.d.ts +17 -6
- package/dist/signals.d.ts.map +1 -1
- package/dist/signals.js +101 -22
- package/dist/signals.js.map +1 -1
- package/dist/snapshot-filesystem.d.ts +1 -0
- package/dist/snapshot-filesystem.d.ts.map +1 -1
- package/dist/snapshot-filesystem.js +3 -0
- package/dist/snapshot-filesystem.js.map +1 -1
- package/dist/socket-flags.d.ts +3 -0
- package/dist/socket-flags.d.ts.map +1 -0
- package/dist/socket-flags.js +9 -0
- package/dist/socket-flags.js.map +1 -0
- package/dist/special-files.d.ts +6 -0
- package/dist/special-files.d.ts.map +1 -0
- package/dist/special-files.js +56 -0
- package/dist/special-files.js.map +1 -0
- package/dist/stdin.d.ts.map +1 -1
- package/dist/stdin.js +13 -5
- package/dist/stdin.js.map +1 -1
- package/dist/symlink.d.ts +2 -1
- package/dist/symlink.d.ts.map +1 -1
- package/dist/symlink.js +25 -93
- package/dist/symlink.js.map +1 -1
- package/dist/system.d.ts +2 -4
- package/dist/system.d.ts.map +1 -1
- package/dist/system.js +46 -29
- package/dist/system.js.map +1 -1
- package/dist/thread-coordinator-worker.js +58 -24
- package/dist/thread-coordinator-worker.js.map +1 -1
- package/dist/thread-lifecycle.d.ts +9 -0
- package/dist/thread-lifecycle.d.ts.map +1 -0
- package/dist/thread-lifecycle.js +30 -0
- package/dist/thread-lifecycle.js.map +1 -0
- package/dist/thread-worker.js +125 -44
- package/dist/thread-worker.js.map +1 -1
- package/dist/threads.d.ts +6 -0
- package/dist/threads.d.ts.map +1 -1
- package/dist/threads.js +46 -6
- package/dist/threads.js.map +1 -1
- package/dist/tty-job-control.d.ts +13 -0
- package/dist/tty-job-control.d.ts.map +1 -0
- package/dist/tty-job-control.js +56 -0
- package/dist/tty-job-control.js.map +1 -0
- package/dist/tty-settings.d.ts +3 -0
- package/dist/tty-settings.d.ts.map +1 -0
- package/dist/tty-settings.js +25 -0
- package/dist/tty-settings.js.map +1 -0
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/wait-state.d.ts +8 -0
- package/dist/wait-state.d.ts.map +1 -1
- package/dist/wait-state.js +15 -0
- package/dist/wait-state.js.map +1 -1
- package/dist/worker-program-protocol.d.ts +22 -2
- package/dist/worker-program-protocol.d.ts.map +1 -1
- package/dist/worker-program-worker.js +31 -9
- package/dist/worker-program-worker.js.map +1 -1
- package/dist/worker-program.d.ts +12 -2
- package/dist/worker-program.d.ts.map +1 -1
- package/dist/worker-program.js +214 -36
- package/dist/worker-program.js.map +1 -1
- package/dist/write-vector.d.ts +3 -0
- package/dist/write-vector.d.ts.map +1 -0
- package/dist/write-vector.js +35 -0
- package/dist/write-vector.js.map +1 -0
- package/package.json +1 -1
package/dist/worker-program.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { KernelProcessTable, kernelProcessOwner } from "@volter/browser-runtime/kernel-processes.js";
|
|
2
|
+
import { waliJumpTransfer } from "./jumps.js";
|
|
2
3
|
import { forkKernelProcessIdentity } from "./kernel-process-client.js";
|
|
3
4
|
import { releaseWaliSharedBuffers } from "./shared-buffer-pool.js";
|
|
4
5
|
import { createSharedFileLayer, lowerTreeOf, sharedFileLayerEntries, withoutHostTrees } from "./shared-file-layer.js";
|
|
@@ -10,7 +11,7 @@ import { SharedWaliStdinWriter } from "./stdin.js";
|
|
|
10
11
|
import { findImportedMemory } from "./module-layout.js";
|
|
11
12
|
import { createWaliSignalState, defaultStops, defaultTerminates, WaliSignals } from "./signals.js";
|
|
12
13
|
import { waliSharedPipeDescribesFd } from "./shared-pipes.js";
|
|
13
|
-
import { applyProcessFilesystemPatch, closeSharedPipe, createProcessFilesystemPatch, encodeProcessPatch, publishProcessCompletion, readSharedPipe, writeSharedPipe, } from "./process-bridge.js";
|
|
14
|
+
import { applyProcessFilesystemPatch, closeSharedPipe, createProcessFilesystemPatch, encodeProcessPatch, waliExecCompletion, waliTerminalInput, publishProcessCompletion, readSharedPipe, writeSharedPipe, } from "./process-bridge.js";
|
|
14
15
|
import { createBrowserFileLockChannel, sharedBrowserFileLockCoordinator, } from "@volter/browser-runtime/browser-file-locks.js";
|
|
15
16
|
import { createBrowserFileCoherenceChannel, sharedBrowserFileCoherenceCoordinator, } from "@volter/browser-runtime/browser-file-coherence.js";
|
|
16
17
|
import { currentEntry, patchWithContent, ProcessFilesystemBase } from "@volter/browser-runtime/browser-process-filesystem.js";
|
|
@@ -18,20 +19,43 @@ import { pageWorker } from "@volter/browser-runtime/page-workers.js";
|
|
|
18
19
|
import { createWaliWaitAgent, startWaliWaitAgent, WaliWaitTable, waliWaitOwner } from "./wait-state.js";
|
|
19
20
|
import { WaliForkPool } from "./fork-pool.js";
|
|
20
21
|
import { WaliExecPool, createWaliProgramWorker } from "./exec-pool.js";
|
|
22
|
+
import { WaliKernelNetwork } from "./kernel-network.js";
|
|
23
|
+
import { WaliKernelFilesystem } from "./kernel-filesystem.js";
|
|
21
24
|
const kernelProcesses = new WeakMap();
|
|
22
25
|
const forkPools = new WeakMap();
|
|
23
26
|
const execPools = new WeakMap();
|
|
27
|
+
const kernelNetworks = new WeakMap();
|
|
28
|
+
const kernelFilesystems = new WeakMap();
|
|
24
29
|
// A pooled worker can have served a different program object. Old queued
|
|
25
30
|
// replies must not match that object's first run after listeners change.
|
|
26
31
|
let nextRunId = 1;
|
|
32
|
+
/** Host commit failures are kernel diagnostics, never bytes written by a guest. */
|
|
33
|
+
class WaliFilesystemCommitError extends Error {
|
|
34
|
+
code = "WALI_FILESYSTEM_COMMIT";
|
|
35
|
+
constructor(message, cause) { super(message, { cause }); }
|
|
36
|
+
}
|
|
37
|
+
function isFilesystemCommitError(error) {
|
|
38
|
+
const seen = new Set();
|
|
39
|
+
while (typeof error === "object" && error !== null && !seen.has(error)) {
|
|
40
|
+
seen.add(error);
|
|
41
|
+
if ("code" in error && error.code === "WALI_FILESYSTEM_COMMIT")
|
|
42
|
+
return true;
|
|
43
|
+
error = "cause" in error ? error.cause : undefined;
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
27
47
|
/** Runs one unchanged WALI executable off the UI thread while retaining its compiled module. */
|
|
28
48
|
/**
|
|
29
49
|
* Each program's module, compiled by the first worker that ran it, for every
|
|
30
50
|
* worker after: a module is compiled once per page, not once per worker. Only
|
|
31
|
-
* a pinned artifact is kept
|
|
51
|
+
* a pinned artifact is kept, including a local Blob admitted with its digest.
|
|
32
52
|
*/
|
|
33
53
|
const compiledModules = new Map();
|
|
34
54
|
const compiledKey = (url, integrity) => integrity ? `${url}\n${integrity}` : undefined;
|
|
55
|
+
/** Artifact owner retirement, after its last active image has ended. */
|
|
56
|
+
export function forgetWaliModule(url, integrity) {
|
|
57
|
+
compiledModules.delete(compiledKey(url, integrity));
|
|
58
|
+
}
|
|
35
59
|
/** `work`, or sooner when `signal` aborts: a run waiting on a preload is still one that can be cancelled (review of da64b88). */
|
|
36
60
|
async function untilAborted(work, signal) {
|
|
37
61
|
if (!signal) {
|
|
@@ -87,16 +111,23 @@ export function preloadWaliModule(url, integrity) {
|
|
|
87
111
|
export class BrowserWaliWorkerProgram {
|
|
88
112
|
filesystem;
|
|
89
113
|
program;
|
|
114
|
+
/** Host continuations outlive a retired guest image until its successor finishes. */
|
|
115
|
+
childRuns = new Set();
|
|
90
116
|
capabilities = Object.freeze({ filesystemPolicy: true });
|
|
91
117
|
/** Started by the first run, so a program the page offers and never runs costs no worker. */
|
|
92
118
|
worker;
|
|
93
119
|
pending;
|
|
94
120
|
stdin;
|
|
121
|
+
terminalInput;
|
|
95
122
|
terminalSize = { columns: 80, rows: 24 };
|
|
96
123
|
detachWorker;
|
|
97
124
|
execPool;
|
|
98
125
|
ownsExecPool = false;
|
|
99
126
|
workerLeased = false;
|
|
127
|
+
kernelNetwork;
|
|
128
|
+
releaseKernelNetwork;
|
|
129
|
+
kernelFilesystem;
|
|
130
|
+
releaseKernelFilesystem;
|
|
100
131
|
forkWorkers = new Map();
|
|
101
132
|
forkCleanup = new Map();
|
|
102
133
|
forkPool;
|
|
@@ -173,6 +204,26 @@ export class BrowserWaliWorkerProgram {
|
|
|
173
204
|
this.execPool = new WaliExecPool();
|
|
174
205
|
execPools.set(this.waitOwner.table, this.execPool);
|
|
175
206
|
}
|
|
207
|
+
this.kernelNetwork = kernelNetworks.get(kernel);
|
|
208
|
+
if (!this.kernelNetwork || this.kernelNetwork.isDisposed) {
|
|
209
|
+
this.kernelNetwork = new WaliKernelNetwork();
|
|
210
|
+
kernelNetworks.set(kernel, this.kernelNetwork);
|
|
211
|
+
}
|
|
212
|
+
this.releaseKernelNetwork = this.kernelNetwork.retain(error => {
|
|
213
|
+
this.pending?.reject(error);
|
|
214
|
+
this.finishPending();
|
|
215
|
+
this.resetWorker();
|
|
216
|
+
});
|
|
217
|
+
this.kernelFilesystem = kernelFilesystems.get(kernel);
|
|
218
|
+
if (!this.kernelFilesystem || this.kernelFilesystem.isDisposed) {
|
|
219
|
+
this.kernelFilesystem = new WaliKernelFilesystem();
|
|
220
|
+
kernelFilesystems.set(kernel, this.kernelFilesystem);
|
|
221
|
+
}
|
|
222
|
+
this.releaseKernelFilesystem = this.kernelFilesystem.retain(error => {
|
|
223
|
+
this.pending?.reject(error);
|
|
224
|
+
this.finishPending();
|
|
225
|
+
this.resetWorker();
|
|
226
|
+
});
|
|
176
227
|
// Started here so its boot overlaps the run's preparation.
|
|
177
228
|
this.worker ??= this.createWorker();
|
|
178
229
|
this.preparing = true;
|
|
@@ -210,7 +261,7 @@ export class BrowserWaliWorkerProgram {
|
|
|
210
261
|
if (layer?.vanished) {
|
|
211
262
|
const vanished = new Set(layer.vanished);
|
|
212
263
|
image = { files: image.files.filter((entry) => !vanished.has(entry.path)) };
|
|
213
|
-
|
|
264
|
+
// The cached layer is immutable and may already serve another run.
|
|
214
265
|
}
|
|
215
266
|
// The host's own layers are described by size in the image and the base,
|
|
216
267
|
// as a store's files are: the program holds them, and a file it did not
|
|
@@ -259,6 +310,7 @@ export class BrowserWaliWorkerProgram {
|
|
|
259
310
|
const size = options.terminal ?? this.terminalSize;
|
|
260
311
|
stdin.resize(size.columns, size.rows);
|
|
261
312
|
this.stdin = stdin;
|
|
313
|
+
this.terminalInput = options[waliTerminalInput] ?? (options.terminal ? stdin.buffer : undefined);
|
|
262
314
|
const signalState = createWaliSignalState();
|
|
263
315
|
return await new Promise((resolve, reject) => {
|
|
264
316
|
// What `kill` sent (the abort reason's `signal`; an abort that names
|
|
@@ -280,7 +332,15 @@ export class BrowserWaliWorkerProgram {
|
|
|
280
332
|
const survives = (signal) => {
|
|
281
333
|
if (signal === 9)
|
|
282
334
|
return false;
|
|
283
|
-
|
|
335
|
+
if (signal === 19) {
|
|
336
|
+
new WaliSignals(signalState).raise(signal);
|
|
337
|
+
kernel.stop(processPid, signal);
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
// Default-ignored signals also remain pending while blocked. Explicit
|
|
341
|
+
// SIG_IGN is still discarded by raise; unblocked defaults act now.
|
|
342
|
+
const disposition = new WaliSignals(signalState).raise(signal, 0, 0, 0, "masked");
|
|
343
|
+
this.waitOwner?.table.notifyProcess(processPid);
|
|
284
344
|
if (disposition === "default" && defaultStops(signal))
|
|
285
345
|
kernel.stop(processPid, signal);
|
|
286
346
|
return disposition !== "default" || !defaultTerminates(signal);
|
|
@@ -343,6 +403,7 @@ export class BrowserWaliWorkerProgram {
|
|
|
343
403
|
childPidAllocator: lineage?.childPidAllocator ?? kernel.allocator,
|
|
344
404
|
id,
|
|
345
405
|
...(options.descriptorTable ? { descriptorTable: options.descriptorTable } : {}),
|
|
406
|
+
...(options[waliExecCompletion] ? { execCompletion: options[waliExecCompletion] } : {}),
|
|
346
407
|
url: this.program.url,
|
|
347
408
|
...(this.program.integrity ? { integrity: this.program.integrity } : {}),
|
|
348
409
|
...(() => {
|
|
@@ -366,6 +427,8 @@ export class BrowserWaliWorkerProgram {
|
|
|
366
427
|
} } : {}),
|
|
367
428
|
...(loopbackChannel ? { loopbackPort: loopbackChannel.port2 } : {}),
|
|
368
429
|
stdin: stdin.buffer,
|
|
430
|
+
...(this.terminalInput ? { terminalStdin: this.terminalInput } : {}),
|
|
431
|
+
...(options.terminal?.write ? { terminalOutput: true } : {}),
|
|
369
432
|
terminal: options.terminal !== undefined,
|
|
370
433
|
filesystem,
|
|
371
434
|
...(store ? { store: { projectId: store } } : {}),
|
|
@@ -383,7 +446,7 @@ export class BrowserWaliWorkerProgram {
|
|
|
383
446
|
if (!active || active.id !== id)
|
|
384
447
|
return;
|
|
385
448
|
if (event.data.type === "spawn") {
|
|
386
|
-
|
|
449
|
+
this.launchChild({ ...event.data, id, type: "spawn" }, active);
|
|
387
450
|
}
|
|
388
451
|
else
|
|
389
452
|
void this.handleMessage({ ...event.data, id });
|
|
@@ -411,7 +474,9 @@ export class BrowserWaliWorkerProgram {
|
|
|
411
474
|
active.options.onDiagnostic?.(event.data.message);
|
|
412
475
|
return;
|
|
413
476
|
}
|
|
414
|
-
if (event.data.type === "
|
|
477
|
+
if (event.data.type === "terminal-output")
|
|
478
|
+
active.options.terminal?.write?.(new TextDecoder().decode(event.data.bytes));
|
|
479
|
+
else if (event.data.type === "stdout")
|
|
415
480
|
this.emitStdout(active, event.data.bytes);
|
|
416
481
|
else
|
|
417
482
|
this.emitStderr(active, new TextDecoder().decode(event.data.bytes));
|
|
@@ -598,6 +663,14 @@ export class BrowserWaliWorkerProgram {
|
|
|
598
663
|
const pending = this.pending;
|
|
599
664
|
if (!pending || message.id !== pending.id)
|
|
600
665
|
return;
|
|
666
|
+
if (message.type === "network-service") {
|
|
667
|
+
this.kernelNetwork.connect(message.reply);
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
if (message.type === "filesystem-service") {
|
|
671
|
+
this.kernelFilesystem.connect(message.reply);
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
601
674
|
if (message.type === "compiled") {
|
|
602
675
|
const key = compiledKey(this.program.url, this.program.integrity);
|
|
603
676
|
if (key)
|
|
@@ -616,6 +689,10 @@ export class BrowserWaliWorkerProgram {
|
|
|
616
689
|
this.emitStdout(pending, message.chunk);
|
|
617
690
|
return;
|
|
618
691
|
}
|
|
692
|
+
if (message.type === "terminal-output") {
|
|
693
|
+
pending.options.terminal?.write?.(new TextDecoder().decode(message.chunk));
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
619
696
|
if (message.type === "stderr") {
|
|
620
697
|
this.emitStderr(pending, message.chunk);
|
|
621
698
|
return;
|
|
@@ -625,8 +702,15 @@ export class BrowserWaliWorkerProgram {
|
|
|
625
702
|
return;
|
|
626
703
|
}
|
|
627
704
|
if (message.type === "process") {
|
|
628
|
-
const
|
|
705
|
+
const table = this.processOwner?.table;
|
|
706
|
+
const settings = message.operation === "termios"
|
|
707
|
+
? table?.terminalSettings(message.callerPid, message.first === 1, message.second, message.data ?? new Uint8Array())
|
|
708
|
+
: undefined;
|
|
709
|
+
const result = message.operation === "termios" ? settings?.result ?? -3
|
|
710
|
+
: table?.call(message.callerPid, message.operation, message.first, message.second) ?? -3;
|
|
629
711
|
const reply = new Int32Array(message.reply);
|
|
712
|
+
if (settings?.result === 0 && settings.settings)
|
|
713
|
+
new Uint8Array(message.reply, 8).set(settings.settings);
|
|
630
714
|
Atomics.store(reply, 1, result);
|
|
631
715
|
Atomics.store(reply, 0, 1);
|
|
632
716
|
Atomics.notify(reply, 0);
|
|
@@ -637,7 +721,7 @@ export class BrowserWaliWorkerProgram {
|
|
|
637
721
|
return;
|
|
638
722
|
}
|
|
639
723
|
if (message.type === "spawn") {
|
|
640
|
-
|
|
724
|
+
this.launchChild(message, pending);
|
|
641
725
|
return;
|
|
642
726
|
}
|
|
643
727
|
if (message.type === "kill") {
|
|
@@ -654,7 +738,8 @@ export class BrowserWaliWorkerProgram {
|
|
|
654
738
|
return;
|
|
655
739
|
}
|
|
656
740
|
console.log("[wali-boot]", JSON.stringify({ stage: "result-received", run: this.traceId, at: Date.now() }));
|
|
657
|
-
const
|
|
741
|
+
const delegated = pending.options.descriptorTable !== undefined;
|
|
742
|
+
const safeFilesystem = !delegated && this.program.sanitizeFilesystem
|
|
658
743
|
? await this.program.sanitizeFilesystem(message.filesystem)
|
|
659
744
|
: message.filesystem;
|
|
660
745
|
if (message.filesystemFinalized)
|
|
@@ -663,19 +748,36 @@ export class BrowserWaliWorkerProgram {
|
|
|
663
748
|
console.log("[wali-boot]", JSON.stringify({ stage: "filesystem-settled", run: this.traceId, at: Date.now() }));
|
|
664
749
|
if (this.pending !== pending)
|
|
665
750
|
return;
|
|
751
|
+
if (message.result?.replaced) {
|
|
752
|
+
// Return the old worker now, while its host continuation awaits the
|
|
753
|
+
// successor. A backend caller may terminate this program in finally;
|
|
754
|
+
// do not resolve that caller until the exec chain has closed its pipes.
|
|
755
|
+
this.resetWorker(true, true);
|
|
756
|
+
await Promise.all([...this.childRuns]);
|
|
757
|
+
if (this.pending !== pending)
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
666
760
|
// A threaded process commits its final delta on the ordered update port.
|
|
667
761
|
// Its full snapshot may predate a peer import acknowledged on that port;
|
|
668
762
|
// diffing it against the advanced base would manufacture a deletion.
|
|
669
|
-
|
|
763
|
+
// Exec/fork images that inherit the owner cannot replay their private
|
|
764
|
+
// artifact snapshot over writes the owner has already published.
|
|
765
|
+
const patch = message.filesystemFinalized || delegated ? { files: [], removed: [] }
|
|
670
766
|
: this.withoutHostTrees(createProcessFilesystemPatch(pending.filesystemBase, safeFilesystem, { written: true }));
|
|
671
|
-
if (!message.filesystemFinalized)
|
|
672
|
-
|
|
767
|
+
if (!message.filesystemFinalized && !delegated) {
|
|
768
|
+
try {
|
|
769
|
+
applyProcessFilesystemPatch(this.filesystem, patch, pending.filesystemBase);
|
|
770
|
+
}
|
|
771
|
+
catch (error) {
|
|
772
|
+
throw new WaliFilesystemCommitError("WALI exit filesystem commit failed", error);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
673
775
|
try {
|
|
674
776
|
await this.program.onFilesystemCommit?.(this.filesystem, patch);
|
|
675
777
|
}
|
|
676
778
|
catch (error) {
|
|
677
779
|
this.finishPending();
|
|
678
|
-
pending.reject(new
|
|
780
|
+
pending.reject(new WaliFilesystemCommitError(`WALI filesystem durability barrier failed: ${error instanceof Error ? error.message : String(error)}`, error));
|
|
679
781
|
return;
|
|
680
782
|
}
|
|
681
783
|
this.finishPending();
|
|
@@ -683,7 +785,7 @@ export class BrowserWaliWorkerProgram {
|
|
|
683
785
|
// this image's listeners and children before a different image borrows
|
|
684
786
|
// the worker; a caller's finally/terminate then has no lease to cancel.
|
|
685
787
|
if (!this.ownsExecPool)
|
|
686
|
-
this.resetWorker(message.result !== undefined);
|
|
788
|
+
this.resetWorker(message.result !== undefined, message.result?.replaced === true);
|
|
687
789
|
console.log("[wali-boot]", JSON.stringify({ stage: "done", run: this.traceId, at: Date.now() }));
|
|
688
790
|
if (message.result)
|
|
689
791
|
pending.resolve({ ...message.result, stdout: pending.stdout, stderr: pending.stderr });
|
|
@@ -711,7 +813,7 @@ export class BrowserWaliWorkerProgram {
|
|
|
711
813
|
}) : Promise.resolve();
|
|
712
814
|
let ended = false;
|
|
713
815
|
let processImage = 0;
|
|
714
|
-
const finish = async (signal) => {
|
|
816
|
+
const finish = async (signal, replaced = false) => {
|
|
715
817
|
if (ended)
|
|
716
818
|
return;
|
|
717
819
|
ended = true;
|
|
@@ -734,15 +836,20 @@ export class BrowserWaliWorkerProgram {
|
|
|
734
836
|
publishProcessCompletion({ ...message, processIdentity: identity });
|
|
735
837
|
releaseWaliSharedBuffers(message.bufferLease);
|
|
736
838
|
}
|
|
737
|
-
else
|
|
839
|
+
else {
|
|
738
840
|
pool.release(worker);
|
|
841
|
+
if (!replaced) {
|
|
842
|
+
publishProcessCompletion({ ...message, processIdentity: identity });
|
|
843
|
+
releaseWaliSharedBuffers(message.bufferLease);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
739
846
|
table?.removeImage(agent.image);
|
|
740
847
|
};
|
|
741
848
|
const receive = (event) => {
|
|
742
849
|
if (this.forkWorkers.get(message.pid) !== worker)
|
|
743
850
|
return;
|
|
744
851
|
if (event.data.type === "fork-done") {
|
|
745
|
-
void finish();
|
|
852
|
+
void finish(undefined, event.data.replaced);
|
|
746
853
|
return;
|
|
747
854
|
}
|
|
748
855
|
if (event.data.type === "stderr" && event.data.chunk.includes("could not allocate memory")) {
|
|
@@ -765,7 +872,14 @@ export class BrowserWaliWorkerProgram {
|
|
|
765
872
|
processImage = owner.table.image(message.pid, signal => {
|
|
766
873
|
if (ended)
|
|
767
874
|
return;
|
|
768
|
-
|
|
875
|
+
if (signal === 19) {
|
|
876
|
+
new WaliSignals(message.forkState.signalState).raise(signal);
|
|
877
|
+
owner.table.stop(message.pid, signal);
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
const disposition = signal === 9 ? "default"
|
|
881
|
+
: new WaliSignals(message.forkState.signalState).raise(signal, 0, 0, 0, "masked");
|
|
882
|
+
table?.notifyProcess(message.pid);
|
|
769
883
|
if (disposition === "default" && defaultStops(signal))
|
|
770
884
|
owner.table.stop(message.pid, signal);
|
|
771
885
|
if (disposition === "default" && defaultTerminates(signal))
|
|
@@ -775,7 +889,23 @@ export class BrowserWaliWorkerProgram {
|
|
|
775
889
|
worker.addEventListener("error", failed);
|
|
776
890
|
const descriptorTable = { ...message.descriptorTable };
|
|
777
891
|
delete descriptorTable.releasePort;
|
|
778
|
-
|
|
892
|
+
// The child's reactor channel travels with the fork: a request for it
|
|
893
|
+
// would be one more round trip through this thread before the child runs.
|
|
894
|
+
const networkServicePort = this.kernelNetwork?.open();
|
|
895
|
+
worker.postMessage({ ...message, processIdentity: identity, descriptorTable, waitAgent: agent,
|
|
896
|
+
...(networkServicePort ? { networkServicePort } : {}) }, [descriptorTable.port, message.forkState.memory, ...waliJumpTransfer(message.forkState.jumps),
|
|
897
|
+
...(networkServicePort ? [networkServicePort] : [])]);
|
|
898
|
+
}
|
|
899
|
+
launchChild(message, pending) {
|
|
900
|
+
const work = this.spawnChild(message, pending);
|
|
901
|
+
this.childRuns.add(work);
|
|
902
|
+
void work.finally(() => this.childRuns.delete(work)).catch(error => {
|
|
903
|
+
if (this.pending !== pending)
|
|
904
|
+
return;
|
|
905
|
+
pending.reject(error);
|
|
906
|
+
this.finishPending();
|
|
907
|
+
this.resetWorker();
|
|
908
|
+
});
|
|
779
909
|
}
|
|
780
910
|
async spawnChild(message, pending) {
|
|
781
911
|
const table = this.waitOwner?.table;
|
|
@@ -803,6 +933,7 @@ export class BrowserWaliWorkerProgram {
|
|
|
803
933
|
const descriptorTable = message.descriptorTable && {
|
|
804
934
|
port: message.descriptorTable.port, control: message.descriptorTable.control,
|
|
805
935
|
payload: message.descriptorTable.payload,
|
|
936
|
+
...(message.descriptorTable.descriptorLimits ? { descriptorLimits: message.descriptorTable.descriptorLimits } : {}),
|
|
806
937
|
...(message.descriptorTable.sharedPipes ? { sharedPipes: message.descriptorTable.sharedPipes } : {}),
|
|
807
938
|
};
|
|
808
939
|
let released;
|
|
@@ -814,6 +945,7 @@ export class BrowserWaliWorkerProgram {
|
|
|
814
945
|
let stderrWrites = Promise.resolve();
|
|
815
946
|
let stdoutStreamed = false;
|
|
816
947
|
let stderrStreamed = false;
|
|
948
|
+
let replaced = false;
|
|
817
949
|
// The child's patch is a diff of the tree before and after it ran; the
|
|
818
950
|
// diff needs sizes and times, and only the entries it names need bytes.
|
|
819
951
|
const metadataSnapshot = () => readBrowserFileSystemSnapshot(this.filesystem, "/", { metadataRoots: ["/"] });
|
|
@@ -874,18 +1006,33 @@ export class BrowserWaliWorkerProgram {
|
|
|
874
1006
|
const safePatch = this.withoutHostTrees(this.program.sanitizeFilesystemPatch
|
|
875
1007
|
? await this.program.sanitizeFilesystemPatch(message.filesystemPatch)
|
|
876
1008
|
: message.filesystemPatch);
|
|
877
|
-
|
|
878
|
-
|
|
1009
|
+
// A descriptor handoff already flushed the owner before exec/spawn.
|
|
1010
|
+
// The old image's private snapshot cannot compete with that owner.
|
|
1011
|
+
if (!message.descriptorTable) {
|
|
1012
|
+
try {
|
|
1013
|
+
applyProcessFilesystemPatch(this.filesystem, safePatch, pending.filesystemBase);
|
|
1014
|
+
}
|
|
1015
|
+
catch (error) {
|
|
1016
|
+
throw new WaliFilesystemCommitError("WALI spawn filesystem commit failed", error);
|
|
1017
|
+
}
|
|
1018
|
+
pending.filesystemBase.apply(safePatch);
|
|
1019
|
+
}
|
|
879
1020
|
childFilesystem = await metadataSnapshot();
|
|
880
1021
|
trace("spawn-patched");
|
|
881
1022
|
const stdinStream = message.stdin ? streamBrowserPipe(message.stdin, controller.signal) : undefined;
|
|
882
1023
|
const spawned = browserCommand(message);
|
|
883
|
-
const
|
|
1024
|
+
const runOptions = {
|
|
1025
|
+
...(this.terminalInput ? { [waliTerminalInput]: this.terminalInput } : {}),
|
|
884
1026
|
[kernelProcessOwner]: { table: owner.table, identity },
|
|
885
1027
|
...(table ? { [waliWaitOwner]: { table, agent } } : {}),
|
|
886
1028
|
[browserProcessLineage]: { pid: message.pid, parentPid: message.parentPid, childPidAllocator: message.childPidAllocator },
|
|
887
1029
|
...(filesystemPolicy ? { filesystemPolicy } : {}),
|
|
888
1030
|
...(descriptorTable ? { descriptorTable } : {}),
|
|
1031
|
+
...(message.replaceImage ? { [waliExecCompletion]: {
|
|
1032
|
+
control: message.control, response: message.response,
|
|
1033
|
+
...(message.bufferLease ? { bufferLease: message.bufferLease } : {}),
|
|
1034
|
+
processIdentity: identity,
|
|
1035
|
+
} } : {}),
|
|
889
1036
|
cwd: message.cwd,
|
|
890
1037
|
...(pending.options.terminal ? { terminal: pending.options.terminal } : {}),
|
|
891
1038
|
...(spawned.executablePath ? { executablePath: spawned.executablePath } : {}),
|
|
@@ -895,7 +1042,9 @@ export class BrowserWaliWorkerProgram {
|
|
|
895
1042
|
...(message.stdout ? { onStdoutBytes: stdoutBytes } : {}),
|
|
896
1043
|
onStdout: stdout,
|
|
897
1044
|
onStderr: stderr,
|
|
898
|
-
}
|
|
1045
|
+
};
|
|
1046
|
+
const result = await backend.run(spawned.command, runOptions);
|
|
1047
|
+
replaced = result.replaced === true;
|
|
899
1048
|
// Tabnode streams long-running commands, but may only return buffered
|
|
900
1049
|
// output for short commands. Deliver exactly one form to the child pipe.
|
|
901
1050
|
if (result.stdout && !stdoutStreamed)
|
|
@@ -905,7 +1054,10 @@ export class BrowserWaliWorkerProgram {
|
|
|
905
1054
|
trace("spawn-ran");
|
|
906
1055
|
await Promise.all([stdoutWrites, stderrWrites]);
|
|
907
1056
|
await releaseDescriptors();
|
|
908
|
-
|
|
1057
|
+
// exec transferred this same completion record. The successor is its
|
|
1058
|
+
// only producer; publishing here would reap a still-running process.
|
|
1059
|
+
if (!replaced)
|
|
1060
|
+
encodeProcessPatch(message, await patchAgainst(childFilesystem), result.exitCode);
|
|
909
1061
|
trace("spawn-returned");
|
|
910
1062
|
}
|
|
911
1063
|
catch (error) {
|
|
@@ -914,7 +1066,12 @@ export class BrowserWaliWorkerProgram {
|
|
|
914
1066
|
&& controller.signal.reason?.handled !== true;
|
|
915
1067
|
const signal = typeof delivered === "number" && Number.isInteger(delivered) && delivered > 0 && delivered <= 64
|
|
916
1068
|
? delivered : cancellation ? abortSignalNumber(controller.signal.reason) : undefined;
|
|
917
|
-
|
|
1069
|
+
const kernelFailure = isFilesystemCommitError(error);
|
|
1070
|
+
if (signal === undefined && kernelFailure) {
|
|
1071
|
+
pending.options.onDiagnostic?.(`[WALI filesystem] ${error instanceof Error ? error.message : String(error)}`);
|
|
1072
|
+
await Promise.allSettled([stdoutWrites, stderrWrites]);
|
|
1073
|
+
}
|
|
1074
|
+
else if (signal === undefined) {
|
|
918
1075
|
const text = `${error instanceof Error ? error.message : String(error)}\n`;
|
|
919
1076
|
stderr(text);
|
|
920
1077
|
await stderrWrites;
|
|
@@ -925,7 +1082,7 @@ export class BrowserWaliWorkerProgram {
|
|
|
925
1082
|
await Promise.allSettled([stdoutWrites, stderrWrites]);
|
|
926
1083
|
}
|
|
927
1084
|
await releaseDescriptors();
|
|
928
|
-
encodeProcessPatch(message, await patchAgainst(childFilesystem), signal === undefined ? 127 : 128 + signal, signal);
|
|
1085
|
+
encodeProcessPatch(message, kernelFailure ? { files: [], removed: [] } : await patchAgainst(childFilesystem), signal === undefined ? 127 : 128 + signal, signal);
|
|
929
1086
|
}
|
|
930
1087
|
finally {
|
|
931
1088
|
table?.removeImage(agent.image);
|
|
@@ -935,7 +1092,8 @@ export class BrowserWaliWorkerProgram {
|
|
|
935
1092
|
closeExternalPipe(message.stdin);
|
|
936
1093
|
closeExternalPipe(message.stdout);
|
|
937
1094
|
closeExternalPipe(message.stderr);
|
|
938
|
-
|
|
1095
|
+
if (!replaced)
|
|
1096
|
+
releaseWaliSharedBuffers(message.bufferLease);
|
|
939
1097
|
}
|
|
940
1098
|
}
|
|
941
1099
|
async applyLiveFilesystemPatch(id, message) {
|
|
@@ -963,6 +1121,10 @@ export class BrowserWaliWorkerProgram {
|
|
|
963
1121
|
this.scheduleFilesystemImport(pending);
|
|
964
1122
|
return;
|
|
965
1123
|
}
|
|
1124
|
+
// An inherited owner's updates arrive on that owner's port, not this
|
|
1125
|
+
// image's bootstrap port. Imported/checkpoint acknowledgements still run.
|
|
1126
|
+
if (pending.options.descriptorTable && message.type === "filesystem")
|
|
1127
|
+
return;
|
|
966
1128
|
try {
|
|
967
1129
|
if (message.type === "import-error")
|
|
968
1130
|
throw new Error(message.message);
|
|
@@ -1007,7 +1169,7 @@ export class BrowserWaliWorkerProgram {
|
|
|
1007
1169
|
return;
|
|
1008
1170
|
this.finishPending();
|
|
1009
1171
|
this.resetWorker();
|
|
1010
|
-
pending.reject(new
|
|
1172
|
+
pending.reject(new WaliFilesystemCommitError(`WALI filesystem update could not be applied: ${error instanceof Error ? error.message : String(error)}`, error));
|
|
1011
1173
|
return;
|
|
1012
1174
|
}
|
|
1013
1175
|
try {
|
|
@@ -1120,6 +1282,10 @@ export class BrowserWaliWorkerProgram {
|
|
|
1120
1282
|
if (this.waitOwner)
|
|
1121
1283
|
execPools.delete(this.waitOwner.table);
|
|
1122
1284
|
}
|
|
1285
|
+
this.releaseKernelNetwork?.();
|
|
1286
|
+
this.releaseKernelNetwork = undefined;
|
|
1287
|
+
this.releaseKernelFilesystem?.();
|
|
1288
|
+
this.releaseKernelFilesystem = undefined;
|
|
1123
1289
|
this.pending?.resolveFilesystemFinalization();
|
|
1124
1290
|
this.pending?.removeAbortListener();
|
|
1125
1291
|
this.pending?.releaseLoopback();
|
|
@@ -1130,6 +1296,7 @@ export class BrowserWaliWorkerProgram {
|
|
|
1130
1296
|
this.waitOwner = undefined;
|
|
1131
1297
|
this.stdin?.close();
|
|
1132
1298
|
this.stdin = undefined;
|
|
1299
|
+
this.terminalInput = undefined;
|
|
1133
1300
|
for (const port of this.processPorts)
|
|
1134
1301
|
port.close();
|
|
1135
1302
|
this.processPorts = [];
|
|
@@ -1166,14 +1333,19 @@ export class BrowserWaliWorkerProgram {
|
|
|
1166
1333
|
}
|
|
1167
1334
|
this.urlScanTail = text.slice(-2048);
|
|
1168
1335
|
}
|
|
1169
|
-
resetWorker(reusable = false) {
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1336
|
+
resetWorker(reusable = false, preserveChildren = false) {
|
|
1337
|
+
// Image replacement retires a worker, not its process's children or the
|
|
1338
|
+
// successor now running through spawnChild. Their owner callbacks remain
|
|
1339
|
+
// alive until their normal completion; cancellation still tears them down.
|
|
1340
|
+
if (!preserveChildren) {
|
|
1341
|
+
for (const finish of this.forkCleanup.values())
|
|
1342
|
+
finish();
|
|
1343
|
+
this.forkCleanup.clear();
|
|
1344
|
+
this.forkWorkers.clear();
|
|
1345
|
+
for (const controller of this.childControllers.values())
|
|
1346
|
+
controller.abort();
|
|
1347
|
+
this.childControllers.clear();
|
|
1348
|
+
}
|
|
1177
1349
|
// A retained program object must not retain its retired worker realm.
|
|
1178
1350
|
// The next run starts lazily; late events are rejected by createWorker.
|
|
1179
1351
|
const worker = this.worker;
|
|
@@ -1211,6 +1383,12 @@ function abortSignalNumber(reason) {
|
|
|
1211
1383
|
* shell, not what the line runs, so it does not go with it.
|
|
1212
1384
|
*/
|
|
1213
1385
|
function browserCommand(message) {
|
|
1386
|
+
// execve already resolved an image. Its argv is data, including argv[0];
|
|
1387
|
+
// it must not be renamed or interpreted as another shell command.
|
|
1388
|
+
if (message.replaceImage)
|
|
1389
|
+
return {
|
|
1390
|
+
command: message.argv.map(shellQuote).join(" "), executablePath: message.executablePath,
|
|
1391
|
+
};
|
|
1214
1392
|
const [program = "", ...args] = message.argv;
|
|
1215
1393
|
const executable = message.executablePath || program;
|
|
1216
1394
|
const basename = executable.slice(executable.lastIndexOf("/") + 1);
|