wasm-idle 1.0.11 → 1.0.12
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/README.md +82 -64
- package/dist/playground/aheui.d.ts +18 -0
- package/dist/playground/aheui.js +21 -0
- package/dist/playground/apecode.d.ts +18 -0
- package/dist/playground/apecode.js +21 -0
- package/dist/playground/assets.d.ts +18 -0
- package/dist/playground/assets.js +28 -0
- package/dist/playground/golfscript.d.ts +6 -0
- package/dist/playground/golfscript.js +8 -0
- package/dist/playground/index.d.ts +1 -1
- package/dist/playground/index.js +81 -0
- package/dist/playground/python.d.ts +6 -2
- package/dist/playground/python.js +81 -15
- package/dist/playground/ruby.d.ts +6 -0
- package/dist/playground/ruby.js +108 -56
- package/dist/playground/wasiInterpreters.d.ts +15 -0
- package/dist/playground/wasiInterpreters.js +67 -0
- package/dist/playground/wasm.d.ts +3 -0
- package/dist/playground/wasm.js +44 -3
- package/dist/playground/wasmAheuiVersion.d.ts +7 -0
- package/dist/playground/wasmAheuiVersion.js +10 -0
- package/dist/playground/wasmApecodeVersion.d.ts +7 -0
- package/dist/playground/wasmApecodeVersion.js +9 -0
- package/dist/playground/wasmGolfscriptVersion.d.ts +10 -0
- package/dist/playground/wasmGolfscriptVersion.js +10 -0
- package/dist/playground/worker/golfscriptMount.d.ts +45 -0
- package/dist/playground/worker/golfscriptMount.js +79 -0
- package/dist/playground/worker/python.js +227 -34
- package/dist/playground/worker/ruby.js +151 -15
- package/dist/playground/worker/wasm.js +106 -7
- package/package.json +21 -7
|
@@ -5,11 +5,14 @@ import { bufferedSequence, flushBufferedEof, flushQueuedStdin, resetBufferedStdi
|
|
|
5
5
|
import { createWasmIdleSharedBuffer, requireSharedArrayBuffer } from './sharedBuffer';
|
|
6
6
|
import { WorkerSession } from './workerSession';
|
|
7
7
|
import { reportWorkerProgress } from './workerProgress';
|
|
8
|
-
import { BusyError, TimeoutError, resolveExecutionLimits } from '@wasm-idle/core';
|
|
8
|
+
import { BusyError, DEFAULT_WORKSPACE_LIMITS, OutputLimitError, RuntimeConfigurationError, TimeoutError, resolveExecutionLimits, validateExecutionWorkspace } from '@wasm-idle/core';
|
|
9
9
|
const debugBreakpointBufferInts = 1028;
|
|
10
|
-
const
|
|
10
|
+
const outputEncoder = new TextEncoder();
|
|
11
|
+
const abortReason = (signal, phase, label = 'Python') => signal.reason !== undefined
|
|
11
12
|
? signal.reason
|
|
12
|
-
: new DOMException(phase === 'startup'
|
|
13
|
+
: new DOMException(phase === 'startup'
|
|
14
|
+
? `${label} runtime startup aborted`
|
|
15
|
+
: `${label} execution aborted`, 'AbortError');
|
|
13
16
|
class Python {
|
|
14
17
|
ts = Date.now();
|
|
15
18
|
output = null;
|
|
@@ -37,6 +40,25 @@ class Python {
|
|
|
37
40
|
workerExtensionKey;
|
|
38
41
|
/** Language executed by the shared Pyodide worker; subclasses run other Python-hosted languages. */
|
|
39
42
|
workerLanguage = 'python';
|
|
43
|
+
get usesInterpreterStdio() {
|
|
44
|
+
return this.workerLanguage === 'aheui' || this.workerLanguage === 'apecode';
|
|
45
|
+
}
|
|
46
|
+
get runtimeId() {
|
|
47
|
+
if (this.workerLanguage === 'apecode')
|
|
48
|
+
return 'APECODE';
|
|
49
|
+
return this.workerLanguage === 'aheui' ? 'AHEUI' : 'PYTHON3';
|
|
50
|
+
}
|
|
51
|
+
get runtimeLabel() {
|
|
52
|
+
if (this.workerLanguage === 'apecode')
|
|
53
|
+
return 'APECode';
|
|
54
|
+
return this.workerLanguage === 'aheui' ? 'Aheui' : 'Python';
|
|
55
|
+
}
|
|
56
|
+
resetInterpreterInput() {
|
|
57
|
+
if (!this.usesInterpreterStdio)
|
|
58
|
+
return;
|
|
59
|
+
this.pendingInput = [];
|
|
60
|
+
resetBufferedStdin(this.buffer);
|
|
61
|
+
}
|
|
40
62
|
/** Extra worker load configuration for Python-hosted languages, keyed for worker reuse. */
|
|
41
63
|
workerLoadExtension(_runtimeAssets, _currentUrl) {
|
|
42
64
|
return undefined;
|
|
@@ -57,10 +79,10 @@ class Python {
|
|
|
57
79
|
});
|
|
58
80
|
beginOperation(phase) {
|
|
59
81
|
if (this.disposed)
|
|
60
|
-
throw new Error(
|
|
82
|
+
throw new Error(`${this.runtimeLabel} runtime has been disposed`);
|
|
61
83
|
if (this.activeOperation) {
|
|
62
|
-
throw new BusyError(
|
|
63
|
-
runtimeId:
|
|
84
|
+
throw new BusyError(`${this.runtimeLabel} runtime already has an active operation`, {
|
|
85
|
+
runtimeId: this.runtimeId,
|
|
64
86
|
phase: this.activeOperation.phase
|
|
65
87
|
});
|
|
66
88
|
}
|
|
@@ -110,6 +132,7 @@ class Python {
|
|
|
110
132
|
this.activeOperation = null;
|
|
111
133
|
this.waitingForInput = false;
|
|
112
134
|
this.pendingEof = false;
|
|
135
|
+
this.resetInterpreterInput();
|
|
113
136
|
this.uid += 1;
|
|
114
137
|
this.exit = true;
|
|
115
138
|
this.workerSession.terminate(reason);
|
|
@@ -124,7 +147,7 @@ class Python {
|
|
|
124
147
|
return;
|
|
125
148
|
let reason;
|
|
126
149
|
try {
|
|
127
|
-
reason = abortReason(signal, operation.phase);
|
|
150
|
+
reason = abortReason(signal, operation.phase, this.runtimeLabel);
|
|
128
151
|
}
|
|
129
152
|
catch (error) {
|
|
130
153
|
reason = error;
|
|
@@ -145,9 +168,10 @@ class Python {
|
|
|
145
168
|
this.cancelOperation(operation, error);
|
|
146
169
|
}
|
|
147
170
|
}
|
|
148
|
-
|
|
171
|
+
bindOperationTimeout(operation, timeoutMs) {
|
|
149
172
|
if (!this.isOperationActive(operation))
|
|
150
173
|
return;
|
|
174
|
+
timeoutMs = Math.min(2_147_483_647, timeoutMs);
|
|
151
175
|
let timeout;
|
|
152
176
|
operation.cleanups.push(() => {
|
|
153
177
|
if (timeout !== undefined)
|
|
@@ -157,9 +181,9 @@ class Python {
|
|
|
157
181
|
timeout = setTimeout(() => {
|
|
158
182
|
if (!this.isOperationActive(operation))
|
|
159
183
|
return;
|
|
160
|
-
this.cancelOperation(operation, new TimeoutError(
|
|
161
|
-
phase:
|
|
162
|
-
runtimeId:
|
|
184
|
+
this.cancelOperation(operation, new TimeoutError(`${this.runtimeLabel} ${operation.phase === 'startup' ? 'startup' : 'execution'} timed out after ${timeoutMs} ms`, {
|
|
185
|
+
phase: operation.phase,
|
|
186
|
+
runtimeId: this.runtimeId,
|
|
163
187
|
timeoutMs
|
|
164
188
|
}));
|
|
165
189
|
}, timeoutMs);
|
|
@@ -187,7 +211,7 @@ class Python {
|
|
|
187
211
|
return Promise.reject(this.releaseBeforeSession(operation, 'Python runtime startup cancelled'));
|
|
188
212
|
}
|
|
189
213
|
if (signal?.aborted) {
|
|
190
|
-
return Promise.reject(this.releaseBeforeSession(operation, abortReason(signal, 'startup')));
|
|
214
|
+
return Promise.reject(this.releaseBeforeSession(operation, abortReason(signal, 'startup', this.runtimeLabel)));
|
|
191
215
|
}
|
|
192
216
|
}
|
|
193
217
|
catch (error) {
|
|
@@ -300,7 +324,7 @@ class Python {
|
|
|
300
324
|
}
|
|
301
325
|
});
|
|
302
326
|
const timeoutMs = Math.min(2_147_483_647, limits.assetTimeoutMs + limits.startupTimeoutMs);
|
|
303
|
-
this.
|
|
327
|
+
this.bindOperationTimeout(operation, timeoutMs);
|
|
304
328
|
this.bindAbortSignal(operation, signal);
|
|
305
329
|
return loading.finally(() => {
|
|
306
330
|
this.releaseOperation(operation);
|
|
@@ -346,6 +370,7 @@ class Python {
|
|
|
346
370
|
let activePath;
|
|
347
371
|
let debugPath;
|
|
348
372
|
let workspaceFiles;
|
|
373
|
+
let limits;
|
|
349
374
|
try {
|
|
350
375
|
signal = options.signal;
|
|
351
376
|
debug = !!options.debug;
|
|
@@ -355,6 +380,24 @@ class Python {
|
|
|
355
380
|
activePath = options.activePath;
|
|
356
381
|
debugPath = options.debugPath;
|
|
357
382
|
workspaceFiles = options.workspaceFiles;
|
|
383
|
+
if (this.usesInterpreterStdio) {
|
|
384
|
+
limits = resolveExecutionLimits(options.limits);
|
|
385
|
+
if (_args.length || options.programArgs?.length) {
|
|
386
|
+
throw new RuntimeConfigurationError(`${this.runtimeLabel} does not accept program arguments`, { runtimeId: this.runtimeId });
|
|
387
|
+
}
|
|
388
|
+
if (debug || (options.debugMode && options.debugMode !== 'none')) {
|
|
389
|
+
throw new RuntimeConfigurationError(`${this.runtimeLabel} debugging is not supported`, { runtimeId: this.runtimeId });
|
|
390
|
+
}
|
|
391
|
+
const workspace = validateExecutionWorkspace(code, workspaceFiles ?? [], activePath ?? (this.workerLanguage === 'apecode' ? 'main.ape' : 'main.aheui'), {
|
|
392
|
+
...options.workspaceLimits,
|
|
393
|
+
maxFileBytes: Math.min(options.workspaceLimits?.maxFileBytes ??
|
|
394
|
+
DEFAULT_WORKSPACE_LIMITS.maxFileBytes, limits.maxWorkspaceBytes),
|
|
395
|
+
maxTotalBytes: Math.min(options.workspaceLimits?.maxTotalBytes ??
|
|
396
|
+
DEFAULT_WORKSPACE_LIMITS.maxTotalBytes, limits.maxWorkspaceBytes)
|
|
397
|
+
});
|
|
398
|
+
activePath = workspace.activePath;
|
|
399
|
+
workspaceFiles = workspace.workspaceFiles;
|
|
400
|
+
}
|
|
358
401
|
const persistentCache = options.persistentCache;
|
|
359
402
|
if (!this.isOperationActive(activeOperation)) {
|
|
360
403
|
return Promise.reject(this.releaseBeforeSession(activeOperation, 'Python execution cancelled'));
|
|
@@ -370,14 +413,22 @@ class Python {
|
|
|
370
413
|
if (!worker || this.worker !== worker) {
|
|
371
414
|
return Promise.reject(this.releaseBeforeSession(activeOperation, 'Worker not loaded'));
|
|
372
415
|
}
|
|
416
|
+
this.resetInterpreterInput();
|
|
417
|
+
if (this.usesInterpreterStdio) {
|
|
418
|
+
this.waitingForInput = false;
|
|
419
|
+
this.pendingEof = false;
|
|
420
|
+
}
|
|
373
421
|
this.exit = false;
|
|
374
422
|
const running = new Promise((resolve, reject) => {
|
|
375
423
|
const workerOperation = this.workerSession.beginRun(worker, reject);
|
|
424
|
+
if (limits)
|
|
425
|
+
this.bindOperationTimeout(activeOperation, prepare ? limits.compileTimeoutMs : limits.runTimeoutMs);
|
|
376
426
|
this.bindAbortSignal(activeOperation, signal);
|
|
377
427
|
if (!this.isOperationActive(activeOperation))
|
|
378
428
|
return;
|
|
379
429
|
const interrupt = new Uint8Array(this.interruptBuffer), runUid = ++this.uid;
|
|
380
430
|
let handler;
|
|
431
|
+
let outputBytes = 0;
|
|
381
432
|
const ownsRun = () => this.isOperationActive(activeOperation) &&
|
|
382
433
|
this.worker === worker &&
|
|
383
434
|
worker.onmessage === handler &&
|
|
@@ -387,6 +438,7 @@ class Python {
|
|
|
387
438
|
this.exit = true;
|
|
388
439
|
this.waitingForInput = false;
|
|
389
440
|
this.pendingEof = false;
|
|
441
|
+
this.resetInterpreterInput();
|
|
390
442
|
};
|
|
391
443
|
const claimRun = () => {
|
|
392
444
|
if (!ownsRun())
|
|
@@ -431,7 +483,7 @@ class Python {
|
|
|
431
483
|
kind: 'ready',
|
|
432
484
|
state: 'waiting-input',
|
|
433
485
|
reason: 'stdin-request',
|
|
434
|
-
label:
|
|
486
|
+
label: `${this.runtimeLabel} program is waiting for input`
|
|
435
487
|
});
|
|
436
488
|
}
|
|
437
489
|
this.waitingForInput = true;
|
|
@@ -443,8 +495,21 @@ class Python {
|
|
|
443
495
|
this.image?.(payload);
|
|
444
496
|
if (!ownsRun())
|
|
445
497
|
return;
|
|
446
|
-
if (output)
|
|
498
|
+
if (output) {
|
|
499
|
+
if (limits) {
|
|
500
|
+
outputBytes += outputEncoder.encode(output).byteLength;
|
|
501
|
+
if (outputBytes > limits.maxOutputBytes) {
|
|
502
|
+
failRun(new OutputLimitError(`${this.runtimeLabel} output exceeded the configured byte limit`, {
|
|
503
|
+
runtimeId: this.runtimeId,
|
|
504
|
+
phase: 'execute',
|
|
505
|
+
actual: outputBytes,
|
|
506
|
+
limit: limits.maxOutputBytes
|
|
507
|
+
}), true);
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
447
511
|
this.output?.(output);
|
|
512
|
+
}
|
|
448
513
|
if (!ownsRun())
|
|
449
514
|
return;
|
|
450
515
|
if (debugEvent)
|
|
@@ -502,6 +567,7 @@ class Python {
|
|
|
502
567
|
this.exit = true;
|
|
503
568
|
this.waitingForInput = false;
|
|
504
569
|
this.pendingEof = false;
|
|
570
|
+
this.resetInterpreterInput();
|
|
505
571
|
}
|
|
506
572
|
this.cleanupOperation(activeOperation);
|
|
507
573
|
});
|
|
@@ -2,6 +2,7 @@ import { type PlaygroundRuntimeAssets } from './assets';
|
|
|
2
2
|
import { type CompilerDiagnostic, type SandboxExecutionOptions } from './options';
|
|
3
3
|
import type { Sandbox, SandboxProgress } from './sandbox';
|
|
4
4
|
declare class Ruby implements Sandbox {
|
|
5
|
+
private readonly workerLanguage;
|
|
5
6
|
output: any;
|
|
6
7
|
worker?: Worker;
|
|
7
8
|
buffer: import("./sharedBuffer").WasmIdleSharedBuffer;
|
|
@@ -21,6 +22,11 @@ declare class Ruby implements Sandbox {
|
|
|
21
22
|
private disposePromise;
|
|
22
23
|
private readonly disposeCancellation;
|
|
23
24
|
private readonly workerSession;
|
|
25
|
+
constructor(workerLanguage?: 'ruby' | 'golfscript');
|
|
26
|
+
private get runtimeId();
|
|
27
|
+
private get runtimeLabel();
|
|
28
|
+
private get defaultSourcePath();
|
|
29
|
+
private validateRuntimeNamespace;
|
|
24
30
|
private beginOperation;
|
|
25
31
|
private isOperationActive;
|
|
26
32
|
private releaseOperation;
|
package/dist/playground/ruby.js
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
|
-
import { resolveRubyRuntimeAssetConfig } from './assets';
|
|
1
|
+
import { resolveRubyRuntimeAssetConfig, resolveGolfscriptRuntimeAssetConfig } from './assets';
|
|
2
|
+
import { GOLFSCRIPT_RUNTIME_DIRECTORY, WASM_GOLFSCRIPT_INTERPRETER_RECEIPT } from './wasmGolfscriptVersion';
|
|
2
3
|
import { createRubyRuntimeOwnedPreflightDelivery, preflightVerifiedRubyRuntimeAssets } from './rubyAssets';
|
|
3
4
|
import { resolveSandboxExecutionArgs } from './options';
|
|
4
5
|
import { flushBufferedEof, flushQueuedStdin, resetBufferedStdin } from './stdinBuffer';
|
|
5
6
|
import { createWasmIdleSharedBuffer } from './sharedBuffer';
|
|
6
7
|
import { WorkerSession } from './workerSession';
|
|
7
8
|
import { reportWorkerInputReady, reportWorkerProgress } from './workerProgress';
|
|
8
|
-
import { BusyError, AssetTooLargeError, CancelledError, DEFAULT_WORKSPACE_LIMITS, DiagnosticLimitError, OutputLimitError, RUBY_MAX_ASSET_BYTES, RUBY_MAX_MANIFEST_BYTES, RUBY_MAX_MODULE_BYTES, RuntimeConfigurationError, TimeoutError, resolveExecutionLimits, validateExecutionWorkspace } from '@wasm-idle/core';
|
|
9
|
+
import { BusyError, AssetTooLargeError, CancelledError, DEFAULT_WORKSPACE_LIMITS, DiagnosticLimitError, OutputLimitError, RUBY_MAX_ASSET_BYTES, RUBY_MAX_MANIFEST_BYTES, RUBY_MAX_MODULE_BYTES, RuntimeConfigurationError, TimeoutError, fetchPinnedRuntimeAsset, resolveExecutionLimits, validateExecutionWorkspace } from '@wasm-idle/core';
|
|
9
10
|
const OUTPUT_ENCODER = new TextEncoder();
|
|
10
|
-
const abortReason = (signal, phase) => signal.reason !== undefined
|
|
11
|
+
const abortReason = (signal, phase, label) => signal.reason !== undefined
|
|
11
12
|
? signal.reason
|
|
12
|
-
: new DOMException(phase === 'startup'
|
|
13
|
+
: new DOMException(phase === 'startup'
|
|
14
|
+
? `${label} runtime startup aborted`
|
|
15
|
+
: `${label} execution aborted`, 'AbortError');
|
|
13
16
|
class Ruby {
|
|
17
|
+
workerLanguage;
|
|
14
18
|
output = null;
|
|
15
19
|
worker;
|
|
16
20
|
buffer = createWasmIdleSharedBuffer(1024);
|
|
@@ -28,32 +32,57 @@ class Ruby {
|
|
|
28
32
|
activeOperation = null;
|
|
29
33
|
disposed = false;
|
|
30
34
|
disposePromise = null;
|
|
31
|
-
disposeCancellation
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
35
|
+
disposeCancellation;
|
|
36
|
+
workerSession;
|
|
37
|
+
constructor(workerLanguage = 'ruby') {
|
|
38
|
+
this.workerLanguage = workerLanguage;
|
|
39
|
+
this.disposeCancellation = new CancelledError(`${this.runtimeLabel} sandbox disposed`, {
|
|
40
|
+
phase: 'dispose',
|
|
41
|
+
runtimeId: this.runtimeId,
|
|
42
|
+
recoverable: false
|
|
43
|
+
});
|
|
44
|
+
this.workerSession = new WorkerSession({
|
|
45
|
+
label: this.runtimeLabel,
|
|
46
|
+
onDispose: (worker) => {
|
|
47
|
+
if (this.worker !== worker)
|
|
48
|
+
return;
|
|
49
|
+
delete this.worker;
|
|
50
|
+
this.exit = true;
|
|
51
|
+
this.waitingForInput = false;
|
|
52
|
+
this.pendingEof = false;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
get runtimeId() {
|
|
57
|
+
return this.workerLanguage === 'golfscript' ? 'GOLFSCRIPT' : 'RUBY';
|
|
58
|
+
}
|
|
59
|
+
get runtimeLabel() {
|
|
60
|
+
return this.workerLanguage === 'golfscript' ? 'GolfScript' : 'Ruby';
|
|
61
|
+
}
|
|
62
|
+
get defaultSourcePath() {
|
|
63
|
+
return this.workerLanguage === 'golfscript' ? 'main.gs' : 'main.rb';
|
|
64
|
+
}
|
|
65
|
+
validateRuntimeNamespace(activePath, files) {
|
|
66
|
+
if (this.workerLanguage !== 'golfscript')
|
|
67
|
+
return;
|
|
68
|
+
if ([activePath ?? this.defaultSourcePath, ...files.map((file) => file.path)].some((path) => path === GOLFSCRIPT_RUNTIME_DIRECTORY ||
|
|
69
|
+
path.startsWith(`${GOLFSCRIPT_RUNTIME_DIRECTORY}/`))) {
|
|
70
|
+
throw new RuntimeConfigurationError('GolfScript workspace conflicts with its private runtime mount', {
|
|
71
|
+
phase: 'configuration',
|
|
72
|
+
runtimeId: this.runtimeId
|
|
73
|
+
});
|
|
45
74
|
}
|
|
46
|
-
}
|
|
75
|
+
}
|
|
47
76
|
beginOperation(phase) {
|
|
48
77
|
if (this.disposed) {
|
|
49
|
-
throw new RuntimeConfigurationError(
|
|
78
|
+
throw new RuntimeConfigurationError(`${this.runtimeLabel} sandbox is disposed`, {
|
|
50
79
|
phase: 'dispose',
|
|
51
|
-
runtimeId:
|
|
80
|
+
runtimeId: this.runtimeId
|
|
52
81
|
});
|
|
53
82
|
}
|
|
54
83
|
if (this.activeOperation) {
|
|
55
|
-
throw new BusyError(
|
|
56
|
-
runtimeId:
|
|
84
|
+
throw new BusyError(`${this.runtimeLabel} runtime already has an active operation`, {
|
|
85
|
+
runtimeId: this.runtimeId,
|
|
57
86
|
phase: this.activeOperation.phase
|
|
58
87
|
});
|
|
59
88
|
}
|
|
@@ -115,7 +144,7 @@ class Ruby {
|
|
|
115
144
|
return;
|
|
116
145
|
let reason;
|
|
117
146
|
try {
|
|
118
|
-
reason = abortReason(signal, operation.phase);
|
|
147
|
+
reason = abortReason(signal, operation.phase, this.runtimeLabel);
|
|
119
148
|
}
|
|
120
149
|
catch (error) {
|
|
121
150
|
reason = error;
|
|
@@ -151,7 +180,7 @@ class Ruby {
|
|
|
151
180
|
return;
|
|
152
181
|
let reason;
|
|
153
182
|
try {
|
|
154
|
-
reason = abortReason(signal, operation.phase);
|
|
183
|
+
reason = abortReason(signal, operation.phase, this.runtimeLabel);
|
|
155
184
|
}
|
|
156
185
|
catch (error) {
|
|
157
186
|
reason = error;
|
|
@@ -185,9 +214,9 @@ class Ruby {
|
|
|
185
214
|
if (!this.isOperationActive(operation))
|
|
186
215
|
return;
|
|
187
216
|
const label = operation.phase === 'startup' ? 'startup' : 'execution';
|
|
188
|
-
this.cancelOperation(operation, new TimeoutError(
|
|
217
|
+
this.cancelOperation(operation, new TimeoutError(`${this.runtimeLabel} ${label} timed out after ${timeoutMs} ms`, {
|
|
189
218
|
phase: operation.phase,
|
|
190
|
-
runtimeId:
|
|
219
|
+
runtimeId: this.runtimeId,
|
|
191
220
|
timeoutMs
|
|
192
221
|
}));
|
|
193
222
|
}, timeoutMs);
|
|
@@ -252,6 +281,7 @@ class Ruby {
|
|
|
252
281
|
}
|
|
253
282
|
let limits;
|
|
254
283
|
let nextConfig;
|
|
284
|
+
let golfscriptConfig;
|
|
255
285
|
let startupContext;
|
|
256
286
|
let nextAssetKey;
|
|
257
287
|
let signal;
|
|
@@ -260,32 +290,37 @@ class Ruby {
|
|
|
260
290
|
signal = options.signal;
|
|
261
291
|
unbindPreSessionAbort = this.bindPreSessionAbort(activeOperation, signal);
|
|
262
292
|
if (!this.isOperationActive(activeOperation)) {
|
|
263
|
-
return Promise.reject(this.releaseBeforeSession(activeOperation,
|
|
293
|
+
return Promise.reject(this.releaseBeforeSession(activeOperation, `${this.runtimeLabel} runtime startup cancelled`));
|
|
264
294
|
}
|
|
265
295
|
limits = resolveExecutionLimits(options.limits);
|
|
266
296
|
if (!this.isOperationActive(activeOperation) || signal?.aborted) {
|
|
267
|
-
return Promise.reject(this.releaseBeforeSession(activeOperation,
|
|
297
|
+
return Promise.reject(this.releaseBeforeSession(activeOperation, `${this.runtimeLabel} runtime startup cancelled`));
|
|
268
298
|
}
|
|
269
|
-
const workspace = validateExecutionWorkspace(_code, options.workspaceFiles ?? [], options.activePath ??
|
|
299
|
+
const workspace = validateExecutionWorkspace(_code, options.workspaceFiles ?? [], options.activePath ?? this.defaultSourcePath, {
|
|
270
300
|
...options.workspaceLimits,
|
|
271
301
|
maxFileBytes: Math.min(options.workspaceLimits?.maxFileBytes ??
|
|
272
302
|
DEFAULT_WORKSPACE_LIMITS.maxFileBytes, limits.maxWorkspaceBytes),
|
|
273
303
|
maxTotalBytes: Math.min(options.workspaceLimits?.maxTotalBytes ??
|
|
274
304
|
DEFAULT_WORKSPACE_LIMITS.maxTotalBytes, limits.maxWorkspaceBytes)
|
|
275
305
|
});
|
|
306
|
+
this.validateRuntimeNamespace(workspace.activePath, workspace.workspaceFiles);
|
|
276
307
|
startupContext = {
|
|
277
|
-
args: [...resolveSandboxExecutionArgs(
|
|
278
|
-
activePath: workspace.activePath ??
|
|
308
|
+
args: [...resolveSandboxExecutionArgs(this.runtimeId, _args, options).programArgs],
|
|
309
|
+
activePath: workspace.activePath ?? this.defaultSourcePath,
|
|
279
310
|
workspaceFiles: workspace.workspaceFiles.map((file) => ({
|
|
280
311
|
path: file.path,
|
|
281
312
|
content: file.content
|
|
282
313
|
}))
|
|
283
314
|
};
|
|
284
315
|
if (!this.isOperationActive(activeOperation) || signal?.aborted) {
|
|
285
|
-
return Promise.reject(this.releaseBeforeSession(activeOperation,
|
|
316
|
+
return Promise.reject(this.releaseBeforeSession(activeOperation, `${this.runtimeLabel} runtime startup cancelled`));
|
|
286
317
|
}
|
|
287
318
|
const currentUrl = typeof window !== 'undefined' ? window.location.href : '';
|
|
288
319
|
nextConfig = resolveRubyRuntimeAssetConfig(runtimeAssets, currentUrl);
|
|
320
|
+
golfscriptConfig =
|
|
321
|
+
this.workerLanguage === 'golfscript'
|
|
322
|
+
? resolveGolfscriptRuntimeAssetConfig(runtimeAssets, currentUrl)
|
|
323
|
+
: undefined;
|
|
289
324
|
const effectiveMaxAssetBytes = Math.min(limits.maxAssetBytes, RUBY_MAX_ASSET_BYTES);
|
|
290
325
|
if (!nextConfig.splitStdlib) {
|
|
291
326
|
for (const [label, bytes, limit] of [
|
|
@@ -311,26 +346,30 @@ class Ruby {
|
|
|
311
346
|
]
|
|
312
347
|
]) {
|
|
313
348
|
if ((bytes ?? 0) > limit) {
|
|
314
|
-
throw new AssetTooLargeError(
|
|
349
|
+
throw new AssetTooLargeError(`${this.runtimeLabel} runtime ${label} exceeds the ${limit} byte limit`, {
|
|
315
350
|
actual: bytes,
|
|
316
351
|
limit,
|
|
317
352
|
phase: 'asset',
|
|
318
353
|
profileId: nextConfig.preflightProfile.profileId,
|
|
319
|
-
runtimeId:
|
|
354
|
+
runtimeId: this.runtimeId
|
|
320
355
|
});
|
|
321
356
|
}
|
|
322
357
|
}
|
|
323
358
|
}
|
|
324
|
-
nextAssetKey = JSON.stringify([
|
|
359
|
+
nextAssetKey = JSON.stringify([
|
|
360
|
+
nextConfig.preflightKey,
|
|
361
|
+
effectiveMaxAssetBytes,
|
|
362
|
+
...(golfscriptConfig ? [golfscriptConfig.assetKey] : [])
|
|
363
|
+
]);
|
|
325
364
|
if (!this.isOperationActive(activeOperation) || signal?.aborted) {
|
|
326
|
-
return Promise.reject(this.releaseBeforeSession(activeOperation,
|
|
365
|
+
return Promise.reject(this.releaseBeforeSession(activeOperation, `${this.runtimeLabel} runtime startup cancelled`));
|
|
327
366
|
}
|
|
328
367
|
}
|
|
329
368
|
catch (error) {
|
|
330
369
|
return Promise.reject(this.releaseBeforeSession(activeOperation, error));
|
|
331
370
|
}
|
|
332
371
|
if (!this.isOperationActive(activeOperation)) {
|
|
333
|
-
return Promise.reject(this.releaseBeforeSession(activeOperation,
|
|
372
|
+
return Promise.reject(this.releaseBeforeSession(activeOperation, `${this.runtimeLabel} runtime startup cancelled`));
|
|
334
373
|
}
|
|
335
374
|
unbindPreSessionAbort();
|
|
336
375
|
const loading = this.workerSession.load(async (resolve, reject) => {
|
|
@@ -364,7 +403,7 @@ class Ruby {
|
|
|
364
403
|
return;
|
|
365
404
|
preflightController.abort(activeOperation.cancellationReason ??
|
|
366
405
|
signal?.reason ??
|
|
367
|
-
new DOMException(
|
|
406
|
+
new DOMException(`${this.runtimeLabel} runtime preflight retired`, 'AbortError'));
|
|
368
407
|
});
|
|
369
408
|
const payload = await preflightVerifiedRubyRuntimeAssets(nextConfig, {
|
|
370
409
|
limits,
|
|
@@ -373,6 +412,15 @@ class Ruby {
|
|
|
373
412
|
progress?.set?.(totalBytes > 0 ? Math.min(0.9, (loadedBytes / totalBytes) * 0.9) : 0);
|
|
374
413
|
}
|
|
375
414
|
});
|
|
415
|
+
const interpreterBytes = golfscriptConfig
|
|
416
|
+
? Uint8Array.from(await fetchPinnedRuntimeAsset({
|
|
417
|
+
url: golfscriptConfig.interpreterUrl,
|
|
418
|
+
receipt: WASM_GOLFSCRIPT_INTERPRETER_RECEIPT,
|
|
419
|
+
maxAssetBytes: limits.maxAssetBytes,
|
|
420
|
+
persistentCache: options.persistentCache,
|
|
421
|
+
signal: preflightController.signal
|
|
422
|
+
}))
|
|
423
|
+
: undefined;
|
|
376
424
|
if (!this.isOperationActive(activeOperation) || signal?.aborted)
|
|
377
425
|
return;
|
|
378
426
|
const delivery = createRubyRuntimeOwnedPreflightDelivery(payload);
|
|
@@ -431,17 +479,20 @@ class Ruby {
|
|
|
431
479
|
const location = event.filename && event.lineno
|
|
432
480
|
? ` (${event.filename}:${event.lineno}:${event.colno})`
|
|
433
481
|
: '';
|
|
434
|
-
rejectOperation(
|
|
482
|
+
rejectOperation(`${this.runtimeLabel} worker script error: ${event.message || 'unknown error'}${location}`);
|
|
435
483
|
};
|
|
436
|
-
worker.onmessageerror = () => rejectOperation(
|
|
484
|
+
worker.onmessageerror = () => rejectOperation(`${this.runtimeLabel} worker message deserialization failed`);
|
|
437
485
|
const transferables = delivery.consume();
|
|
438
486
|
try {
|
|
439
487
|
worker.postMessage({
|
|
440
488
|
load: true,
|
|
441
489
|
runtimePreflight: delivery.payload,
|
|
442
490
|
startupContext,
|
|
443
|
-
maxAssetBytes: Math.min(limits.maxAssetBytes, RUBY_MAX_ASSET_BYTES)
|
|
444
|
-
|
|
491
|
+
maxAssetBytes: Math.min(limits.maxAssetBytes, RUBY_MAX_ASSET_BYTES),
|
|
492
|
+
...(interpreterBytes
|
|
493
|
+
? { language: 'golfscript', interpreterBytes }
|
|
494
|
+
: {})
|
|
495
|
+
}, [...transferables, ...(interpreterBytes ? [interpreterBytes.buffer] : [])]);
|
|
445
496
|
}
|
|
446
497
|
finally {
|
|
447
498
|
delivery.retire();
|
|
@@ -501,32 +552,33 @@ class Ruby {
|
|
|
501
552
|
signal = options.signal;
|
|
502
553
|
unbindPreSessionAbort = this.bindPreSessionAbort(activeOperation, signal);
|
|
503
554
|
if (!this.isOperationActive(activeOperation)) {
|
|
504
|
-
return Promise.reject(this.releaseBeforeSession(activeOperation,
|
|
555
|
+
return Promise.reject(this.releaseBeforeSession(activeOperation, `${this.runtimeLabel} execution cancelled`));
|
|
505
556
|
}
|
|
506
557
|
limits = resolveExecutionLimits(options.limits);
|
|
507
|
-
const workspace = validateExecutionWorkspace(code, options.workspaceFiles ?? [], options.activePath ??
|
|
558
|
+
const workspace = validateExecutionWorkspace(code, options.workspaceFiles ?? [], options.activePath ?? this.defaultSourcePath, {
|
|
508
559
|
...options.workspaceLimits,
|
|
509
560
|
maxFileBytes: Math.min(options.workspaceLimits?.maxFileBytes ??
|
|
510
561
|
DEFAULT_WORKSPACE_LIMITS.maxFileBytes, limits.maxWorkspaceBytes),
|
|
511
562
|
maxTotalBytes: Math.min(options.workspaceLimits?.maxTotalBytes ??
|
|
512
563
|
DEFAULT_WORKSPACE_LIMITS.maxTotalBytes, limits.maxWorkspaceBytes)
|
|
513
564
|
});
|
|
514
|
-
|
|
565
|
+
this.validateRuntimeNamespace(workspace.activePath, workspace.workspaceFiles);
|
|
566
|
+
const programArgs = resolveSandboxExecutionArgs(this.runtimeId, args, options).programArgs;
|
|
515
567
|
const stdin = options.stdin;
|
|
516
568
|
if (stdin !== undefined && typeof stdin !== 'string') {
|
|
517
|
-
throw new RuntimeConfigurationError(
|
|
569
|
+
throw new RuntimeConfigurationError(`${this.runtimeLabel} stdin must be a string`, {
|
|
518
570
|
phase: 'execute',
|
|
519
|
-
runtimeId:
|
|
571
|
+
runtimeId: this.runtimeId
|
|
520
572
|
});
|
|
521
573
|
}
|
|
522
574
|
request = {
|
|
523
575
|
programArgs,
|
|
524
576
|
stdin,
|
|
525
|
-
activePath: workspace.activePath ??
|
|
577
|
+
activePath: workspace.activePath ?? this.defaultSourcePath,
|
|
526
578
|
workspaceFiles: workspace.workspaceFiles
|
|
527
579
|
};
|
|
528
580
|
if (!this.isOperationActive(activeOperation) || signal?.aborted) {
|
|
529
|
-
return Promise.reject(this.releaseBeforeSession(activeOperation,
|
|
581
|
+
return Promise.reject(this.releaseBeforeSession(activeOperation, `${this.runtimeLabel} execution cancelled`));
|
|
530
582
|
}
|
|
531
583
|
unbindPreSessionAbort();
|
|
532
584
|
}
|
|
@@ -534,7 +586,7 @@ class Ruby {
|
|
|
534
586
|
return Promise.reject(this.releaseBeforeSession(activeOperation, error));
|
|
535
587
|
}
|
|
536
588
|
if (!this.isOperationActive(activeOperation)) {
|
|
537
|
-
return Promise.reject(this.releaseBeforeSession(activeOperation,
|
|
589
|
+
return Promise.reject(this.releaseBeforeSession(activeOperation, `${this.runtimeLabel} execution cancelled`));
|
|
538
590
|
}
|
|
539
591
|
const worker = this.worker;
|
|
540
592
|
if (!worker) {
|
|
@@ -603,7 +655,7 @@ class Ruby {
|
|
|
603
655
|
if (buffer && !hasExplicitStdin) {
|
|
604
656
|
this.waitingForInput = true;
|
|
605
657
|
if (!prepare) {
|
|
606
|
-
reportWorkerInputReady(_prog,
|
|
658
|
+
reportWorkerInputReady(_prog, `${this.runtimeLabel} runtime ready for input`);
|
|
607
659
|
if (!ownsRun())
|
|
608
660
|
return;
|
|
609
661
|
}
|
|
@@ -617,11 +669,11 @@ class Ruby {
|
|
|
617
669
|
if (typeof output === 'string' && output.length > 0) {
|
|
618
670
|
const actual = outputBytes + OUTPUT_ENCODER.encode(output).byteLength;
|
|
619
671
|
if (actual > limits.maxOutputBytes) {
|
|
620
|
-
failRun(new OutputLimitError(
|
|
672
|
+
failRun(new OutputLimitError(`${this.runtimeLabel} output exceeded ${limits.maxOutputBytes} bytes`, {
|
|
621
673
|
actual,
|
|
622
674
|
limit: limits.maxOutputBytes,
|
|
623
675
|
phase: 'execute',
|
|
624
|
-
runtimeId:
|
|
676
|
+
runtimeId: this.runtimeId
|
|
625
677
|
}), true);
|
|
626
678
|
return;
|
|
627
679
|
}
|
|
@@ -633,11 +685,11 @@ class Ruby {
|
|
|
633
685
|
if (diagnostic !== undefined) {
|
|
634
686
|
const actual = diagnosticCount + 1;
|
|
635
687
|
if (actual > limits.maxDiagnostics) {
|
|
636
|
-
failRun(new DiagnosticLimitError(
|
|
688
|
+
failRun(new DiagnosticLimitError(`${this.runtimeLabel} diagnostics exceeded ${limits.maxDiagnostics} messages`, {
|
|
637
689
|
actual,
|
|
638
690
|
limit: limits.maxDiagnostics,
|
|
639
691
|
phase: 'execute',
|
|
640
|
-
runtimeId:
|
|
692
|
+
runtimeId: this.runtimeId
|
|
641
693
|
}), true);
|
|
642
694
|
return;
|
|
643
695
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type WasiInterpreterProfile = {
|
|
2
|
+
readonly id: string;
|
|
3
|
+
readonly folder: string;
|
|
4
|
+
readonly fileName: string;
|
|
5
|
+
readonly sourcePath: string;
|
|
6
|
+
readonly sourcePathPrefix?: string;
|
|
7
|
+
readonly maxSourcePathBytes?: number;
|
|
8
|
+
readonly maxSourceBytes?: number;
|
|
9
|
+
readonly minSourceCharacters?: number;
|
|
10
|
+
readonly command: string;
|
|
11
|
+
readonly args: readonly string[];
|
|
12
|
+
readonly sha256: string;
|
|
13
|
+
readonly bytes: number;
|
|
14
|
+
};
|
|
15
|
+
export declare const WASI_INTERPRETERS: Readonly<Record<string, WasiInterpreterProfile>>;
|