wasm-idle 1.0.7 → 1.0.9
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 +48 -11
- package/dist/playground/applicationAssets.js +18 -0
- package/dist/playground/assetBridge.js +7 -0
- package/dist/playground/assets.d.ts +70 -0
- package/dist/playground/assets.js +120 -0
- package/dist/playground/cachedClang.js +43 -5
- package/dist/playground/clang.d.ts +5 -0
- package/dist/playground/clang.js +96 -0
- package/dist/playground/clangAssetIntegrity.d.ts +6 -0
- package/dist/playground/clangAssetIntegrity.js +3 -2
- package/dist/playground/clangWorkerProtocol.d.ts +12 -1
- package/dist/playground/fennel.d.ts +9 -0
- package/dist/playground/fennel.js +30 -0
- package/dist/playground/fennelBootstrap.d.ts +31 -0
- package/dist/playground/fennelBootstrap.js +118 -0
- package/dist/playground/grain.d.ts +9 -0
- package/dist/playground/grain.js +127 -0
- package/dist/playground/hy.d.ts +21 -0
- package/dist/playground/hy.js +24 -0
- package/dist/playground/index.d.ts +1 -1
- package/dist/playground/index.js +49 -0
- package/dist/playground/lua.d.ts +19 -0
- package/dist/playground/lua.js +64 -39
- package/dist/playground/objectivec.d.ts +3 -1
- package/dist/playground/objectivec.js +6 -2
- package/dist/playground/options.js +1 -1
- package/dist/playground/postgresql.d.ts +36 -0
- package/dist/playground/postgresql.js +543 -0
- package/dist/playground/python.d.ts +5 -0
- package/dist/playground/python.js +13 -0
- package/dist/playground/rescript.d.ts +5 -0
- package/dist/playground/rescript.js +58 -0
- package/dist/playground/rescriptPreflight.d.ts +26 -0
- package/dist/playground/rescriptPreflight.js +381 -0
- package/dist/playground/staticRuntimeModuleVersion.d.ts +1 -1
- package/dist/playground/staticRuntimeModuleVersion.js +1 -1
- package/dist/playground/staticRuntimePreflightExecute.d.ts +2 -1
- package/dist/playground/staticRuntimePreflightExecute.js +9 -0
- package/dist/playground/staticRuntimePreflightProtocol.d.ts +1 -1
- package/dist/playground/v.d.ts +46 -0
- package/dist/playground/v.js +703 -0
- package/dist/playground/wasmFennelVersion.d.ts +23 -0
- package/dist/playground/wasmFennelVersion.js +16 -0
- package/dist/playground/wasmGrainVersion.d.ts +24 -0
- package/dist/playground/wasmGrainVersion.js +25 -0
- package/dist/playground/wasmHyVersion.d.ts +7 -0
- package/dist/playground/wasmHyVersion.js +15 -0
- package/dist/playground/wasmReScriptVersion.d.ts +20 -0
- package/dist/playground/wasmReScriptVersion.js +20 -0
- package/dist/playground/worker/cachedClangCompile.js +37 -4
- package/dist/playground/worker/clang.js +35 -7
- package/dist/playground/worker/lua.js +48 -11
- package/dist/playground/worker/postgresql.d.ts +1 -0
- package/dist/playground/worker/postgresql.js +205 -0
- package/dist/playground/worker/python.js +106 -9
- package/dist/playground/worker/staticRuntimePreflight.js +2 -1
- package/dist/playground/worker/v.d.ts +1 -0
- package/dist/playground/worker/v.js +106 -0
- package/package.json +18 -7
|
@@ -0,0 +1,703 @@
|
|
|
1
|
+
import { WorkerAssetBridge } from './assetBridge';
|
|
2
|
+
import { resolveVBaseUrl, resolveRuntimeAssetConfig } from './assets';
|
|
3
|
+
import { BusyError, CancelledError, DEFAULT_WORKSPACE_LIMITS, OutputLimitError, RuntimeConfigurationError, TimeoutError, resolveExecutionLimits, validateExecutionWorkspace } from '@wasm-idle/core';
|
|
4
|
+
import { resolveSandboxExecutionArgs } from './options';
|
|
5
|
+
import { createWasmIdleSharedBuffer } from './sharedBuffer';
|
|
6
|
+
import { WorkerSession } from './workerSession';
|
|
7
|
+
import { reportWorkerInputReady } from './workerProgress';
|
|
8
|
+
import { flushBufferedEof, flushQueuedStdin, resetBufferedStdin } from './stdinBuffer';
|
|
9
|
+
const OUTPUT_ENCODER = new TextEncoder();
|
|
10
|
+
const abortReason = (signal, phase) => signal.reason !== undefined
|
|
11
|
+
? signal.reason
|
|
12
|
+
: new DOMException(phase === 'startup' ? 'V runtime startup aborted' : 'V execution aborted', 'AbortError');
|
|
13
|
+
class VLang {
|
|
14
|
+
language = 'V';
|
|
15
|
+
output;
|
|
16
|
+
worker = null;
|
|
17
|
+
buffer = createWasmIdleSharedBuffer(4096);
|
|
18
|
+
pendingInput = [];
|
|
19
|
+
begin = 0;
|
|
20
|
+
elapse = 0;
|
|
21
|
+
uid = 0;
|
|
22
|
+
log = true;
|
|
23
|
+
waitingForInput = false;
|
|
24
|
+
pendingEof = false;
|
|
25
|
+
exit = true;
|
|
26
|
+
assetBridge = null;
|
|
27
|
+
activeVBaseUrl = '';
|
|
28
|
+
activeOperation = null;
|
|
29
|
+
disposed = false;
|
|
30
|
+
disposePromise = null;
|
|
31
|
+
disposeCancellation = new CancelledError('V sandbox disposed', {
|
|
32
|
+
phase: 'dispose',
|
|
33
|
+
runtimeId: 'V',
|
|
34
|
+
recoverable: false
|
|
35
|
+
});
|
|
36
|
+
workerSession = new WorkerSession({
|
|
37
|
+
label: 'V',
|
|
38
|
+
onDispose: (worker) => {
|
|
39
|
+
if (this.worker === worker) {
|
|
40
|
+
const assetBridge = this.assetBridge;
|
|
41
|
+
delete this.worker;
|
|
42
|
+
this.assetBridge = null;
|
|
43
|
+
this.activeVBaseUrl = '';
|
|
44
|
+
try {
|
|
45
|
+
assetBridge?.dispose();
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// Asset cleanup must not replace the worker operation result.
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
this.exit = true;
|
|
52
|
+
this.waitingForInput = false;
|
|
53
|
+
this.pendingEof = false;
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
beginOperation(phase) {
|
|
57
|
+
if (this.disposed) {
|
|
58
|
+
throw new RuntimeConfigurationError('V sandbox is disposed', {
|
|
59
|
+
phase: 'dispose',
|
|
60
|
+
runtimeId: 'V'
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
if (this.activeOperation) {
|
|
64
|
+
throw new BusyError('V runtime already has an active operation', {
|
|
65
|
+
runtimeId: 'V',
|
|
66
|
+
phase: this.activeOperation.phase
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
const operation = {
|
|
70
|
+
token: Symbol(phase),
|
|
71
|
+
phase,
|
|
72
|
+
cancelled: false,
|
|
73
|
+
cleanedUp: false,
|
|
74
|
+
cleanups: [],
|
|
75
|
+
explicitStdin: false
|
|
76
|
+
};
|
|
77
|
+
this.activeOperation = operation;
|
|
78
|
+
return operation;
|
|
79
|
+
}
|
|
80
|
+
isOperationActive(operation) {
|
|
81
|
+
return this.activeOperation?.token === operation.token && !operation.cancelled;
|
|
82
|
+
}
|
|
83
|
+
releaseOperation(operation) {
|
|
84
|
+
if (this.activeOperation?.token !== operation.token)
|
|
85
|
+
return false;
|
|
86
|
+
this.activeOperation = null;
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
cleanupOperation(operation) {
|
|
90
|
+
if (operation.cleanedUp)
|
|
91
|
+
return;
|
|
92
|
+
operation.cleanedUp = true;
|
|
93
|
+
const cleanups = operation.cleanups.splice(0);
|
|
94
|
+
for (const cleanup of cleanups) {
|
|
95
|
+
try {
|
|
96
|
+
cleanup();
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// Caller-owned lifecycle cleanup must not replace the operation result.
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
releaseBeforeSession(operation, reason) {
|
|
104
|
+
const outcome = operation.cancelled ? operation.cancellationReason : reason;
|
|
105
|
+
this.releaseOperation(operation);
|
|
106
|
+
this.cleanupOperation(operation);
|
|
107
|
+
return outcome;
|
|
108
|
+
}
|
|
109
|
+
bindPreSessionAbort(operation, signal) {
|
|
110
|
+
if (!signal || !this.isOperationActive(operation))
|
|
111
|
+
return () => undefined;
|
|
112
|
+
let registered = false;
|
|
113
|
+
let unbound = false;
|
|
114
|
+
const unbind = () => {
|
|
115
|
+
if (unbound)
|
|
116
|
+
return;
|
|
117
|
+
unbound = true;
|
|
118
|
+
if (registered)
|
|
119
|
+
signal.removeEventListener('abort', onAbort);
|
|
120
|
+
};
|
|
121
|
+
const onAbort = () => {
|
|
122
|
+
if (!this.isOperationActive(operation))
|
|
123
|
+
return;
|
|
124
|
+
let reason;
|
|
125
|
+
try {
|
|
126
|
+
reason = abortReason(signal, operation.phase);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
reason = error;
|
|
130
|
+
}
|
|
131
|
+
operation.cancelled = true;
|
|
132
|
+
operation.cancellationReason = reason;
|
|
133
|
+
this.releaseOperation(operation);
|
|
134
|
+
this.cleanupOperation(operation);
|
|
135
|
+
};
|
|
136
|
+
operation.cleanups.push(unbind);
|
|
137
|
+
try {
|
|
138
|
+
registered = true;
|
|
139
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
140
|
+
if (signal.aborted)
|
|
141
|
+
onAbort();
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
if (this.isOperationActive(operation)) {
|
|
145
|
+
operation.cancelled = true;
|
|
146
|
+
operation.cancellationReason = error;
|
|
147
|
+
this.releaseOperation(operation);
|
|
148
|
+
this.cleanupOperation(operation);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return unbind;
|
|
152
|
+
}
|
|
153
|
+
bindAbortSignal(operation, signal) {
|
|
154
|
+
if (!signal || !this.isOperationActive(operation))
|
|
155
|
+
return;
|
|
156
|
+
let registered = false;
|
|
157
|
+
const onAbort = () => {
|
|
158
|
+
if (!this.isOperationActive(operation))
|
|
159
|
+
return;
|
|
160
|
+
let reason;
|
|
161
|
+
try {
|
|
162
|
+
reason = abortReason(signal, operation.phase);
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
reason = error;
|
|
166
|
+
}
|
|
167
|
+
this.cancelOperation(operation, reason);
|
|
168
|
+
};
|
|
169
|
+
operation.cleanups.push(() => {
|
|
170
|
+
if (!registered)
|
|
171
|
+
return;
|
|
172
|
+
try {
|
|
173
|
+
signal.removeEventListener('abort', onAbort);
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
// Listener cleanup must not replace the operation result.
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
try {
|
|
180
|
+
registered = true;
|
|
181
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
182
|
+
if (signal.aborted)
|
|
183
|
+
onAbort();
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
this.cancelOperation(operation, error);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
bindOperationTimeout(operation, timeoutMs) {
|
|
190
|
+
if (!this.isOperationActive(operation))
|
|
191
|
+
return;
|
|
192
|
+
let timeout;
|
|
193
|
+
operation.cleanups.push(() => {
|
|
194
|
+
if (timeout !== undefined)
|
|
195
|
+
clearTimeout(timeout);
|
|
196
|
+
});
|
|
197
|
+
try {
|
|
198
|
+
timeout = setTimeout(() => {
|
|
199
|
+
if (!this.isOperationActive(operation))
|
|
200
|
+
return;
|
|
201
|
+
const label = operation.phase === 'startup' ? 'startup' : 'execution';
|
|
202
|
+
this.cancelOperation(operation, new TimeoutError(`V ${label} timed out after ${timeoutMs} ms`, {
|
|
203
|
+
phase: operation.phase,
|
|
204
|
+
runtimeId: 'V',
|
|
205
|
+
timeoutMs
|
|
206
|
+
}));
|
|
207
|
+
}, timeoutMs);
|
|
208
|
+
if (operation.cleanedUp)
|
|
209
|
+
clearTimeout(timeout);
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
this.cancelOperation(operation, error);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
resetExplicitStdinState() {
|
|
216
|
+
this.pendingInput = [];
|
|
217
|
+
this.pendingEof = false;
|
|
218
|
+
this.waitingForInput = false;
|
|
219
|
+
try {
|
|
220
|
+
resetBufferedStdin(this.buffer);
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
// Explicit stdin never consumes the shared terminal buffer.
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
finishExplicitStdin(operation) {
|
|
227
|
+
if (!operation.explicitStdin)
|
|
228
|
+
return;
|
|
229
|
+
operation.explicitStdin = false;
|
|
230
|
+
this.resetExplicitStdinState();
|
|
231
|
+
}
|
|
232
|
+
load(runtimeAssets = '', code = '', log = true, args = [], options = {}, progress) {
|
|
233
|
+
let operation;
|
|
234
|
+
try {
|
|
235
|
+
operation = this.beginOperation('startup');
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
return Promise.reject(error);
|
|
239
|
+
}
|
|
240
|
+
let signal;
|
|
241
|
+
let limits;
|
|
242
|
+
let unbindPreSessionAbort = () => undefined;
|
|
243
|
+
try {
|
|
244
|
+
signal = options.signal;
|
|
245
|
+
unbindPreSessionAbort = this.bindPreSessionAbort(operation, signal);
|
|
246
|
+
if (!this.isOperationActive(operation) || signal?.aborted) {
|
|
247
|
+
return Promise.reject(this.releaseBeforeSession(operation, 'V runtime startup cancelled'));
|
|
248
|
+
}
|
|
249
|
+
limits = resolveExecutionLimits(options.limits);
|
|
250
|
+
if (!this.isOperationActive(operation) || signal?.aborted) {
|
|
251
|
+
return Promise.reject(this.releaseBeforeSession(operation, 'V runtime startup cancelled'));
|
|
252
|
+
}
|
|
253
|
+
unbindPreSessionAbort();
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
return Promise.reject(this.releaseBeforeSession(operation, error));
|
|
257
|
+
}
|
|
258
|
+
if (!this.isOperationActive(operation)) {
|
|
259
|
+
return Promise.reject(this.releaseBeforeSession(operation, 'V runtime startup cancelled'));
|
|
260
|
+
}
|
|
261
|
+
const loading = this.workerSession.load(async (resolve, reject) => {
|
|
262
|
+
const resolveOperation = () => {
|
|
263
|
+
if (!this.releaseOperation(operation))
|
|
264
|
+
return;
|
|
265
|
+
resolve();
|
|
266
|
+
this.cleanupOperation(operation);
|
|
267
|
+
};
|
|
268
|
+
const rejectOperation = (reason) => {
|
|
269
|
+
if (!this.releaseOperation(operation))
|
|
270
|
+
return;
|
|
271
|
+
reject(reason);
|
|
272
|
+
this.cleanupOperation(operation);
|
|
273
|
+
};
|
|
274
|
+
try {
|
|
275
|
+
if (!this.isOperationActive(operation))
|
|
276
|
+
return;
|
|
277
|
+
this.pendingInput = [];
|
|
278
|
+
this.waitingForInput = false;
|
|
279
|
+
this.pendingEof = false;
|
|
280
|
+
const currentUrl = typeof window !== 'undefined' ? window.location.href : '';
|
|
281
|
+
const clangAssets = resolveRuntimeAssetConfig('clang', runtimeAssets, currentUrl);
|
|
282
|
+
if (!this.isOperationActive(operation))
|
|
283
|
+
return;
|
|
284
|
+
const vBaseUrl = resolveVBaseUrl(runtimeAssets, currentUrl);
|
|
285
|
+
if (!this.isOperationActive(operation))
|
|
286
|
+
return;
|
|
287
|
+
const needsWorkerReset = !this.worker ||
|
|
288
|
+
!this.assetBridge ||
|
|
289
|
+
!this.assetBridge.matches(clangAssets, limits.maxAssetBytes) ||
|
|
290
|
+
this.activeVBaseUrl !== vBaseUrl;
|
|
291
|
+
if (needsWorkerReset && this.worker) {
|
|
292
|
+
this.workerSession.reset();
|
|
293
|
+
}
|
|
294
|
+
if (!this.worker) {
|
|
295
|
+
const WorkerConstructor = (await import('./worker/v?worker'))
|
|
296
|
+
.default;
|
|
297
|
+
if (!this.isOperationActive(operation))
|
|
298
|
+
return;
|
|
299
|
+
const worker = new WorkerConstructor();
|
|
300
|
+
if (!this.isOperationActive(operation)) {
|
|
301
|
+
worker.terminate();
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
let assetBridge;
|
|
305
|
+
try {
|
|
306
|
+
assetBridge = new WorkerAssetBridge(worker, 'clang', clangAssets, progress, limits.maxAssetBytes);
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
try {
|
|
310
|
+
worker.terminate();
|
|
311
|
+
}
|
|
312
|
+
catch {
|
|
313
|
+
// Worker cleanup must not replace the progress callback error.
|
|
314
|
+
}
|
|
315
|
+
throw error;
|
|
316
|
+
}
|
|
317
|
+
if (!this.isOperationActive(operation)) {
|
|
318
|
+
try {
|
|
319
|
+
assetBridge.dispose();
|
|
320
|
+
}
|
|
321
|
+
catch {
|
|
322
|
+
// Stale asset cleanup is best effort.
|
|
323
|
+
}
|
|
324
|
+
try {
|
|
325
|
+
worker.terminate();
|
|
326
|
+
}
|
|
327
|
+
catch {
|
|
328
|
+
// The unattached worker is already detached.
|
|
329
|
+
}
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
this.worker = worker;
|
|
333
|
+
this.assetBridge = assetBridge;
|
|
334
|
+
this.workerSession.attach(worker);
|
|
335
|
+
let handler;
|
|
336
|
+
const ownsWorker = () => this.worker === worker &&
|
|
337
|
+
this.assetBridge === assetBridge &&
|
|
338
|
+
worker.onmessage === handler;
|
|
339
|
+
const ownsLoad = () => this.isOperationActive(operation) && ownsWorker();
|
|
340
|
+
handler = (event) => {
|
|
341
|
+
if (!ownsWorker())
|
|
342
|
+
return;
|
|
343
|
+
try {
|
|
344
|
+
if (assetBridge.handleMessage(event))
|
|
345
|
+
return;
|
|
346
|
+
if (!ownsLoad())
|
|
347
|
+
return;
|
|
348
|
+
if (event.data?.progress != null) {
|
|
349
|
+
progress?.set?.(event.data.progress);
|
|
350
|
+
if (!ownsLoad())
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
if (event.data?.load) {
|
|
354
|
+
this.log = log;
|
|
355
|
+
this.activeVBaseUrl = vBaseUrl;
|
|
356
|
+
resolveOperation();
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
if (event.data?.error !== undefined)
|
|
360
|
+
rejectOperation(event.data.error);
|
|
361
|
+
}
|
|
362
|
+
catch (error) {
|
|
363
|
+
rejectOperation(error);
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
worker.onmessage = handler;
|
|
367
|
+
worker.postMessage({
|
|
368
|
+
load: true,
|
|
369
|
+
log,
|
|
370
|
+
code,
|
|
371
|
+
args,
|
|
372
|
+
clangAssets: {
|
|
373
|
+
baseUrl: clangAssets.baseUrl,
|
|
374
|
+
maxAssetBytes: limits.maxAssetBytes,
|
|
375
|
+
useAssetBridge: clangAssets.useAssetBridge
|
|
376
|
+
},
|
|
377
|
+
vBaseUrl,
|
|
378
|
+
maxAssetBytes: limits.maxAssetBytes
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
const worker = this.worker;
|
|
383
|
+
const assetBridge = this.assetBridge;
|
|
384
|
+
if (!assetBridge) {
|
|
385
|
+
return rejectOperation('V asset bridge is not loaded');
|
|
386
|
+
}
|
|
387
|
+
assetBridge.rebind(worker, clangAssets, progress, limits.maxAssetBytes);
|
|
388
|
+
if (!this.isOperationActive(operation) ||
|
|
389
|
+
this.worker !== worker ||
|
|
390
|
+
this.assetBridge !== assetBridge) {
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
worker.postMessage({ log });
|
|
394
|
+
this.log = log;
|
|
395
|
+
this.activeVBaseUrl = vBaseUrl;
|
|
396
|
+
resolveOperation();
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
catch (error) {
|
|
400
|
+
rejectOperation(error);
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
const timeoutMs = Math.min(2_147_483_647, limits.assetTimeoutMs + limits.startupTimeoutMs);
|
|
404
|
+
this.bindOperationTimeout(operation, timeoutMs);
|
|
405
|
+
this.bindAbortSignal(operation, signal);
|
|
406
|
+
return loading.finally(() => {
|
|
407
|
+
this.releaseOperation(operation);
|
|
408
|
+
this.cleanupOperation(operation);
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
write(input) {
|
|
412
|
+
if (this.disposed)
|
|
413
|
+
return;
|
|
414
|
+
this.pendingInput.push(input);
|
|
415
|
+
this.pendingEof = false;
|
|
416
|
+
this.flushPendingInput();
|
|
417
|
+
}
|
|
418
|
+
eof() {
|
|
419
|
+
if (this.disposed)
|
|
420
|
+
return;
|
|
421
|
+
this.pendingEof = true;
|
|
422
|
+
this.flushPendingInput();
|
|
423
|
+
}
|
|
424
|
+
flushPendingInput() {
|
|
425
|
+
if (!this.waitingForInput)
|
|
426
|
+
return;
|
|
427
|
+
if (flushQueuedStdin(this.pendingInput, this.buffer)) {
|
|
428
|
+
this.waitingForInput = false;
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
if (this.pendingEof) {
|
|
432
|
+
flushBufferedEof(this.buffer);
|
|
433
|
+
this.pendingEof = false;
|
|
434
|
+
this.waitingForInput = false;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
async run(code, prepare, log = this.log, prog, args = [], options = {}) {
|
|
438
|
+
let activeOperation;
|
|
439
|
+
try {
|
|
440
|
+
activeOperation = this.beginOperation('execute');
|
|
441
|
+
}
|
|
442
|
+
catch (error) {
|
|
443
|
+
return Promise.reject(error);
|
|
444
|
+
}
|
|
445
|
+
let signal;
|
|
446
|
+
let unbindPreSessionAbort = () => undefined;
|
|
447
|
+
let worker;
|
|
448
|
+
let assetBridge;
|
|
449
|
+
let compileArgs;
|
|
450
|
+
let programArgs;
|
|
451
|
+
let limits;
|
|
452
|
+
let workspace;
|
|
453
|
+
let stdin;
|
|
454
|
+
try {
|
|
455
|
+
const debug = options.debug;
|
|
456
|
+
if (!this.isOperationActive(activeOperation)) {
|
|
457
|
+
return Promise.reject(this.releaseBeforeSession(activeOperation, 'V execution cancelled'));
|
|
458
|
+
}
|
|
459
|
+
if (debug) {
|
|
460
|
+
return Promise.reject(this.releaseBeforeSession(activeOperation, 'V debugging is not supported yet.'));
|
|
461
|
+
}
|
|
462
|
+
signal = options.signal;
|
|
463
|
+
unbindPreSessionAbort = this.bindPreSessionAbort(activeOperation, signal);
|
|
464
|
+
if (!this.isOperationActive(activeOperation) || signal?.aborted) {
|
|
465
|
+
return Promise.reject(this.releaseBeforeSession(activeOperation, 'V execution cancelled'));
|
|
466
|
+
}
|
|
467
|
+
const currentWorker = this.worker;
|
|
468
|
+
worker = currentWorker;
|
|
469
|
+
if (!worker)
|
|
470
|
+
throw 'Worker not loaded';
|
|
471
|
+
const currentAssetBridge = this.assetBridge;
|
|
472
|
+
assetBridge = currentAssetBridge;
|
|
473
|
+
if (!assetBridge)
|
|
474
|
+
throw 'V asset bridge is not loaded';
|
|
475
|
+
({ compileArgs, programArgs } = resolveSandboxExecutionArgs(this.language, args, options));
|
|
476
|
+
limits = resolveExecutionLimits(options.limits);
|
|
477
|
+
const workspaceFiles = options.workspaceFiles ?? [];
|
|
478
|
+
const activePath = options.activePath ?? 'main.v';
|
|
479
|
+
const workspaceLimits = options.workspaceLimits;
|
|
480
|
+
workspace = validateExecutionWorkspace(code, workspaceFiles, activePath, {
|
|
481
|
+
...workspaceLimits,
|
|
482
|
+
maxFileBytes: Math.min(workspaceLimits?.maxFileBytes ?? DEFAULT_WORKSPACE_LIMITS.maxFileBytes, limits.maxWorkspaceBytes),
|
|
483
|
+
maxTotalBytes: Math.min(workspaceLimits?.maxTotalBytes ?? DEFAULT_WORKSPACE_LIMITS.maxTotalBytes, limits.maxWorkspaceBytes)
|
|
484
|
+
});
|
|
485
|
+
stdin = options.stdin;
|
|
486
|
+
if (!this.isOperationActive(activeOperation) || signal?.aborted) {
|
|
487
|
+
return Promise.reject(this.releaseBeforeSession(activeOperation, 'V execution cancelled'));
|
|
488
|
+
}
|
|
489
|
+
unbindPreSessionAbort();
|
|
490
|
+
}
|
|
491
|
+
catch (error) {
|
|
492
|
+
return Promise.reject(this.releaseBeforeSession(activeOperation, error));
|
|
493
|
+
}
|
|
494
|
+
if (!this.isOperationActive(activeOperation)) {
|
|
495
|
+
return Promise.reject(this.releaseBeforeSession(activeOperation, 'V execution cancelled'));
|
|
496
|
+
}
|
|
497
|
+
const hasExplicitStdin = stdin !== undefined;
|
|
498
|
+
if (hasExplicitStdin) {
|
|
499
|
+
activeOperation.explicitStdin = true;
|
|
500
|
+
this.resetExplicitStdinState();
|
|
501
|
+
}
|
|
502
|
+
this.exit = false;
|
|
503
|
+
try {
|
|
504
|
+
return await new Promise((resolve, reject) => {
|
|
505
|
+
let outputBytes = 0;
|
|
506
|
+
const workerOperation = this.workerSession.beginRun(worker, reject);
|
|
507
|
+
const timeoutMs = Math.min(2_147_483_647, limits.compileTimeoutMs + limits.runTimeoutMs);
|
|
508
|
+
this.bindOperationTimeout(activeOperation, timeoutMs);
|
|
509
|
+
this.bindAbortSignal(activeOperation, signal);
|
|
510
|
+
if (!this.isOperationActive(activeOperation))
|
|
511
|
+
return;
|
|
512
|
+
const runUid = ++this.uid;
|
|
513
|
+
let handler;
|
|
514
|
+
const ownsWorker = () => this.worker === worker &&
|
|
515
|
+
this.assetBridge === assetBridge &&
|
|
516
|
+
worker.onmessage === handler;
|
|
517
|
+
const ownsRun = () => this.isOperationActive(activeOperation) && ownsWorker() && runUid === this.uid;
|
|
518
|
+
const claimRun = () => {
|
|
519
|
+
if (!ownsRun())
|
|
520
|
+
return false;
|
|
521
|
+
this.finishExplicitStdin(activeOperation);
|
|
522
|
+
this.workerSession.complete(workerOperation);
|
|
523
|
+
this.releaseOperation(activeOperation);
|
|
524
|
+
this.cleanupOperation(activeOperation);
|
|
525
|
+
this.elapse = Date.now() - this.begin;
|
|
526
|
+
this.exit = true;
|
|
527
|
+
this.waitingForInput = false;
|
|
528
|
+
this.pendingEof = false;
|
|
529
|
+
return true;
|
|
530
|
+
};
|
|
531
|
+
const failRun = (error, disposeWorker = false) => {
|
|
532
|
+
if (!ownsRun())
|
|
533
|
+
return;
|
|
534
|
+
if (disposeWorker) {
|
|
535
|
+
this.cancelOperation(activeOperation, error);
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
if (!claimRun())
|
|
539
|
+
return;
|
|
540
|
+
reject(error);
|
|
541
|
+
};
|
|
542
|
+
handler = (event) => {
|
|
543
|
+
if (!ownsWorker())
|
|
544
|
+
return;
|
|
545
|
+
try {
|
|
546
|
+
if (assetBridge.handleMessage(event))
|
|
547
|
+
return;
|
|
548
|
+
if (!ownsRun())
|
|
549
|
+
return;
|
|
550
|
+
const { output, results, log: workerLog, error, buffer, progress } = event.data;
|
|
551
|
+
if (buffer && !hasExplicitStdin) {
|
|
552
|
+
this.waitingForInput = true;
|
|
553
|
+
if (!prepare) {
|
|
554
|
+
reportWorkerInputReady(prog, 'V runtime ready for input');
|
|
555
|
+
if (!ownsRun())
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
this.flushPendingInput();
|
|
559
|
+
if (!ownsRun())
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
if (output) {
|
|
563
|
+
const actual = outputBytes + OUTPUT_ENCODER.encode(String(output)).byteLength;
|
|
564
|
+
if (actual > limits.maxOutputBytes) {
|
|
565
|
+
failRun(new OutputLimitError(`V output exceeded ${limits.maxOutputBytes} bytes`, {
|
|
566
|
+
actual,
|
|
567
|
+
limit: limits.maxOutputBytes,
|
|
568
|
+
phase: 'execute',
|
|
569
|
+
runtimeId: 'V'
|
|
570
|
+
}), true);
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
outputBytes = actual;
|
|
574
|
+
this.output?.(output);
|
|
575
|
+
if (!ownsRun())
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
if (results !== undefined) {
|
|
579
|
+
if (!claimRun())
|
|
580
|
+
return;
|
|
581
|
+
resolve(results);
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
if (workerLog) {
|
|
585
|
+
console.log(workerLog);
|
|
586
|
+
if (!ownsRun())
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
if (error !== undefined) {
|
|
590
|
+
failRun(error);
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
if (progress != null) {
|
|
594
|
+
prog?.set?.(progress);
|
|
595
|
+
if (!ownsRun())
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
catch (error) {
|
|
600
|
+
failRun(error, true);
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
worker.onmessage = handler;
|
|
604
|
+
if (!ownsRun())
|
|
605
|
+
return;
|
|
606
|
+
this.begin = Date.now();
|
|
607
|
+
try {
|
|
608
|
+
worker.postMessage({
|
|
609
|
+
code,
|
|
610
|
+
prepare,
|
|
611
|
+
buffer: this.buffer,
|
|
612
|
+
stdin,
|
|
613
|
+
log,
|
|
614
|
+
compileArgs,
|
|
615
|
+
programArgs,
|
|
616
|
+
activePath: workspace.activePath,
|
|
617
|
+
workspaceFiles: workspace.workspaceFiles
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
catch (error) {
|
|
621
|
+
failRun(error);
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
finally {
|
|
626
|
+
if (this.releaseOperation(activeOperation)) {
|
|
627
|
+
this.finishExplicitStdin(activeOperation);
|
|
628
|
+
this.exit = true;
|
|
629
|
+
}
|
|
630
|
+
this.cleanupOperation(activeOperation);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
cancelOperation(operation, reason) {
|
|
634
|
+
if (!this.isOperationActive(operation))
|
|
635
|
+
return;
|
|
636
|
+
this.finishExplicitStdin(operation);
|
|
637
|
+
operation.cancelled = true;
|
|
638
|
+
operation.cancellationReason = reason;
|
|
639
|
+
this.activeOperation = null;
|
|
640
|
+
this.waitingForInput = false;
|
|
641
|
+
this.pendingEof = false;
|
|
642
|
+
this.uid += 1;
|
|
643
|
+
this.workerSession.terminate(reason);
|
|
644
|
+
this.exit = true;
|
|
645
|
+
this.cleanupOperation(operation);
|
|
646
|
+
}
|
|
647
|
+
kill() {
|
|
648
|
+
this.terminate();
|
|
649
|
+
}
|
|
650
|
+
terminate(reason = 'Process terminated') {
|
|
651
|
+
if (this.disposed)
|
|
652
|
+
return;
|
|
653
|
+
const operation = this.activeOperation;
|
|
654
|
+
if (operation) {
|
|
655
|
+
this.cancelOperation(operation, reason);
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
this.waitingForInput = false;
|
|
659
|
+
this.pendingEof = false;
|
|
660
|
+
this.uid += 1;
|
|
661
|
+
this.workerSession.terminate(reason);
|
|
662
|
+
this.exit = true;
|
|
663
|
+
}
|
|
664
|
+
async clear() {
|
|
665
|
+
if (this.disposed)
|
|
666
|
+
return;
|
|
667
|
+
this.terminate();
|
|
668
|
+
this.pendingInput = [];
|
|
669
|
+
this.waitingForInput = false;
|
|
670
|
+
this.pendingEof = false;
|
|
671
|
+
resetBufferedStdin(this.buffer);
|
|
672
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
673
|
+
}
|
|
674
|
+
dispose() {
|
|
675
|
+
if (this.disposePromise)
|
|
676
|
+
return this.disposePromise;
|
|
677
|
+
this.disposed = true;
|
|
678
|
+
this.disposePromise = Promise.resolve();
|
|
679
|
+
const activeOperation = this.activeOperation;
|
|
680
|
+
const assetBridge = this.assetBridge;
|
|
681
|
+
delete this.worker;
|
|
682
|
+
this.assetBridge = null;
|
|
683
|
+
this.activeVBaseUrl = '';
|
|
684
|
+
this.output = undefined;
|
|
685
|
+
this.resetExplicitStdinState();
|
|
686
|
+
try {
|
|
687
|
+
assetBridge?.dispose();
|
|
688
|
+
}
|
|
689
|
+
catch {
|
|
690
|
+
// Asset cleanup is best effort after host state becomes unreachable.
|
|
691
|
+
}
|
|
692
|
+
if (activeOperation) {
|
|
693
|
+
this.cancelOperation(activeOperation, this.disposeCancellation);
|
|
694
|
+
}
|
|
695
|
+
else {
|
|
696
|
+
this.uid += 1;
|
|
697
|
+
this.workerSession.terminate(this.disposeCancellation);
|
|
698
|
+
this.exit = true;
|
|
699
|
+
}
|
|
700
|
+
return this.disposePromise;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
export default VLang;
|