@volter/tabnode 0.4.1 → 0.5.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/__sw__.js +259 -5
- package/dist/assets/{runtime-worker-LWfW6VWE.js → runtime-worker-B9buriZs.js} +4004 -2955
- package/dist/fetch-transport.d.ts +20 -0
- package/dist/guest-timers.d.ts +15 -0
- package/dist/http-client-transport.d.ts +19 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.mjs +5187 -3051
- package/dist/native-stream-binding.d.ts +57 -0
- package/dist/native-stream-owner.d.ts +121 -0
- package/dist/node-lib/binding/duplex_stream.d.ts +26 -0
- package/dist/node-lib/binding/fds.d.ts +8 -0
- package/dist/node-lib/binding/fs.d.ts +311 -78
- package/dist/node-lib/binding/misc.d.ts +1 -0
- package/dist/node-lib/binding/pipe_wrap.d.ts +4 -4
- package/dist/node-lib/binding/process_wrap.d.ts +12 -1
- package/dist/node-lib/binding/stream_wrap.d.ts +27 -18
- package/dist/node-lib/binding/tcp_wrap.d.ts +0 -1
- package/dist/node-lib/binding/tty_wrap.d.ts +43 -2
- package/dist/node-lib/binding/util.d.ts +83 -0
- package/dist/node-lib/binding/uv.d.ts +4 -0
- package/dist/node-lib/fs-module.d.ts +1 -0
- package/dist/node-lib/internals/buffer-and-streams.d.ts +30 -17
- package/dist/node-lib/internals/index.d.ts +1 -1
- package/dist/node-lib/load.d.ts +17 -3
- package/dist/node-lib/module-hooks.d.ts +1 -0
- package/dist/node-lib/public-modules.d.ts +1 -1
- package/dist/node-lib/timers.d.ts +38 -0
- package/dist/node-process-host.d.ts +41 -0
- package/dist/process-registry.d.ts +39 -0
- package/dist/process-tokens.d.ts +11 -0
- package/dist/promise-ownership.d.ts +3 -0
- package/dist/runtime.d.ts +10 -9
- package/dist/shims/child_process.d.ts +2 -0
- package/dist/shims/crypto.d.ts +660 -119
- package/dist/shims/dns.d.ts +76 -2
- package/dist/shims/process.d.ts +1 -0
- package/dist/shims/string_decoder.d.ts +19 -3
- package/dist/shims/tls.d.ts +853 -31
- package/dist/source-edits.d.ts +7 -0
- package/package.json +1 -1
- package/src/code-transforms.ts +2 -3
- package/src/fetch-transport.ts +98 -0
- package/src/guest-timers.ts +89 -0
- package/src/http-client-transport.ts +169 -0
- package/src/index.ts +12 -0
- package/src/native-stream-binding.ts +393 -0
- package/src/native-stream-owner.ts +317 -0
- package/src/node-lib/binding/duplex_stream.ts +95 -0
- package/src/node-lib/binding/fds.ts +18 -1
- package/src/node-lib/binding/fs.ts +45 -17
- package/src/node-lib/binding/misc.ts +2 -0
- package/src/node-lib/binding/pipe_wrap.ts +35 -12
- package/src/node-lib/binding/process_wrap.ts +50 -9
- package/src/node-lib/binding/stream_wrap.ts +196 -72
- package/src/node-lib/binding/tcp_wrap.ts +43 -8
- package/src/node-lib/binding/tty_wrap.ts +135 -8
- package/src/node-lib/binding/util.ts +24 -15
- package/src/node-lib/binding/uv.ts +4 -0
- package/src/node-lib/fs-module.ts +2 -18
- package/src/node-lib/internal/navigator.js +149 -0
- package/src/node-lib/internals/buffer-and-streams.ts +50 -11
- package/src/node-lib/internals/events-util.ts +5 -0
- package/src/node-lib/internals/index.ts +12 -2
- package/src/node-lib/load.ts +170 -62
- package/src/node-lib/module-hooks.ts +5 -2
- package/src/node-lib/public-modules.ts +12 -48
- package/src/node-lib/sources.ts +4 -0
- package/src/node-lib/timers.ts +93 -0
- package/src/node-process-host.ts +124 -0
- package/src/process-registry.ts +144 -0
- package/src/process-tokens.ts +55 -6
- package/src/promise-ownership.ts +28 -0
- package/src/runtime.ts +90 -214
- package/src/shims/child_process.ts +310 -80
- package/src/shims/crypto.ts +115 -100
- package/src/shims/dns.ts +17 -1
- package/src/shims/process.ts +33 -11
- package/src/shims/string_decoder.ts +10 -6
- package/src/shims/tls.ts +49 -19
- package/src/source-edits.ts +20 -0
|
@@ -33,6 +33,7 @@ if (typeof globalThis.process === 'undefined') {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
import { forGuestRealm, heldWork } from '../host-globals';
|
|
36
|
+
import { promiseOwner } from '../promise-ownership';
|
|
36
37
|
import { Bash, defineCommand } from 'just-bash';
|
|
37
38
|
import type { CommandContext, ExecResult as JustBashExecResult } from 'just-bash';
|
|
38
39
|
import { EventEmitter } from '../node-lib/events-module';
|
|
@@ -43,12 +44,18 @@ import { VirtualFSAdapter } from './vfs-adapter';
|
|
|
43
44
|
import { __releaseOwnedServers, __ownedServerPorts } from '../node-lib/net-module';
|
|
44
45
|
import { __ownedHandleCount, __releaseOwnedHandles } from '../node-lib/net-module';
|
|
45
46
|
import { setProcessRunner, type RunRequest, type StartedRun } from '../node-lib/binding/process_wrap';
|
|
47
|
+
import { registerRunFd, releaseRunFds, inheritedRunFds } from '../node-lib/binding/fds';
|
|
48
|
+
import { nodeProcessHostFor, nodeProcessHostInstalled, nodeProcessRealmToken, nodeProcessInput, type NodeProcessHost, type NodeProcessLaunch } from '../node-process-host';
|
|
49
|
+
import { nativeStreamDescriptor } from '../native-stream-binding';
|
|
46
50
|
import { Pipe, constants as pipeConstants } from '../node-lib/binding/pipe_wrap';
|
|
47
|
-
import {
|
|
51
|
+
import {
|
|
52
|
+
WriteWrap, ShutdownWrap, streamBaseState, kReadBytesOrError, kArrayBufferOffset,
|
|
53
|
+
type LibuvStreamWrap,
|
|
54
|
+
} from '../node-lib/binding/stream_wrap';
|
|
48
55
|
import { UV_ESRCH } from '../node-lib/binding/uv';
|
|
49
|
-
import {
|
|
56
|
+
import { loadNodeLibFor } from '../node-lib/load';
|
|
57
|
+
import type { ChildProcessModule } from '../node-lib/child-process-module';
|
|
50
58
|
import { getCommandNames } from 'just-bash';
|
|
51
|
-
import { diagnosticsChannelModule } from '../node-lib/small-modules';
|
|
52
59
|
import { __substrateExecPath, __substrateProgramName, __substrateLineFor, __substrateShellLine, setProgramResolver } from './command-line';
|
|
53
60
|
|
|
54
61
|
import { __substrateChildren, __onUncaughtException, __reportUncaughtException } from './process';
|
|
@@ -65,6 +72,34 @@ import { resolve as __resolvePath } from './path';
|
|
|
65
72
|
import { setSyncChildVfs, warmSyncChild } from './sync-child';
|
|
66
73
|
import type { PackageJson } from '../types/package-json';
|
|
67
74
|
|
|
75
|
+
// Capture host capabilities before the guest's authority is restricted. These
|
|
76
|
+
// endpoints are private, carry no guest bytes, and close at each checkpoint.
|
|
77
|
+
const HostBroadcastChannel = globalThis.BroadcastChannel;
|
|
78
|
+
const hostChannelName = globalThis.crypto?.randomUUID.bind(globalThis.crypto);
|
|
79
|
+
const isWorkerRealm = typeof (globalThis as Record<string, unknown>).WorkerGlobalScope !== 'undefined';
|
|
80
|
+
|
|
81
|
+
async function settlePrintedEntry(): Promise<void> {
|
|
82
|
+
await new Promise<void>(resolve => (globalThis.__browserRuntimeNativeSetTimeout ?? setTimeout).call(globalThis, resolve, 0));
|
|
83
|
+
if (!isWorkerRealm || nodeProcessRealmToken() === null) return;
|
|
84
|
+
if (!HostBroadcastChannel || !hostChannelName) throw new Error('Native process rejection checkpoint is unavailable.');
|
|
85
|
+
// HTML queues rejection reporting after the microtask checkpoint on the
|
|
86
|
+
// DOM manipulation task source. A timer alone can overtake that report.
|
|
87
|
+
// BroadcastChannel uses the same source, ordering this acknowledgment
|
|
88
|
+
// behind reports already queued before the timer turn above.
|
|
89
|
+
const name = hostChannelName();
|
|
90
|
+
const receiver = new HostBroadcastChannel(name);
|
|
91
|
+
try {
|
|
92
|
+
const sender = new HostBroadcastChannel(name);
|
|
93
|
+
try {
|
|
94
|
+
await new Promise<void>((resolve, reject) => {
|
|
95
|
+
receiver.onmessage = () => resolve();
|
|
96
|
+
receiver.onmessageerror = () => reject(new Error('Native process rejection checkpoint failed.'));
|
|
97
|
+
sender.postMessage(null);
|
|
98
|
+
});
|
|
99
|
+
} finally { sender.close(); }
|
|
100
|
+
} finally { receiver.close(); }
|
|
101
|
+
}
|
|
102
|
+
|
|
68
103
|
// Singleton bash instance - uses VFS adapter for two-way file sync
|
|
69
104
|
let bashInstance: Bash | null = null;
|
|
70
105
|
let vfsAdapter: VirtualFSAdapter | null = null;
|
|
@@ -114,7 +149,12 @@ Object.defineProperty = function(target: object, key: PropertyKey, descriptor: P
|
|
|
114
149
|
} as typeof Object.defineProperty;
|
|
115
150
|
|
|
116
151
|
/** The stdin of a held run's guest, as the run's own. */
|
|
117
|
-
type RunStdin = {
|
|
152
|
+
type RunStdin = {
|
|
153
|
+
emit: (event: string, ...args: unknown[]) => void;
|
|
154
|
+
push: (chunk: string | Uint8Array | null) => boolean;
|
|
155
|
+
/** A hosted sink acknowledges consumption before another pipe read. */
|
|
156
|
+
writeAsync?: (chunk: string | Uint8Array | null) => Promise<void>;
|
|
157
|
+
};
|
|
118
158
|
|
|
119
159
|
/**
|
|
120
160
|
* What a host gave one run: where its output goes, the handle that aborts it,
|
|
@@ -156,6 +196,29 @@ export interface RunStreams {
|
|
|
156
196
|
*/
|
|
157
197
|
const _runStreams = new Map<ProcessToken, RunStreams>();
|
|
158
198
|
|
|
199
|
+
/** Submit before returning to a spawning parent; completion remains async. */
|
|
200
|
+
async function runHostedNode(host: NodeProcessHost, launch: Omit<NodeProcessLaunch, 'identity' | 'inherited' | 'stdinStream'>): Promise<CommandOutcome> {
|
|
201
|
+
const { token, streams } = launch;
|
|
202
|
+
if (!runPid(token)) setRunPid(token, mintPid(), 0);
|
|
203
|
+
let input: ReturnType<typeof nodeProcessInput> | undefined;
|
|
204
|
+
try {
|
|
205
|
+
streams?.signal?.throwIfAborted();
|
|
206
|
+
const inherited = inheritedRunFds(token).map(({ fd, handle }) => {
|
|
207
|
+
const descriptor = nativeStreamDescriptor(handle as LibuvStreamWrap);
|
|
208
|
+
if (!descriptor) throw new Error(`Node descriptor ${fd} has no native owner for process isolation.`);
|
|
209
|
+
return { fd, handle: descriptor };
|
|
210
|
+
});
|
|
211
|
+
input = nodeProcessInput(streams);
|
|
212
|
+
return await host.run({ ...launch, identity: { ...runPid(token)! }, inherited,
|
|
213
|
+
...(input.stream ? { stdinStream: input.stream } : {}) });
|
|
214
|
+
} finally {
|
|
215
|
+
if (streams) streams.stdin = null;
|
|
216
|
+
input?.dispose();
|
|
217
|
+
// Adoption moves owner registration; only the source-local lookup remains.
|
|
218
|
+
forgetRunPid(token);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
159
222
|
/**
|
|
160
223
|
* One line on the host realm's console when a run the tab owns dies and
|
|
161
224
|
* nobody is reading its stderr.
|
|
@@ -274,7 +337,7 @@ forGuestRealm(() => {
|
|
|
274
337
|
if (typeof globalThis.addEventListener !== 'function') return;
|
|
275
338
|
__backstopInstalled = true;
|
|
276
339
|
globalThis.addEventListener('error', (event: Event) => {
|
|
277
|
-
const token = __currentProcessToken() ?? __lastLaunchedToken;
|
|
340
|
+
const token = nodeProcessRealmToken() ?? __currentProcessToken() ?? __lastLaunchedToken;
|
|
278
341
|
if (token === null) return;
|
|
279
342
|
const raised = event as ErrorEvent;
|
|
280
343
|
const error = raised.error ?? new Error(raised.message || 'uncaught exception');
|
|
@@ -333,16 +396,25 @@ export function initChildProcess(vfs: VirtualFS): void {
|
|
|
333
396
|
* of the server's became `TypeError: Cannot read properties of undefined
|
|
334
397
|
* (reading 'catch')` inside a timer, 59 of them in one boot.
|
|
335
398
|
*/
|
|
336
|
-
const __listenForUnhandledRejections = (report: (reason: unknown, promise?: Promise<unknown>) => void): (() => void) => {
|
|
399
|
+
const __listenForUnhandledRejections = (owner: object, report: (reason: unknown, promise?: Promise<unknown>) => void, ownsRealm: boolean): (() => void) => {
|
|
337
400
|
if (typeof globalThis.addEventListener === 'function') {
|
|
338
401
|
const target = globalThis as unknown as EventTarget;
|
|
339
|
-
const listener = (event: Event) => {
|
|
402
|
+
const listener = (event: Event) => {
|
|
403
|
+
const rejection = event as PromiseRejectionEvent;
|
|
404
|
+
// An admitted process worker has exactly one Node owner. Shared
|
|
405
|
+
// embeddings still require provenance and never broadcast failures.
|
|
406
|
+
if (!ownsRealm && promiseOwner(rejection.promise) !== owner) return;
|
|
407
|
+
event.preventDefault();
|
|
408
|
+
report(rejection.reason, rejection.promise);
|
|
409
|
+
};
|
|
340
410
|
target.addEventListener('unhandledrejection', listener);
|
|
341
411
|
return () => target.removeEventListener('unhandledrejection', listener);
|
|
342
412
|
}
|
|
343
413
|
const host = __hostProcess;
|
|
344
414
|
if (host) {
|
|
345
|
-
const listener = (reason: unknown, promise?: Promise<unknown>) =>
|
|
415
|
+
const listener = (reason: unknown, promise?: Promise<unknown>) => {
|
|
416
|
+
if (ownsRealm || promiseOwner(promise) === owner) report(reason, promise);
|
|
417
|
+
};
|
|
346
418
|
host.on('unhandledRejection', listener);
|
|
347
419
|
return () => host.off('unhandledRejection', listener);
|
|
348
420
|
}
|
|
@@ -353,6 +425,18 @@ export function initChildProcess(vfs: VirtualFS): void {
|
|
|
353
425
|
return { stdout: '', stderr: 'VFS not initialized\n', exitCode: 1 };
|
|
354
426
|
}
|
|
355
427
|
|
|
428
|
+
// Capture this run's name, streams and cancellation before another Node
|
|
429
|
+
// entry can start. Forks and shell entries reach this same dispatch seam.
|
|
430
|
+
const runToken = runTokenOf(ctx);
|
|
431
|
+
const streams = runToken === null ? undefined : runStreamsFor(runToken);
|
|
432
|
+
const processHost = nodeProcessHostFor(runToken);
|
|
433
|
+
if (processHost && runToken !== null) {
|
|
434
|
+
return runHostedNode(processHost, {
|
|
435
|
+
token: runToken, argv: [...args], cwd: ctx.cwd, filesystem: tree, env: guestEnvironmentOf(ctx),
|
|
436
|
+
...(typeof ctx.stdin === 'string' ? { stdin: ctx.stdin } : {}), ...(streams ? { streams } : {}),
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
|
|
356
440
|
// Node reads its own options before the script: `node --turbo-fast-api-calls
|
|
357
441
|
// file.js a b` runs file.js with `a b`, and the options it were given are
|
|
358
442
|
// its `execArgv`. Node's own suite spawns children that way, and the
|
|
@@ -378,8 +462,28 @@ export function initChildProcess(vfs: VirtualFS): void {
|
|
|
378
462
|
// its first fork.
|
|
379
463
|
const execArgv: string[] = [];
|
|
380
464
|
let first = 0;
|
|
465
|
+
// `-e`/`--eval` and `-p`/`--print` carry the program itself as their
|
|
466
|
+
// value: there is no script, and every word after the source is the
|
|
467
|
+
// program's argument. Taken for an option, the source was read as the
|
|
468
|
+
// script's path ("Cannot find module '/workspace/console.log(1+1)'").
|
|
469
|
+
let evaluated: { source: string; print: boolean } | null = null;
|
|
381
470
|
while (first < args.length && args[first]!.startsWith('-') && args[first] !== '-' && args[first] !== '--') {
|
|
382
|
-
|
|
471
|
+
const option = args[first]!;
|
|
472
|
+
const inline = /^(--eval|--print)=([\s\S]*)$/.exec(option);
|
|
473
|
+
if (inline) {
|
|
474
|
+
evaluated = { source: inline[2]!, print: inline[1] === '--print' };
|
|
475
|
+
execArgv.push(option);
|
|
476
|
+
first += 1;
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
479
|
+
if (option === '-e' || option === '--eval' || option === '-p' || option === '--print' || option === '-pe') {
|
|
480
|
+
if (first + 1 >= args.length) return { stdout: '', stderr: `node: ${option} requires an argument\n`, exitCode: 9 };
|
|
481
|
+
evaluated = { source: args[first + 1]!, print: option.includes('p') };
|
|
482
|
+
execArgv.push(option, args[first + 1]!);
|
|
483
|
+
first += 2;
|
|
484
|
+
break;
|
|
485
|
+
}
|
|
486
|
+
execArgv.push(option);
|
|
383
487
|
first += 1;
|
|
384
488
|
}
|
|
385
489
|
if (args[first] === '--') first += 1;
|
|
@@ -392,30 +496,21 @@ export function initChildProcess(vfs: VirtualFS): void {
|
|
|
392
496
|
};
|
|
393
497
|
let script = first;
|
|
394
498
|
let resolvedPath: string | null = null;
|
|
395
|
-
for (let index = first; index < args.length; index += 1) {
|
|
499
|
+
for (let index = first; evaluated === null && index < args.length; index += 1) {
|
|
396
500
|
const found = fileNamed(args[index]!);
|
|
397
501
|
if (found !== null) { script = index; resolvedPath = found; break; }
|
|
398
502
|
}
|
|
399
|
-
for (let index = first; index < script; index += 1) execArgv.push(args[index]!);
|
|
400
|
-
if (!args[script]) {
|
|
503
|
+
if (evaluated === null) for (let index = first; index < script; index += 1) execArgv.push(args[index]!);
|
|
504
|
+
if (evaluated === null && !args[script]) {
|
|
401
505
|
return { stdout: '', stderr: 'Usage: node <script.js> [args...]\n', exitCode: 1 };
|
|
402
506
|
}
|
|
403
|
-
if (resolvedPath === null) {
|
|
507
|
+
if (evaluated === null && resolvedPath === null) {
|
|
404
508
|
return { stdout: '', stderr: `Error: Cannot find module '${__resolvePath(ctx.cwd, args[script]!)}'\n`, exitCode: 1 };
|
|
405
509
|
}
|
|
406
510
|
|
|
407
511
|
let stdout = '';
|
|
408
512
|
let stderr = '';
|
|
409
513
|
|
|
410
|
-
// A run the host named is this guest process, for as long as the run
|
|
411
|
-
// lasts: `pendingTimers`, `processPorts` and `stopProcess` are answered
|
|
412
|
-
// from here, and a server the guest opens registers under this name.
|
|
413
|
-
const runToken = runTokenOf(ctx);
|
|
414
|
-
// What the host gave THIS run — its streams, its abort handle, whether it
|
|
415
|
-
// holds the run open. Read once here and nowhere from a module global, so
|
|
416
|
-
// a child spawned by this guest (a second run) cannot take them.
|
|
417
|
-
const streams = runToken === null ? undefined : runStreamsFor(runToken);
|
|
418
|
-
|
|
419
514
|
// Track whether process.exit() was called
|
|
420
515
|
let exitCalled = false;
|
|
421
516
|
let exitCode = 0;
|
|
@@ -534,7 +629,11 @@ export function initChildProcess(vfs: VirtualFS): void {
|
|
|
534
629
|
// Set up process.argv for the script. Node fills argv[0] with the
|
|
535
630
|
// executable's path, the same value `process.execPath` reports; `argv0`
|
|
536
631
|
// keeps the original argv[0], the plain word.
|
|
537
|
-
|
|
632
|
+
// An evaluated program has no script: its argv is the executable and the
|
|
633
|
+
// words after the source, as Node's is.
|
|
634
|
+
proc.argv = evaluated !== null
|
|
635
|
+
? [__substrateExecPath, ...args.slice(first)]
|
|
636
|
+
: [__substrateExecPath, resolvedPath!, ...args.slice(script + 1)];
|
|
538
637
|
proc.argv0 = 'node';
|
|
539
638
|
proc.execArgv = execArgv;
|
|
540
639
|
|
|
@@ -599,7 +698,8 @@ export function initChildProcess(vfs: VirtualFS): void {
|
|
|
599
698
|
syncExecution = false;
|
|
600
699
|
try { proc.exit(1); } finally { syncExecution = wasSync; }
|
|
601
700
|
};
|
|
602
|
-
const detachRejections = __listenForUnhandledRejections(onUnhandledRejection
|
|
701
|
+
const detachRejections = __listenForUnhandledRejections(proc, onUnhandledRejection,
|
|
702
|
+
runToken !== null && nodeProcessRealmToken() === runToken);
|
|
603
703
|
|
|
604
704
|
// An exception nobody caught ends the program it was thrown in, as it ends
|
|
605
705
|
// one in Node: a guest listener for `uncaughtException` on its process
|
|
@@ -668,7 +768,14 @@ export function initChildProcess(vfs: VirtualFS): void {
|
|
|
668
768
|
// this program's, not whichever run started last. The engine's storage
|
|
669
769
|
// carries it into the timers, microtasks and `then` callbacks the entry
|
|
670
770
|
// schedules from here, so a child spawned later still names its parent.
|
|
671
|
-
|
|
771
|
+
// `-p` prints the completion value of the source, which a direct `eval`
|
|
772
|
+
// in the module body yields with the body's own `require` in scope.
|
|
773
|
+
const runEntry = () => evaluated !== null
|
|
774
|
+
? runtime.evaluate(
|
|
775
|
+
evaluated.print ? `console.log(eval(${JSON.stringify(evaluated.source)}));` : evaluated.source,
|
|
776
|
+
__resolvePath(ctx.cwd, '[eval]'),
|
|
777
|
+
)
|
|
778
|
+
: runtime.runFile(resolvedPath!);
|
|
672
779
|
entrySettling = __substratePendingOf(
|
|
673
780
|
(runToken === null ? runEntry() : enterRun(runToken, runEntry)).exports,
|
|
674
781
|
);
|
|
@@ -734,7 +841,7 @@ export function initChildProcess(vfs: VirtualFS): void {
|
|
|
734
841
|
if ((stdout.length > 0 || stderr.length > 0) && !__printedThenWorking()) {
|
|
735
842
|
// Settling the command is host work. Killing guest timers must not
|
|
736
843
|
// cancel this continuation and leave the command's promise unresolved.
|
|
737
|
-
await
|
|
844
|
+
await settlePrintedEntry();
|
|
738
845
|
// Work that began during that tick is the program's still: a build
|
|
739
846
|
// reached through a dynamic import's chain, a timer the entry set.
|
|
740
847
|
// vue-pure-admin's mock loader, `import('bundle-import').then(...)`
|
|
@@ -950,7 +1057,6 @@ export function initChildProcess(vfs: VirtualFS): void {
|
|
|
950
1057
|
},
|
|
951
1058
|
start: startChildRun,
|
|
952
1059
|
});
|
|
953
|
-
watchChildren();
|
|
954
1060
|
}
|
|
955
1061
|
|
|
956
1062
|
/**
|
|
@@ -961,19 +1067,10 @@ export function initChildProcess(vfs: VirtualFS): void {
|
|
|
961
1067
|
* a realm here holds many programs and the channel belongs to one of them.
|
|
962
1068
|
*/
|
|
963
1069
|
function attachChannel(proc: ReturnType<Runtime['getProcess']>, fd: number, serialization: string): void {
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
//
|
|
967
|
-
|
|
968
|
-
// listens for a message.
|
|
969
|
-
channel.unref();
|
|
970
|
-
const control = setupChannel(proc, channel, serialization);
|
|
971
|
-
proc.on('newListener', (name: unknown) => {
|
|
972
|
-
if (name === 'message' || name === 'disconnect') control.refCounted();
|
|
973
|
-
});
|
|
974
|
-
proc.on('removeListener', (name: unknown) => {
|
|
975
|
-
if (name === 'message' || name === 'disconnect') control.unrefCounted();
|
|
976
|
-
});
|
|
1070
|
+
// With a process-bound loader, Node's own bootstrap now owns this too.
|
|
1071
|
+
// The host setupChannel built received sockets from the host net graph;
|
|
1072
|
+
// process.send and every received socket must use the child's graph.
|
|
1073
|
+
(loadNodeLibFor(proc, 'child_process') as ChildProcessModule)._forkChild(fd, serialization);
|
|
977
1074
|
}
|
|
978
1075
|
|
|
979
1076
|
/** The shell's environment as a record: just-bash hands a command a Map. */
|
|
@@ -1251,6 +1348,19 @@ interface ChildProcessHostResult {
|
|
|
1251
1348
|
exitCode: number;
|
|
1252
1349
|
}
|
|
1253
1350
|
|
|
1351
|
+
/**
|
|
1352
|
+
* One of a child's pipes past fd 2, as the host's program holds it: the
|
|
1353
|
+
* parent's handle for that fd is the other end.
|
|
1354
|
+
*/
|
|
1355
|
+
interface ChildProcessHostDescriptor {
|
|
1356
|
+
/** Bytes the parent writes to this fd, in order; ends when the parent closes it. */
|
|
1357
|
+
input: AsyncIterable<Uint8Array>;
|
|
1358
|
+
/** Bytes the child writes on this fd; they are reads on the parent's handle. */
|
|
1359
|
+
write(bytes: Uint8Array): void;
|
|
1360
|
+
/** The child closed this fd: EOF on the parent's handle. */
|
|
1361
|
+
end(): void;
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1254
1364
|
/** How a command is handed to that host. */
|
|
1255
1365
|
interface ChildProcessHostRequest {
|
|
1256
1366
|
cwd?: string;
|
|
@@ -1258,10 +1368,13 @@ interface ChildProcessHostRequest {
|
|
|
1258
1368
|
/** Bytes already on fd 0 when the command begins, as Node delivers them. */
|
|
1259
1369
|
stdin?: string;
|
|
1260
1370
|
stdinStream: AsyncIterable<Uint8Array>;
|
|
1371
|
+
terminal?: RunStreams['terminal'];
|
|
1261
1372
|
signal: AbortSignal;
|
|
1262
1373
|
hold: { value: boolean };
|
|
1263
1374
|
onStdout: (data: string) => void;
|
|
1264
1375
|
onStderr: (data: string) => void;
|
|
1376
|
+
/** The child's pipes past fd 2, by number; only where it was started with any. */
|
|
1377
|
+
descriptors?: Record<number, ChildProcessHostDescriptor>;
|
|
1265
1378
|
}
|
|
1266
1379
|
|
|
1267
1380
|
/** The executor a host publishes on the well-known global symbol. */
|
|
@@ -1308,10 +1421,13 @@ interface CommandRun {
|
|
|
1308
1421
|
stdin?: string;
|
|
1309
1422
|
/** Bytes a writer still holds the run's fd 0 open with, as they arrive. */
|
|
1310
1423
|
stdinStream?: AsyncIterable<Uint8Array>;
|
|
1424
|
+
terminal?: RunStreams['terminal'];
|
|
1311
1425
|
signal?: AbortSignal;
|
|
1312
1426
|
onStdout?: (data: string) => void;
|
|
1313
1427
|
onStderr?: (data: string) => void;
|
|
1314
1428
|
vfs?: VirtualFS;
|
|
1429
|
+
/** The child's ends of its pipes past fd 2, for a host that runs it. */
|
|
1430
|
+
descriptors?: { fd: number; pipe: Pipe }[];
|
|
1315
1431
|
}
|
|
1316
1432
|
|
|
1317
1433
|
/**
|
|
@@ -1349,16 +1465,29 @@ async function routeCommand(run: CommandRun): Promise<CommandOutcome> {
|
|
|
1349
1465
|
let streamedErr = '';
|
|
1350
1466
|
const onStdout = (data: string): void => { streamedOut += String(data); run.onStdout?.(String(data)); };
|
|
1351
1467
|
const onStderr = (data: string): void => { streamedErr += String(data); run.onStderr?.(String(data)); };
|
|
1468
|
+
// This token routes the engine's own shell, not a program's environment.
|
|
1469
|
+
// If a host shell inherits it, a later Node launch can override its new
|
|
1470
|
+
// stream identity with the ancestor's and deliver output to both runs.
|
|
1471
|
+
// Host ancestry is already carried by the current process context.
|
|
1472
|
+
const hostEnv = run.env === undefined ? undefined : { ...run.env };
|
|
1473
|
+
if (hostEnv) delete hostEnv[PROCESS_TOKEN_ENV];
|
|
1474
|
+
// The host's program is the one holding the child's pipes past fd 2; the
|
|
1475
|
+
// run's end is its end of them, whatever it did not close itself.
|
|
1476
|
+
const opened = (run.descriptors ?? []).map(({ fd, pipe }) => ({ fd, ...descriptorOver(pipe) }));
|
|
1352
1477
|
try {
|
|
1353
1478
|
const result = await bridge.run(run.command, {
|
|
1354
1479
|
cwd: run.cwd,
|
|
1355
|
-
env:
|
|
1480
|
+
env: hostEnv,
|
|
1356
1481
|
...(typeof run.stdin === 'string' ? { stdin: run.stdin } : {}),
|
|
1357
1482
|
stdinStream: run.stdinStream ?? emptyStdinStream(),
|
|
1483
|
+
...(run.terminal ? { terminal: run.terminal } : {}),
|
|
1358
1484
|
signal: controller.signal,
|
|
1359
1485
|
hold: { value: true },
|
|
1360
1486
|
onStdout,
|
|
1361
1487
|
onStderr,
|
|
1488
|
+
...(opened.length > 0
|
|
1489
|
+
? { descriptors: Object.fromEntries(opened.map(({ fd, descriptor }) => [fd, descriptor])) }
|
|
1490
|
+
: {}),
|
|
1362
1491
|
});
|
|
1363
1492
|
const stdout = result.stdout || streamedOut;
|
|
1364
1493
|
const stderr = result.stderr || streamedErr;
|
|
@@ -1367,6 +1496,7 @@ async function routeCommand(run: CommandRun): Promise<CommandOutcome> {
|
|
|
1367
1496
|
return { stdout, stderr, exitCode: result.exitCode };
|
|
1368
1497
|
} finally {
|
|
1369
1498
|
if (bridge.parentSignal) bridge.parentSignal.removeEventListener('abort', abortWithParent);
|
|
1499
|
+
for (const { release } of opened) release();
|
|
1370
1500
|
}
|
|
1371
1501
|
}
|
|
1372
1502
|
|
|
@@ -1379,6 +1509,63 @@ async function routeCommand(run: CommandRun): Promise<CommandOutcome> {
|
|
|
1379
1509
|
return { stdout: result.stdout || '', stderr: result.stderr || '', exitCode: result.exitCode };
|
|
1380
1510
|
}
|
|
1381
1511
|
|
|
1512
|
+
/**
|
|
1513
|
+
* A child's pipe past fd 2, held by a program the host runs: the parent's
|
|
1514
|
+
* writes on its handle are the program's input, read the way the handle's fd 0
|
|
1515
|
+
* is read, and the program's writes are reads on the parent's handle, as bytes.
|
|
1516
|
+
* `release` is the run's end: reading stops and the input ends.
|
|
1517
|
+
*/
|
|
1518
|
+
function descriptorOver(pipe: Pipe): { descriptor: ChildProcessHostDescriptor; release(): void } {
|
|
1519
|
+
const queued: Uint8Array[] = [];
|
|
1520
|
+
let waiting: ((result: IteratorResult<Uint8Array>) => void) | null = null;
|
|
1521
|
+
let ended = false;
|
|
1522
|
+
const finish = (): void => {
|
|
1523
|
+
if (ended) return;
|
|
1524
|
+
ended = true;
|
|
1525
|
+
pipe.readStop();
|
|
1526
|
+
pipe.onread = null;
|
|
1527
|
+
if (waiting) { const wake = waiting; waiting = null; wake({ value: undefined, done: true }); }
|
|
1528
|
+
};
|
|
1529
|
+
pipe.onread = (arrayBuffer: ArrayBuffer | null): void => {
|
|
1530
|
+
const length = streamBaseState[kReadBytesOrError];
|
|
1531
|
+
if (length <= 0 || arrayBuffer === null) { finish(); return; }
|
|
1532
|
+
const bytes = new Uint8Array(arrayBuffer, streamBaseState[kArrayBufferOffset], length).slice();
|
|
1533
|
+
if (waiting) { const wake = waiting; waiting = null; wake({ value: bytes, done: false }); }
|
|
1534
|
+
else queued.push(bytes);
|
|
1535
|
+
};
|
|
1536
|
+
pipe.readStart();
|
|
1537
|
+
const input: AsyncIterable<Uint8Array> = {
|
|
1538
|
+
[Symbol.asyncIterator](): AsyncIterator<Uint8Array> {
|
|
1539
|
+
return {
|
|
1540
|
+
next(): Promise<IteratorResult<Uint8Array>> {
|
|
1541
|
+
const next = queued.shift();
|
|
1542
|
+
if (next) return Promise.resolve({ value: next, done: false });
|
|
1543
|
+
if (ended) return Promise.resolve({ value: undefined, done: true });
|
|
1544
|
+
return new Promise((resolve) => { waiting = resolve; });
|
|
1545
|
+
},
|
|
1546
|
+
return(): Promise<IteratorResult<Uint8Array>> {
|
|
1547
|
+
finish();
|
|
1548
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
1549
|
+
},
|
|
1550
|
+
};
|
|
1551
|
+
},
|
|
1552
|
+
};
|
|
1553
|
+
return {
|
|
1554
|
+
descriptor: {
|
|
1555
|
+
input,
|
|
1556
|
+
write(bytes: Uint8Array): void {
|
|
1557
|
+
if (pipe.closed) return;
|
|
1558
|
+
pipe.writeBuffer(new WriteWrap(), bytes);
|
|
1559
|
+
},
|
|
1560
|
+
end(): void {
|
|
1561
|
+
finish();
|
|
1562
|
+
if (!pipe.closed) pipe.shutdown(new ShutdownWrap());
|
|
1563
|
+
},
|
|
1564
|
+
},
|
|
1565
|
+
release: finish,
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1382
1569
|
/** A child whose stdin nobody writes to still needs a stream the host can read. */
|
|
1383
1570
|
function emptyStdinStream(): AsyncIterable<Uint8Array> {
|
|
1384
1571
|
return { async *[Symbol.asyncIterator]() { /* nothing is ever written */ } };
|
|
@@ -1491,9 +1678,28 @@ function startChildRun(request: RunRequest): StartedRun {
|
|
|
1491
1678
|
// number of the run that spawned it. `src/process-tokens.ts` says what
|
|
1492
1679
|
// reads them.
|
|
1493
1680
|
const pid = mintPid();
|
|
1494
|
-
|
|
1681
|
+
// In a process realm the admitted guest is always the spawning parent.
|
|
1682
|
+
// Async bookkeeping for a routed child must not make that child its parent.
|
|
1683
|
+
setRunPid(token, pid, runPid(nodeProcessRealmToken() ?? __currentProcessToken() ?? __lastLaunchedToken)?.pid ?? 0);
|
|
1495
1684
|
const controller = new AbortController();
|
|
1496
1685
|
const pendingStdin: Array<Uint8Array | null> = [];
|
|
1686
|
+
// A host terminal consumes input incrementally. The old string-only
|
|
1687
|
+
// route dropped every keystroke that arrived after a child was launched.
|
|
1688
|
+
const admittedNode = nodeProcessHostInstalled() && engineProgramFor(request.file, request.cwd) === 'node';
|
|
1689
|
+
const hostTerminal = request.terminal !== undefined && hostExecutor() !== null && !admittedNode;
|
|
1690
|
+
let wakeInput: (() => void) | undefined;
|
|
1691
|
+
const hostInput: AsyncIterable<Uint8Array> = {
|
|
1692
|
+
async *[Symbol.asyncIterator]() {
|
|
1693
|
+
while (!finished && !controller.signal.aborted) {
|
|
1694
|
+
if (pendingStdin.length) {
|
|
1695
|
+
const bytes = pendingStdin.shift();
|
|
1696
|
+
if (bytes === null) return;
|
|
1697
|
+
if (bytes) yield bytes;
|
|
1698
|
+
} else await new Promise<void>((resolve) => { wakeInput = resolve; });
|
|
1699
|
+
}
|
|
1700
|
+
},
|
|
1701
|
+
};
|
|
1702
|
+
controller.signal.addEventListener('abort', () => { wakeInput?.(); }, { once: true });
|
|
1497
1703
|
let stdinSink: RunStdin | null = null;
|
|
1498
1704
|
let finished = false;
|
|
1499
1705
|
let started = false;
|
|
@@ -1501,9 +1707,22 @@ function startChildRun(request: RunRequest): StartedRun {
|
|
|
1501
1707
|
/** What the parent wrote to the child's fd 0 before the command began. */
|
|
1502
1708
|
const initialStdin: Uint8Array[] = [];
|
|
1503
1709
|
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1710
|
+
let flushingInput: Promise<void> | undefined;
|
|
1711
|
+
const flushStdin = (): void | Promise<void> => {
|
|
1712
|
+
if (flushingInput) return flushingInput;
|
|
1713
|
+
if (!stdinSink || finished) return;
|
|
1714
|
+
while (pendingStdin.length > 0) {
|
|
1715
|
+
const chunk = pendingStdin.shift() as Uint8Array | null;
|
|
1716
|
+
if (stdinSink.writeAsync) {
|
|
1717
|
+
const waiting = stdinSink.writeAsync(chunk);
|
|
1718
|
+
flushingInput = waiting.then(() => {
|
|
1719
|
+
flushingInput = undefined;
|
|
1720
|
+
return flushStdin();
|
|
1721
|
+
}, cause => { flushingInput = undefined; throw cause; });
|
|
1722
|
+
return flushingInput;
|
|
1723
|
+
}
|
|
1724
|
+
stdinSink.push(chunk);
|
|
1725
|
+
}
|
|
1507
1726
|
};
|
|
1508
1727
|
|
|
1509
1728
|
let streamedOut = 0;
|
|
@@ -1515,23 +1734,39 @@ function startChildRun(request: RunRequest): StartedRun {
|
|
|
1515
1734
|
held: false,
|
|
1516
1735
|
stdinOpen: request.stdinIsPipe,
|
|
1517
1736
|
stderrIsPipe: request.stderrIsPipe,
|
|
1737
|
+
terminal: request.terminal,
|
|
1518
1738
|
get stdin(): RunStdin | null | undefined { return stdinSink; },
|
|
1519
|
-
set stdin(sink: RunStdin | null | undefined) {
|
|
1739
|
+
set stdin(sink: RunStdin | null | undefined) {
|
|
1740
|
+
stdinSink = sink ?? null;
|
|
1741
|
+
if (stdinSink) {
|
|
1742
|
+
const pending = flushStdin();
|
|
1743
|
+
if (pending) void pending.catch(cause => {
|
|
1744
|
+
// Normal child exit detaches the sink before cancelling its writer.
|
|
1745
|
+
// That cancellation must not turn a successful exit into failure.
|
|
1746
|
+
if (!finished && stdinSink === sink) { controller.abort(cause); end(1, null); }
|
|
1747
|
+
});
|
|
1748
|
+
}
|
|
1749
|
+
},
|
|
1520
1750
|
};
|
|
1521
1751
|
registerRunStreams(token, streams);
|
|
1522
1752
|
// The child is started with the descriptors its parent wired: an IPC channel
|
|
1523
1753
|
// is at the number its `NODE_CHANNEL_FD` will name, under this run's name,
|
|
1524
1754
|
// because every child is told the same number.
|
|
1525
1755
|
if (request.channel) registerRunFd(token, request.channel.fd, 'PIPE', request.channel.pipe);
|
|
1756
|
+
// Its pipes past fd 2 are there the same way, at the numbers it was given.
|
|
1757
|
+
for (const { fd, pipe } of request.descriptors) registerRunFd(token, fd, 'PIPE', pipe);
|
|
1526
1758
|
|
|
1527
1759
|
const end = (code: number, signal: string | null): void => {
|
|
1528
1760
|
if (finished) return;
|
|
1529
1761
|
finished = true;
|
|
1762
|
+
wakeInput?.();
|
|
1763
|
+
wakeInput = undefined;
|
|
1530
1764
|
_activeForkedChildren -= 1;
|
|
1531
1765
|
releaseRunStreams(token);
|
|
1532
1766
|
releaseRunFds(token);
|
|
1533
1767
|
// A run that has ended is no longer a process: its number answers ESRCH.
|
|
1534
1768
|
forgetRunPid(token);
|
|
1769
|
+
__substrateChildren.delete(pid);
|
|
1535
1770
|
_onForkedChildExit?.();
|
|
1536
1771
|
request.exit(code, signal);
|
|
1537
1772
|
};
|
|
@@ -1554,16 +1789,24 @@ function startChildRun(request: RunRequest): StartedRun {
|
|
|
1554
1789
|
void (async () => {
|
|
1555
1790
|
let outcome: CommandOutcome;
|
|
1556
1791
|
try {
|
|
1557
|
-
|
|
1792
|
+
const processHost = admittedNode ? nodeProcessHostFor(token) : undefined;
|
|
1793
|
+
const env = { ...request.env };
|
|
1794
|
+
delete env[PROCESS_TOKEN_ENV];
|
|
1795
|
+
outcome = processHost ? await runHostedNode(processHost, {
|
|
1796
|
+
token, argv: request.args.slice(1), cwd: request.cwd ?? '/',
|
|
1797
|
+
filesystem: currentVfs!, env, streams, ...(stdin !== undefined ? { stdin } : {}),
|
|
1798
|
+
}) : await enterRun(token, () => routeCommand({
|
|
1558
1799
|
command: __substrateLineFor(request.file, request.args, request.cwd),
|
|
1559
|
-
engineFirst: programExists(request.file, request.cwd, request.env),
|
|
1800
|
+
engineFirst: !hostTerminal && programExists(request.file, request.cwd, request.env),
|
|
1560
1801
|
cwd: request.cwd,
|
|
1561
1802
|
env: { ...request.env, [PROCESS_TOKEN_ENV]: token },
|
|
1562
1803
|
stdin,
|
|
1804
|
+
...(hostTerminal ? { stdinStream: hostInput, terminal: request.terminal } : {}),
|
|
1563
1805
|
signal: controller.signal,
|
|
1564
1806
|
onStdout: streams.onStdout,
|
|
1565
1807
|
onStderr: streams.onStderr,
|
|
1566
|
-
|
|
1808
|
+
descriptors: request.descriptors,
|
|
1809
|
+
}));
|
|
1567
1810
|
} catch (error) {
|
|
1568
1811
|
outcome = { stdout: '', stderr: `${error instanceof Error ? error.message : String(error)}\n`, exitCode: 1 };
|
|
1569
1812
|
}
|
|
@@ -1577,10 +1820,10 @@ function startChildRun(request: RunRequest): StartedRun {
|
|
|
1577
1820
|
};
|
|
1578
1821
|
|
|
1579
1822
|
_activeForkedChildren += 1;
|
|
1580
|
-
if (request.stdinIsPipe) (globalThis.__browserRuntimeNativeSetTimeout ?? setTimeout).call(globalThis, begin, 0);
|
|
1823
|
+
if (request.stdinIsPipe && !admittedNode) (globalThis.__browserRuntimeNativeSetTimeout ?? setTimeout).call(globalThis, begin, 0);
|
|
1581
1824
|
else begin();
|
|
1582
1825
|
|
|
1583
|
-
|
|
1826
|
+
const control: StartedRun = {
|
|
1584
1827
|
token,
|
|
1585
1828
|
pid,
|
|
1586
1829
|
kill(signal: string): number {
|
|
@@ -1593,12 +1836,14 @@ function startChildRun(request: RunRequest): StartedRun {
|
|
|
1593
1836
|
end(0, killedBy);
|
|
1594
1837
|
return 0;
|
|
1595
1838
|
},
|
|
1596
|
-
writeStdin(bytes: Uint8Array): void {
|
|
1839
|
+
writeStdin(bytes: Uint8Array): void | Promise<void> {
|
|
1840
|
+
if (hostTerminal) { pendingStdin.push(bytes); wakeInput?.(); wakeInput = undefined; return; }
|
|
1597
1841
|
if (!started) { initialStdin.push(bytes); return; }
|
|
1598
1842
|
pendingStdin.push(bytes);
|
|
1599
|
-
flushStdin();
|
|
1843
|
+
return flushStdin();
|
|
1600
1844
|
},
|
|
1601
1845
|
endStdin(): void {
|
|
1846
|
+
if (hostTerminal) { pendingStdin.push(null); wakeInput?.(); wakeInput = undefined; return; }
|
|
1602
1847
|
if (!started) {
|
|
1603
1848
|
// The writer closed before the command began: the run's fd 0 is the
|
|
1604
1849
|
// string already collected and nothing more, so it is not held open.
|
|
@@ -1607,38 +1852,23 @@ function startChildRun(request: RunRequest): StartedRun {
|
|
|
1607
1852
|
return;
|
|
1608
1853
|
}
|
|
1609
1854
|
pendingStdin.push(null);
|
|
1610
|
-
|
|
1855
|
+
const sink = stdinSink;
|
|
1856
|
+
const pending = flushStdin();
|
|
1857
|
+
if (pending) void pending.catch(cause => {
|
|
1858
|
+
if (!finished && stdinSink === sink && sink) { controller.abort(cause); end(1, null); }
|
|
1859
|
+
});
|
|
1611
1860
|
},
|
|
1612
1861
|
};
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
* Node publishes each `ChildProcess` on the `child_process` diagnostics
|
|
1619
|
-
* channel as it is constructed, which is the door this reads: the pid is
|
|
1620
|
-
* filled by `spawn` a moment later, so the child is recorded on the next tick.
|
|
1621
|
-
*/
|
|
1622
|
-
function watchChildren(): void {
|
|
1623
|
-
// Node's own `diagnostics_channel`, the same module the vendored
|
|
1624
|
-
// `child_process.js` publishes on. A channel is a name in one registry, and
|
|
1625
|
-
// the engine's own imitation of the module was a second registry: a
|
|
1626
|
-
// subscriber there heard nothing a vendored file published.
|
|
1627
|
-
const diagnosticsChannel = diagnosticsChannelModule as unknown as {
|
|
1628
|
-
channel(name: string): { subscribe(listener: (message: unknown) => void): void };
|
|
1629
|
-
};
|
|
1630
|
-
diagnosticsChannel.channel('child_process').subscribe((message: unknown) => {
|
|
1631
|
-
const child = (message as { process?: { pid?: number; exitCode: number | null; signalCode: string | null; kill(signal?: string): boolean; once(event: string, listener: () => void): unknown } }).process;
|
|
1632
|
-
if (!child) return;
|
|
1633
|
-
queueMicrotask(() => {
|
|
1634
|
-
const pid = child.pid;
|
|
1635
|
-
if (typeof pid !== 'number' || pid === 0) return;
|
|
1636
|
-
__substrateChildren.set(pid, child);
|
|
1637
|
-
child.once('exit', () => { __substrateChildren.delete(pid); });
|
|
1638
|
-
});
|
|
1862
|
+
// A pid is signalable before spawn returns, including cancellation in
|
|
1863
|
+
// the same turn. A diagnostics-channel microtask registered it too late.
|
|
1864
|
+
__substrateChildren.set(pid, {
|
|
1865
|
+
exitCode: null, signalCode: null,
|
|
1866
|
+
kill: (signal = 'SIGTERM') => control.kill(signal) === 0,
|
|
1639
1867
|
});
|
|
1868
|
+
return control;
|
|
1640
1869
|
}
|
|
1641
1870
|
|
|
1871
|
+
|
|
1642
1872
|
export default {
|
|
1643
1873
|
initChildProcess,
|
|
1644
1874
|
registerRunStreams,
|