@tracecode/harness 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +113 -0
- package/LICENSE +674 -0
- package/README.md +266 -0
- package/dist/browser.cjs +1352 -0
- package/dist/browser.cjs.map +1 -0
- package/dist/browser.d.cts +49 -0
- package/dist/browser.d.ts +49 -0
- package/dist/browser.js +1317 -0
- package/dist/browser.js.map +1 -0
- package/dist/cli.cjs +70 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +70 -0
- package/dist/cli.js.map +1 -0
- package/dist/core.cjs +286 -0
- package/dist/core.cjs.map +1 -0
- package/dist/core.d.cts +69 -0
- package/dist/core.d.ts +69 -0
- package/dist/core.js +254 -0
- package/dist/core.js.map +1 -0
- package/dist/index.cjs +2603 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +2538 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/browser.cjs +647 -0
- package/dist/internal/browser.cjs.map +1 -0
- package/dist/internal/browser.d.cts +143 -0
- package/dist/internal/browser.d.ts +143 -0
- package/dist/internal/browser.js +617 -0
- package/dist/internal/browser.js.map +1 -0
- package/dist/javascript.cjs +549 -0
- package/dist/javascript.cjs.map +1 -0
- package/dist/javascript.d.cts +11 -0
- package/dist/javascript.d.ts +11 -0
- package/dist/javascript.js +518 -0
- package/dist/javascript.js.map +1 -0
- package/dist/python.cjs +744 -0
- package/dist/python.cjs.map +1 -0
- package/dist/python.d.cts +97 -0
- package/dist/python.d.ts +97 -0
- package/dist/python.js +698 -0
- package/dist/python.js.map +1 -0
- package/dist/runtime-types-C7d1LFbx.d.ts +85 -0
- package/dist/runtime-types-Dvgn07z9.d.cts +85 -0
- package/dist/types-Bzr1Ohcf.d.cts +96 -0
- package/dist/types-Bzr1Ohcf.d.ts +96 -0
- package/package.json +89 -0
- package/workers/javascript/javascript-worker.js +2918 -0
- package/workers/python/generated-python-harness-snippets.js +20 -0
- package/workers/python/pyodide-worker.js +1197 -0
- package/workers/python/runtime-core.js +1529 -0
- package/workers/vendor/typescript.js +200276 -0
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
// packages/harness-browser/src/pyodide-worker-client.ts
|
|
2
|
+
var EXECUTION_TIMEOUT_MS = 1e4;
|
|
3
|
+
var INTERVIEW_MODE_TIMEOUT_MS = 5e3;
|
|
4
|
+
var TRACING_TIMEOUT_MS = 3e4;
|
|
5
|
+
var INIT_TIMEOUT_MS = 12e4;
|
|
6
|
+
var MESSAGE_TIMEOUT_MS = 2e4;
|
|
7
|
+
var WORKER_READY_TIMEOUT_MS = 1e4;
|
|
8
|
+
var PyodideWorkerClient = class {
|
|
9
|
+
constructor(options) {
|
|
10
|
+
this.options = options;
|
|
11
|
+
this.debug = options.debug ?? process.env.NODE_ENV === "development";
|
|
12
|
+
}
|
|
13
|
+
worker = null;
|
|
14
|
+
pendingMessages = /* @__PURE__ */ new Map();
|
|
15
|
+
messageId = 0;
|
|
16
|
+
isInitializing = false;
|
|
17
|
+
initPromise = null;
|
|
18
|
+
workerReadyPromise = null;
|
|
19
|
+
workerReadyResolve = null;
|
|
20
|
+
workerReadyReject = null;
|
|
21
|
+
debug;
|
|
22
|
+
/**
|
|
23
|
+
* Check if Web Workers are supported
|
|
24
|
+
*/
|
|
25
|
+
isSupported() {
|
|
26
|
+
return typeof Worker !== "undefined";
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Get or create the worker instance
|
|
30
|
+
*/
|
|
31
|
+
getWorker() {
|
|
32
|
+
if (this.worker) return this.worker;
|
|
33
|
+
if (!this.isSupported()) {
|
|
34
|
+
throw new Error("Web Workers are not supported in this environment");
|
|
35
|
+
}
|
|
36
|
+
this.workerReadyPromise = new Promise((resolve, reject) => {
|
|
37
|
+
this.workerReadyResolve = resolve;
|
|
38
|
+
this.workerReadyReject = (error) => reject(error);
|
|
39
|
+
});
|
|
40
|
+
const workerUrl = this.debug && !this.options.workerUrl.includes("?") ? `${this.options.workerUrl}?dev=${Date.now()}` : this.options.workerUrl;
|
|
41
|
+
this.worker = new Worker(workerUrl);
|
|
42
|
+
this.worker.onmessage = (event) => {
|
|
43
|
+
const { id, type, payload } = event.data;
|
|
44
|
+
if (type === "worker-ready") {
|
|
45
|
+
this.workerReadyResolve?.();
|
|
46
|
+
this.workerReadyResolve = null;
|
|
47
|
+
this.workerReadyReject = null;
|
|
48
|
+
if (this.debug) console.log("[PyodideWorkerClient] worker-ready");
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (this.debug && !id) {
|
|
52
|
+
console.log("[PyodideWorkerClient] event", { type, payload });
|
|
53
|
+
}
|
|
54
|
+
if (id) {
|
|
55
|
+
const pending = this.pendingMessages.get(id);
|
|
56
|
+
if (pending) {
|
|
57
|
+
this.pendingMessages.delete(id);
|
|
58
|
+
if (pending.timeoutId) globalThis.clearTimeout(pending.timeoutId);
|
|
59
|
+
if (type === "error") {
|
|
60
|
+
pending.reject(new Error(payload.error));
|
|
61
|
+
} else {
|
|
62
|
+
if (this.debug) console.log("[PyodideWorkerClient] recv", { id, type });
|
|
63
|
+
pending.resolve(payload);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
this.worker.onerror = (error) => {
|
|
69
|
+
console.error("[PyodideWorkerClient] Worker error:", error);
|
|
70
|
+
const workerError = new Error("Worker error");
|
|
71
|
+
this.workerReadyReject?.(workerError);
|
|
72
|
+
this.workerReadyResolve = null;
|
|
73
|
+
this.workerReadyReject = null;
|
|
74
|
+
for (const [id, pending] of this.pendingMessages) {
|
|
75
|
+
if (pending.timeoutId) {
|
|
76
|
+
globalThis.clearTimeout(pending.timeoutId);
|
|
77
|
+
}
|
|
78
|
+
pending.reject(workerError);
|
|
79
|
+
this.pendingMessages.delete(id);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
return this.worker;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Wait for worker bootstrap signal with timeout.
|
|
86
|
+
* Guards against deadlocks when the worker script fails before posting "worker-ready".
|
|
87
|
+
*/
|
|
88
|
+
async waitForWorkerReady() {
|
|
89
|
+
const readyPromise = this.workerReadyPromise;
|
|
90
|
+
if (!readyPromise) return;
|
|
91
|
+
await new Promise((resolve, reject) => {
|
|
92
|
+
let settled = false;
|
|
93
|
+
const timeoutId = globalThis.setTimeout(() => {
|
|
94
|
+
if (settled) return;
|
|
95
|
+
settled = true;
|
|
96
|
+
const timeoutError = new Error(
|
|
97
|
+
`Python worker failed to initialize in time (${Math.round(WORKER_READY_TIMEOUT_MS / 1e3)}s)`
|
|
98
|
+
);
|
|
99
|
+
if (this.debug) {
|
|
100
|
+
console.warn("[PyodideWorkerClient] worker-ready timeout", { timeoutMs: WORKER_READY_TIMEOUT_MS });
|
|
101
|
+
}
|
|
102
|
+
this.terminateAndReset(timeoutError);
|
|
103
|
+
reject(timeoutError);
|
|
104
|
+
}, WORKER_READY_TIMEOUT_MS);
|
|
105
|
+
readyPromise.then(() => {
|
|
106
|
+
if (settled) return;
|
|
107
|
+
settled = true;
|
|
108
|
+
globalThis.clearTimeout(timeoutId);
|
|
109
|
+
resolve();
|
|
110
|
+
}).catch((error) => {
|
|
111
|
+
if (settled) return;
|
|
112
|
+
settled = true;
|
|
113
|
+
globalThis.clearTimeout(timeoutId);
|
|
114
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Send a message to the worker and wait for a response
|
|
120
|
+
*/
|
|
121
|
+
async sendMessage(type, payload, timeoutMs = MESSAGE_TIMEOUT_MS) {
|
|
122
|
+
const worker = this.getWorker();
|
|
123
|
+
await this.waitForWorkerReady();
|
|
124
|
+
const id = String(++this.messageId);
|
|
125
|
+
return new Promise((resolve, reject) => {
|
|
126
|
+
this.pendingMessages.set(id, {
|
|
127
|
+
resolve,
|
|
128
|
+
reject
|
|
129
|
+
});
|
|
130
|
+
if (this.debug) console.log("[PyodideWorkerClient] send", { id, type });
|
|
131
|
+
const timeoutId = globalThis.setTimeout(() => {
|
|
132
|
+
const pending2 = this.pendingMessages.get(id);
|
|
133
|
+
if (!pending2) return;
|
|
134
|
+
this.pendingMessages.delete(id);
|
|
135
|
+
if (this.debug) console.warn("[PyodideWorkerClient] timeout", { id, type });
|
|
136
|
+
pending2.reject(new Error(`Worker request timed out: ${type}`));
|
|
137
|
+
}, timeoutMs);
|
|
138
|
+
const pending = this.pendingMessages.get(id);
|
|
139
|
+
if (pending) pending.timeoutId = timeoutId;
|
|
140
|
+
worker.postMessage({ id, type, payload });
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Execute code with a timeout - terminates worker if execution takes too long
|
|
145
|
+
*/
|
|
146
|
+
async executeWithTimeout(executor, timeoutMs = EXECUTION_TIMEOUT_MS) {
|
|
147
|
+
return new Promise((resolve, reject) => {
|
|
148
|
+
let settled = false;
|
|
149
|
+
const timeoutId = globalThis.setTimeout(() => {
|
|
150
|
+
if (settled) return;
|
|
151
|
+
settled = true;
|
|
152
|
+
if (this.debug) {
|
|
153
|
+
console.warn("[PyodideWorkerClient] Execution timeout - terminating worker");
|
|
154
|
+
}
|
|
155
|
+
this.terminateAndReset();
|
|
156
|
+
const seconds = Math.round(timeoutMs / 1e3);
|
|
157
|
+
reject(new Error(`Execution timed out (possible infinite loop). Code execution was stopped after ${seconds} seconds.`));
|
|
158
|
+
}, timeoutMs);
|
|
159
|
+
executor().then((result) => {
|
|
160
|
+
if (settled) return;
|
|
161
|
+
settled = true;
|
|
162
|
+
globalThis.clearTimeout(timeoutId);
|
|
163
|
+
resolve(result);
|
|
164
|
+
}).catch((error) => {
|
|
165
|
+
if (settled) return;
|
|
166
|
+
settled = true;
|
|
167
|
+
globalThis.clearTimeout(timeoutId);
|
|
168
|
+
reject(error);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Terminate the worker and reset state for recreation
|
|
174
|
+
*/
|
|
175
|
+
terminateAndReset(reason = new Error("Worker was terminated")) {
|
|
176
|
+
this.workerReadyReject?.(reason);
|
|
177
|
+
if (this.worker) {
|
|
178
|
+
this.worker.terminate();
|
|
179
|
+
this.worker = null;
|
|
180
|
+
}
|
|
181
|
+
this.initPromise = null;
|
|
182
|
+
this.isInitializing = false;
|
|
183
|
+
this.workerReadyPromise = null;
|
|
184
|
+
this.workerReadyResolve = null;
|
|
185
|
+
for (const [, pending] of this.pendingMessages) {
|
|
186
|
+
if (pending.timeoutId) globalThis.clearTimeout(pending.timeoutId);
|
|
187
|
+
pending.reject(reason);
|
|
188
|
+
}
|
|
189
|
+
this.pendingMessages.clear();
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Initialize Pyodide in the worker
|
|
193
|
+
*/
|
|
194
|
+
async init() {
|
|
195
|
+
if (this.initPromise) {
|
|
196
|
+
return this.initPromise;
|
|
197
|
+
}
|
|
198
|
+
if (this.isInitializing) {
|
|
199
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
200
|
+
return this.init();
|
|
201
|
+
}
|
|
202
|
+
this.isInitializing = true;
|
|
203
|
+
this.initPromise = (async () => {
|
|
204
|
+
try {
|
|
205
|
+
return await this.sendMessage("init", void 0, INIT_TIMEOUT_MS);
|
|
206
|
+
} catch (error) {
|
|
207
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
208
|
+
const shouldRetry = message.includes("Worker request timed out: init") || message.includes("Worker was terminated") || message.includes("Worker error") || message.includes("failed to initialize in time");
|
|
209
|
+
if (!shouldRetry) {
|
|
210
|
+
throw error;
|
|
211
|
+
}
|
|
212
|
+
if (this.debug) {
|
|
213
|
+
console.warn("[PyodideWorkerClient] init failed, resetting worker and retrying once", { message });
|
|
214
|
+
}
|
|
215
|
+
this.terminateAndReset();
|
|
216
|
+
return this.sendMessage("init", void 0, INIT_TIMEOUT_MS);
|
|
217
|
+
}
|
|
218
|
+
})();
|
|
219
|
+
try {
|
|
220
|
+
const result = await this.initPromise;
|
|
221
|
+
return result;
|
|
222
|
+
} catch (error) {
|
|
223
|
+
this.initPromise = null;
|
|
224
|
+
throw error;
|
|
225
|
+
} finally {
|
|
226
|
+
this.isInitializing = false;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Execute Python code with tracing for step-by-step visualization
|
|
231
|
+
* @param options.maxLineEvents - Max line events before abort (for complexity analysis, use higher values)
|
|
232
|
+
*/
|
|
233
|
+
async executeWithTracing(code, functionName, inputs, options, executionStyle = "function") {
|
|
234
|
+
await this.init();
|
|
235
|
+
try {
|
|
236
|
+
return await this.executeWithTimeout(
|
|
237
|
+
() => this.sendMessage("execute-with-tracing", {
|
|
238
|
+
code,
|
|
239
|
+
functionName,
|
|
240
|
+
inputs,
|
|
241
|
+
executionStyle,
|
|
242
|
+
options
|
|
243
|
+
}, TRACING_TIMEOUT_MS + 5e3),
|
|
244
|
+
// Message timeout slightly longer than execution timeout
|
|
245
|
+
TRACING_TIMEOUT_MS
|
|
246
|
+
);
|
|
247
|
+
} catch (error) {
|
|
248
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
249
|
+
const isClientTimeout = errorMessage.includes("Execution timed out") || errorMessage.includes("possible infinite loop");
|
|
250
|
+
if (isClientTimeout) {
|
|
251
|
+
return {
|
|
252
|
+
success: false,
|
|
253
|
+
error: errorMessage,
|
|
254
|
+
trace: [],
|
|
255
|
+
executionTimeMs: TRACING_TIMEOUT_MS,
|
|
256
|
+
consoleOutput: [],
|
|
257
|
+
traceLimitExceeded: true,
|
|
258
|
+
timeoutReason: "client-timeout",
|
|
259
|
+
lineEventCount: 0,
|
|
260
|
+
traceStepCount: 0
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
throw error;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Execute Python code without tracing (for running tests)
|
|
268
|
+
*/
|
|
269
|
+
async executeCode(code, functionName, inputs, executionStyle = "function") {
|
|
270
|
+
await this.init();
|
|
271
|
+
return this.executeWithTimeout(
|
|
272
|
+
() => this.sendMessage("execute-code", {
|
|
273
|
+
code,
|
|
274
|
+
functionName,
|
|
275
|
+
inputs,
|
|
276
|
+
executionStyle
|
|
277
|
+
}, EXECUTION_TIMEOUT_MS + 5e3),
|
|
278
|
+
EXECUTION_TIMEOUT_MS
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Execute Python code in interview mode - 5 second timeout, generic error messages
|
|
283
|
+
* Does not reveal which line caused the timeout
|
|
284
|
+
*/
|
|
285
|
+
async executeCodeInterviewMode(code, functionName, inputs, executionStyle = "function") {
|
|
286
|
+
await this.init();
|
|
287
|
+
try {
|
|
288
|
+
const result = await this.executeWithTimeout(
|
|
289
|
+
() => this.sendMessage("execute-code-interview", {
|
|
290
|
+
code,
|
|
291
|
+
functionName,
|
|
292
|
+
inputs,
|
|
293
|
+
executionStyle
|
|
294
|
+
}, INTERVIEW_MODE_TIMEOUT_MS + 2e3),
|
|
295
|
+
INTERVIEW_MODE_TIMEOUT_MS
|
|
296
|
+
);
|
|
297
|
+
if (!result.success && result.error) {
|
|
298
|
+
const normalizedError = result.error.toLowerCase();
|
|
299
|
+
const isTimeoutOrResourceLimit = normalizedError.includes("timed out") || normalizedError.includes("execution timeout") || normalizedError.includes("infinite loop") || normalizedError.includes("interview_guard_triggered") || normalizedError.includes("memory-limit") || normalizedError.includes("line-limit") || normalizedError.includes("single-line-limit") || normalizedError.includes("recursion-limit");
|
|
300
|
+
if (isTimeoutOrResourceLimit) {
|
|
301
|
+
return {
|
|
302
|
+
success: false,
|
|
303
|
+
output: null,
|
|
304
|
+
error: "Time Limit Exceeded",
|
|
305
|
+
consoleOutput: result.consoleOutput ?? []
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return result;
|
|
310
|
+
} catch (error) {
|
|
311
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
312
|
+
if (errorMsg.includes("timed out") || errorMsg.includes("Execution timeout")) {
|
|
313
|
+
return {
|
|
314
|
+
success: false,
|
|
315
|
+
output: null,
|
|
316
|
+
error: "Time Limit Exceeded",
|
|
317
|
+
consoleOutput: []
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
return {
|
|
321
|
+
success: false,
|
|
322
|
+
output: null,
|
|
323
|
+
error: errorMsg,
|
|
324
|
+
consoleOutput: []
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Check the status of the worker
|
|
330
|
+
*/
|
|
331
|
+
async getStatus() {
|
|
332
|
+
return this.sendMessage("status");
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Analyze Python code using AST (off main thread)
|
|
336
|
+
* Returns CodeFacts with semantic information about the code
|
|
337
|
+
*/
|
|
338
|
+
async analyzeCode(code) {
|
|
339
|
+
await this.init();
|
|
340
|
+
return this.sendMessage("analyze-code", { code }, 5e3);
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Terminate the worker and clean up resources
|
|
344
|
+
*/
|
|
345
|
+
terminate() {
|
|
346
|
+
this.terminateAndReset();
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
function isWorkerSupported() {
|
|
350
|
+
return typeof Worker !== "undefined";
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// packages/harness-browser/src/javascript-worker-client.ts
|
|
354
|
+
var EXECUTION_TIMEOUT_MS2 = 7e3;
|
|
355
|
+
var INTERVIEW_MODE_TIMEOUT_MS2 = 5e3;
|
|
356
|
+
var TRACING_TIMEOUT_MS2 = 7e3;
|
|
357
|
+
var INIT_TIMEOUT_MS2 = 1e4;
|
|
358
|
+
var MESSAGE_TIMEOUT_MS2 = 12e3;
|
|
359
|
+
var WORKER_READY_TIMEOUT_MS2 = 1e4;
|
|
360
|
+
var JavaScriptWorkerClient = class {
|
|
361
|
+
constructor(options) {
|
|
362
|
+
this.options = options;
|
|
363
|
+
this.debug = options.debug ?? process.env.NODE_ENV === "development";
|
|
364
|
+
}
|
|
365
|
+
worker = null;
|
|
366
|
+
pendingMessages = /* @__PURE__ */ new Map();
|
|
367
|
+
messageId = 0;
|
|
368
|
+
isInitializing = false;
|
|
369
|
+
initPromise = null;
|
|
370
|
+
workerReadyPromise = null;
|
|
371
|
+
workerReadyResolve = null;
|
|
372
|
+
workerReadyReject = null;
|
|
373
|
+
debug;
|
|
374
|
+
isSupported() {
|
|
375
|
+
return typeof Worker !== "undefined";
|
|
376
|
+
}
|
|
377
|
+
getWorker() {
|
|
378
|
+
if (this.worker) return this.worker;
|
|
379
|
+
if (!this.isSupported()) {
|
|
380
|
+
throw new Error("Web Workers are not supported in this environment");
|
|
381
|
+
}
|
|
382
|
+
this.workerReadyPromise = new Promise((resolve, reject) => {
|
|
383
|
+
this.workerReadyResolve = resolve;
|
|
384
|
+
this.workerReadyReject = (error) => reject(error);
|
|
385
|
+
});
|
|
386
|
+
const workerUrl = this.debug && !this.options.workerUrl.includes("?") ? `${this.options.workerUrl}?dev=${Date.now()}` : this.options.workerUrl;
|
|
387
|
+
this.worker = new Worker(workerUrl);
|
|
388
|
+
this.worker.onmessage = (event) => {
|
|
389
|
+
const { id, type, payload } = event.data;
|
|
390
|
+
if (type === "worker-ready") {
|
|
391
|
+
this.workerReadyResolve?.();
|
|
392
|
+
this.workerReadyResolve = null;
|
|
393
|
+
this.workerReadyReject = null;
|
|
394
|
+
if (this.debug) console.log("[JavaScriptWorkerClient] worker-ready");
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
if (id) {
|
|
398
|
+
const pending = this.pendingMessages.get(id);
|
|
399
|
+
if (!pending) return;
|
|
400
|
+
this.pendingMessages.delete(id);
|
|
401
|
+
if (pending.timeoutId) globalThis.clearTimeout(pending.timeoutId);
|
|
402
|
+
if (type === "error") {
|
|
403
|
+
pending.reject(new Error(payload.error));
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
pending.resolve(payload);
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
this.worker.onerror = (error) => {
|
|
410
|
+
console.error("[JavaScriptWorkerClient] Worker error:", error);
|
|
411
|
+
const workerError = new Error("Worker error");
|
|
412
|
+
this.workerReadyReject?.(workerError);
|
|
413
|
+
this.workerReadyResolve = null;
|
|
414
|
+
this.workerReadyReject = null;
|
|
415
|
+
for (const [id, pending] of this.pendingMessages) {
|
|
416
|
+
if (pending.timeoutId) globalThis.clearTimeout(pending.timeoutId);
|
|
417
|
+
pending.reject(workerError);
|
|
418
|
+
this.pendingMessages.delete(id);
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
return this.worker;
|
|
422
|
+
}
|
|
423
|
+
async waitForWorkerReady() {
|
|
424
|
+
const readyPromise = this.workerReadyPromise;
|
|
425
|
+
if (!readyPromise) return;
|
|
426
|
+
await new Promise((resolve, reject) => {
|
|
427
|
+
let settled = false;
|
|
428
|
+
const timeoutId = globalThis.setTimeout(() => {
|
|
429
|
+
if (settled) return;
|
|
430
|
+
settled = true;
|
|
431
|
+
const timeoutError = new Error(
|
|
432
|
+
`JavaScript worker failed to initialize in time (${Math.round(WORKER_READY_TIMEOUT_MS2 / 1e3)}s)`
|
|
433
|
+
);
|
|
434
|
+
this.terminateAndReset(timeoutError);
|
|
435
|
+
reject(timeoutError);
|
|
436
|
+
}, WORKER_READY_TIMEOUT_MS2);
|
|
437
|
+
readyPromise.then(() => {
|
|
438
|
+
if (settled) return;
|
|
439
|
+
settled = true;
|
|
440
|
+
globalThis.clearTimeout(timeoutId);
|
|
441
|
+
resolve();
|
|
442
|
+
}).catch((error) => {
|
|
443
|
+
if (settled) return;
|
|
444
|
+
settled = true;
|
|
445
|
+
globalThis.clearTimeout(timeoutId);
|
|
446
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
async sendMessage(type, payload, timeoutMs = MESSAGE_TIMEOUT_MS2) {
|
|
451
|
+
const worker = this.getWorker();
|
|
452
|
+
await this.waitForWorkerReady();
|
|
453
|
+
const id = String(++this.messageId);
|
|
454
|
+
return new Promise((resolve, reject) => {
|
|
455
|
+
this.pendingMessages.set(id, {
|
|
456
|
+
resolve,
|
|
457
|
+
reject
|
|
458
|
+
});
|
|
459
|
+
const timeoutId = globalThis.setTimeout(() => {
|
|
460
|
+
const pending2 = this.pendingMessages.get(id);
|
|
461
|
+
if (!pending2) return;
|
|
462
|
+
this.pendingMessages.delete(id);
|
|
463
|
+
pending2.reject(new Error(`Worker request timed out: ${type}`));
|
|
464
|
+
}, timeoutMs);
|
|
465
|
+
const pending = this.pendingMessages.get(id);
|
|
466
|
+
if (pending) pending.timeoutId = timeoutId;
|
|
467
|
+
worker.postMessage({ id, type, payload });
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
async executeWithTimeout(executor, timeoutMs) {
|
|
471
|
+
return new Promise((resolve, reject) => {
|
|
472
|
+
let settled = false;
|
|
473
|
+
const timeoutId = globalThis.setTimeout(() => {
|
|
474
|
+
if (settled) return;
|
|
475
|
+
settled = true;
|
|
476
|
+
this.terminateAndReset();
|
|
477
|
+
reject(
|
|
478
|
+
new Error(
|
|
479
|
+
`Execution timed out (possible infinite loop). Code execution was stopped after ${Math.round(timeoutMs / 1e3)} seconds.`
|
|
480
|
+
)
|
|
481
|
+
);
|
|
482
|
+
}, timeoutMs);
|
|
483
|
+
executor().then((result) => {
|
|
484
|
+
if (settled) return;
|
|
485
|
+
settled = true;
|
|
486
|
+
globalThis.clearTimeout(timeoutId);
|
|
487
|
+
resolve(result);
|
|
488
|
+
}).catch((error) => {
|
|
489
|
+
if (settled) return;
|
|
490
|
+
settled = true;
|
|
491
|
+
globalThis.clearTimeout(timeoutId);
|
|
492
|
+
reject(error);
|
|
493
|
+
});
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
terminateAndReset(reason = new Error("Worker was terminated")) {
|
|
497
|
+
this.workerReadyReject?.(reason);
|
|
498
|
+
if (this.worker) {
|
|
499
|
+
this.worker.terminate();
|
|
500
|
+
this.worker = null;
|
|
501
|
+
}
|
|
502
|
+
this.initPromise = null;
|
|
503
|
+
this.isInitializing = false;
|
|
504
|
+
this.workerReadyPromise = null;
|
|
505
|
+
this.workerReadyResolve = null;
|
|
506
|
+
this.workerReadyReject = null;
|
|
507
|
+
for (const [, pending] of this.pendingMessages) {
|
|
508
|
+
if (pending.timeoutId) globalThis.clearTimeout(pending.timeoutId);
|
|
509
|
+
pending.reject(reason);
|
|
510
|
+
}
|
|
511
|
+
this.pendingMessages.clear();
|
|
512
|
+
}
|
|
513
|
+
async init() {
|
|
514
|
+
if (this.initPromise) return this.initPromise;
|
|
515
|
+
if (this.isInitializing) {
|
|
516
|
+
await new Promise((resolve) => globalThis.setTimeout(resolve, 100));
|
|
517
|
+
return this.init();
|
|
518
|
+
}
|
|
519
|
+
this.isInitializing = true;
|
|
520
|
+
this.initPromise = this.sendMessage("init", void 0, INIT_TIMEOUT_MS2);
|
|
521
|
+
try {
|
|
522
|
+
return await this.initPromise;
|
|
523
|
+
} catch (error) {
|
|
524
|
+
this.initPromise = null;
|
|
525
|
+
throw error;
|
|
526
|
+
} finally {
|
|
527
|
+
this.isInitializing = false;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
async executeWithTracing(code, functionName, inputs, options, executionStyle = "function", language = "javascript") {
|
|
531
|
+
await this.init();
|
|
532
|
+
return this.executeWithTimeout(
|
|
533
|
+
() => this.sendMessage(
|
|
534
|
+
"execute-with-tracing",
|
|
535
|
+
{
|
|
536
|
+
code,
|
|
537
|
+
functionName,
|
|
538
|
+
inputs,
|
|
539
|
+
options,
|
|
540
|
+
executionStyle,
|
|
541
|
+
language
|
|
542
|
+
},
|
|
543
|
+
TRACING_TIMEOUT_MS2 + 2e3
|
|
544
|
+
),
|
|
545
|
+
TRACING_TIMEOUT_MS2
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
async executeCode(code, functionName, inputs, executionStyle = "function", language = "javascript") {
|
|
549
|
+
await this.init();
|
|
550
|
+
return this.executeWithTimeout(
|
|
551
|
+
() => this.sendMessage(
|
|
552
|
+
"execute-code",
|
|
553
|
+
{
|
|
554
|
+
code,
|
|
555
|
+
functionName,
|
|
556
|
+
inputs,
|
|
557
|
+
executionStyle,
|
|
558
|
+
language
|
|
559
|
+
},
|
|
560
|
+
EXECUTION_TIMEOUT_MS2 + 2e3
|
|
561
|
+
),
|
|
562
|
+
EXECUTION_TIMEOUT_MS2
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
async executeCodeInterviewMode(code, functionName, inputs, executionStyle = "function", language = "javascript") {
|
|
566
|
+
await this.init();
|
|
567
|
+
try {
|
|
568
|
+
const result = await this.executeWithTimeout(
|
|
569
|
+
() => this.sendMessage(
|
|
570
|
+
"execute-code-interview",
|
|
571
|
+
{
|
|
572
|
+
code,
|
|
573
|
+
functionName,
|
|
574
|
+
inputs,
|
|
575
|
+
executionStyle,
|
|
576
|
+
language
|
|
577
|
+
},
|
|
578
|
+
INTERVIEW_MODE_TIMEOUT_MS2 + 2e3
|
|
579
|
+
),
|
|
580
|
+
INTERVIEW_MODE_TIMEOUT_MS2
|
|
581
|
+
);
|
|
582
|
+
if (!result.success && result.error) {
|
|
583
|
+
const normalized = result.error.toLowerCase();
|
|
584
|
+
const isTimeoutOrResourceLimit = normalized.includes("timed out") || normalized.includes("infinite loop") || normalized.includes("line-limit") || normalized.includes("single-line-limit") || normalized.includes("recursion-limit") || normalized.includes("trace-limit") || normalized.includes("line events") || normalized.includes("trace steps") || normalized.includes("call depth");
|
|
585
|
+
if (isTimeoutOrResourceLimit) {
|
|
586
|
+
return {
|
|
587
|
+
success: false,
|
|
588
|
+
output: null,
|
|
589
|
+
error: "Time Limit Exceeded",
|
|
590
|
+
consoleOutput: result.consoleOutput ?? []
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
return result;
|
|
595
|
+
} catch {
|
|
596
|
+
return {
|
|
597
|
+
success: false,
|
|
598
|
+
output: null,
|
|
599
|
+
error: "Time Limit Exceeded",
|
|
600
|
+
consoleOutput: []
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
terminate() {
|
|
605
|
+
this.terminateAndReset();
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
function isJavaScriptWorkerSupported() {
|
|
609
|
+
return typeof Worker !== "undefined";
|
|
610
|
+
}
|
|
611
|
+
export {
|
|
612
|
+
JavaScriptWorkerClient,
|
|
613
|
+
PyodideWorkerClient,
|
|
614
|
+
isJavaScriptWorkerSupported,
|
|
615
|
+
isWorkerSupported
|
|
616
|
+
};
|
|
617
|
+
//# sourceMappingURL=browser.js.map
|