@oxc-minify/binding-wasm32-wasi 0.152.0 → 0.153.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/minify.wasi-browser.js +35 -0
- package/minify.wasi.cjs +394 -3
- package/minify.wasm32-wasi.wasm +0 -0
- package/package.json +2 -2
- package/wasi-worker-browser.mjs +24 -0
- package/wasi-worker.mjs +167 -63
package/minify.wasi-browser.js
CHANGED
|
@@ -1341,6 +1341,36 @@ function __rollbackWasiInitialization() {
|
|
|
1341
1341
|
return __rollbackWasmEnvForWasiInitialization()
|
|
1342
1342
|
}
|
|
1343
1343
|
|
|
1344
|
+
// A view of the addon's crash flag, one word of the shared wasm memory, for the
|
|
1345
|
+
// pool workers: they raise it when their wasm thread dies, and the shutdown
|
|
1346
|
+
// waits inside the cleanup calls on this thread then trap instead of waiting on
|
|
1347
|
+
// the dead thread for good. Undefined for an addon built with an older napi.
|
|
1348
|
+
let __wasiAddonCrashFlag
|
|
1349
|
+
|
|
1350
|
+
function __captureWasiAddonCrashFlag(instance) {
|
|
1351
|
+
try {
|
|
1352
|
+
const getAddress = instance.exports.napi_wasm_thread_crash_flag_address
|
|
1353
|
+
if (typeof getAddress !== 'function') {
|
|
1354
|
+
return
|
|
1355
|
+
}
|
|
1356
|
+
const address = getAddress() >>> 0
|
|
1357
|
+
const buffer = __sharedMemory.buffer
|
|
1358
|
+
if (address === 0 || address % 4 !== 0 || address + 4 > buffer.byteLength) {
|
|
1359
|
+
return
|
|
1360
|
+
}
|
|
1361
|
+
__wasiAddonCrashFlag = new Int32Array(buffer, address, 1)
|
|
1362
|
+
} catch {}
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
function __shareWasiAddonCrashFlag(worker) {
|
|
1366
|
+
if (__wasiAddonCrashFlag === undefined) {
|
|
1367
|
+
return
|
|
1368
|
+
}
|
|
1369
|
+
try {
|
|
1370
|
+
worker.postMessage({ __napiRsAddonCrashFlag: __wasiAddonCrashFlag })
|
|
1371
|
+
} catch {}
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1344
1374
|
let __wasiModule
|
|
1345
1375
|
let __napiModule
|
|
1346
1376
|
|
|
@@ -1371,6 +1401,7 @@ try {
|
|
|
1371
1401
|
type: 'module',
|
|
1372
1402
|
})
|
|
1373
1403
|
__wasiWorkers.add(worker)
|
|
1404
|
+
__shareWasiAddonCrashFlag(worker)
|
|
1374
1405
|
|
|
1375
1406
|
|
|
1376
1407
|
return worker
|
|
@@ -1386,6 +1417,10 @@ try {
|
|
|
1386
1417
|
},
|
|
1387
1418
|
beforeInit({ instance }) {
|
|
1388
1419
|
__napiInstance = instance
|
|
1420
|
+
__captureWasiAddonCrashFlag(instance)
|
|
1421
|
+
for (const worker of __wasiWorkers) {
|
|
1422
|
+
__shareWasiAddonCrashFlag(worker)
|
|
1423
|
+
}
|
|
1389
1424
|
for (const name of Object.keys(instance.exports)) {
|
|
1390
1425
|
if (name.startsWith('__napi_register__')) {
|
|
1391
1426
|
instance.exports[name]()
|
package/minify.wasi.cjs
CHANGED
|
@@ -139,7 +139,13 @@ function __createWasiWorker(filename) {
|
|
|
139
139
|
return new Worker(filename, {
|
|
140
140
|
env: process.env,
|
|
141
141
|
execArgv: __workerExecArgv,
|
|
142
|
-
workerData: {
|
|
142
|
+
workerData: {
|
|
143
|
+
hostRoot: __hostRoot,
|
|
144
|
+
rootDir: __rootDir,
|
|
145
|
+
crashFlag: __wasiThreadCrashFlag,
|
|
146
|
+
crashReport: __wasiThreadCrashReport,
|
|
147
|
+
addonCrashFlag: __wasiAddonCrashFlag,
|
|
148
|
+
},
|
|
143
149
|
})
|
|
144
150
|
} catch (error) {
|
|
145
151
|
if (!error || error.code !== 'ERR_WORKER_INVALID_EXEC_ARGV') {
|
|
@@ -155,6 +161,300 @@ function __createWasiWorker(filename) {
|
|
|
155
161
|
}
|
|
156
162
|
}
|
|
157
163
|
|
|
164
|
+
// Set to 1 by a pool worker (see wasi-worker.mjs) right before it reports that
|
|
165
|
+
// its wasm thread died. Shared memory, so this thread reads it synchronously even
|
|
166
|
+
// while the worker's 'error' event is still queued behind the code that is
|
|
167
|
+
// exiting right now.
|
|
168
|
+
const __wasiThreadCrashFlag = new Int32Array(new SharedArrayBuffer(4))
|
|
169
|
+
// Written by the first pool worker whose wasm thread dies, before it raises the
|
|
170
|
+
// flag: its error and `threadId` (layout in wasi-worker.mjs). Read without
|
|
171
|
+
// waiting for any event, see `__readWasiThreadCrashReport`.
|
|
172
|
+
const __wasiThreadCrashReport = new SharedArrayBuffer(4096)
|
|
173
|
+
let __wasiThreadCrashReportRead
|
|
174
|
+
// The first error a pool worker reported through its 'error' event, and that
|
|
175
|
+
// worker's `threadId`. See `__getWasiThreadCrashError`.
|
|
176
|
+
let __wasiThreadCrashWorkerError
|
|
177
|
+
let __wasiThreadCrashWorkerId
|
|
178
|
+
let __wasiThreadCrashError
|
|
179
|
+
// Raised while `__runWasiInitializationRollbackSteps` runs, so its polls and
|
|
180
|
+
// steps stop after a crash the way a public disposal's do.
|
|
181
|
+
let __wasiInitializationRollbackActive = false
|
|
182
|
+
let __wasiThreadCrashed = false
|
|
183
|
+
|
|
184
|
+
// A view of the addon's crash flag, one word of the shared wasm memory, for the
|
|
185
|
+
// pool workers: they raise it when their wasm thread dies, and the shutdown
|
|
186
|
+
// waits inside the cleanup calls on this thread then trap instead of waiting on
|
|
187
|
+
// the dead thread for good. Undefined for an addon built with an older napi.
|
|
188
|
+
let __wasiAddonCrashFlag
|
|
189
|
+
|
|
190
|
+
function __captureWasiAddonCrashFlag(instance) {
|
|
191
|
+
try {
|
|
192
|
+
const getAddress = instance.exports.napi_wasm_thread_crash_flag_address
|
|
193
|
+
if (typeof getAddress !== 'function') {
|
|
194
|
+
return
|
|
195
|
+
}
|
|
196
|
+
const address = getAddress() >>> 0
|
|
197
|
+
const buffer = __sharedMemory.buffer
|
|
198
|
+
if (address === 0 || address % 4 !== 0 || address + 4 > buffer.byteLength) {
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
__wasiAddonCrashFlag = new Int32Array(buffer, address, 1)
|
|
202
|
+
} catch {}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Whether any wasm thread of this binding has died: a trap or an uncaught error
|
|
207
|
+
* in a pool worker, including one that failed to load after its thread spawn
|
|
208
|
+
* had already been reported as started.
|
|
209
|
+
*
|
|
210
|
+
* After that the shared wasm state cannot be trusted: a lock the dead thread
|
|
211
|
+
* held stays held, and a join or park that waits on it — the async runtime's
|
|
212
|
+
* `finish_shutdown` waiting for the dead thread's work to go idle — blocks
|
|
213
|
+
* this thread forever in a raw `memory.atomic.wait32`, which neither
|
|
214
|
+
* emnapi's crash check nor a signal can interrupt.
|
|
215
|
+
*/
|
|
216
|
+
function __hasWasiThreadCrashed() {
|
|
217
|
+
if (__wasiThreadCrashed || Atomics.load(__wasiThreadCrashFlag, 0) !== 0) {
|
|
218
|
+
return true
|
|
219
|
+
}
|
|
220
|
+
const manager = __getWasiThreadManager()
|
|
221
|
+
return Boolean(manager && manager._fatalError)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
let __wasiThreadCrashDisposePromise
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Stores the first error a pool worker reported, with the worker's id. Called
|
|
228
|
+
* from the loader's own 'error' listener, which runs before emnapi's.
|
|
229
|
+
*/
|
|
230
|
+
function __recordWasiThreadCrashError(error, workerId) {
|
|
231
|
+
if (__wasiThreadCrashWorkerError !== undefined || error === undefined) {
|
|
232
|
+
return
|
|
233
|
+
}
|
|
234
|
+
__wasiThreadCrashWorkerError = error
|
|
235
|
+
__wasiThreadCrashWorkerId = workerId
|
|
236
|
+
__fillWasiThreadCrashError()
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* The error in the shared crash report, rebuilt once it is complete: the
|
|
241
|
+
* worker writes it before it raises the flag, so it is there as soon as the
|
|
242
|
+
* crash is seen. Plain JavaScript over shared memory; never enters wasm.
|
|
243
|
+
*/
|
|
244
|
+
function __readWasiThreadCrashReport() {
|
|
245
|
+
if (__wasiThreadCrashReportRead !== undefined) {
|
|
246
|
+
return __wasiThreadCrashReportRead
|
|
247
|
+
}
|
|
248
|
+
try {
|
|
249
|
+
const header = new Int32Array(__wasiThreadCrashReport, 0, 3)
|
|
250
|
+
if (Atomics.load(header, 0) !== 2) {
|
|
251
|
+
return
|
|
252
|
+
}
|
|
253
|
+
const length = Atomics.load(header, 1)
|
|
254
|
+
const threadId = Atomics.load(header, 2)
|
|
255
|
+
let error
|
|
256
|
+
if (length > 0) {
|
|
257
|
+
// Copied out of shared memory: TextDecoder does not take a shared view.
|
|
258
|
+
const bytes = new Uint8Array(__wasiThreadCrashReport, 12, length).slice()
|
|
259
|
+
const report = JSON.parse(new TextDecoder().decode(bytes))
|
|
260
|
+
error = new Error(String(report.message))
|
|
261
|
+
if (typeof report.name === 'string') {
|
|
262
|
+
error.name = report.name
|
|
263
|
+
}
|
|
264
|
+
if (typeof report.stack === 'string') {
|
|
265
|
+
error.stack = report.stack
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
__wasiThreadCrashReportRead = { error, threadId }
|
|
269
|
+
} catch {
|
|
270
|
+
__wasiThreadCrashReportRead = { error: undefined, threadId: 0 }
|
|
271
|
+
}
|
|
272
|
+
return __wasiThreadCrashReportRead
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* The one error every crash path of this binding reports, created on first
|
|
277
|
+
* use.
|
|
278
|
+
*
|
|
279
|
+
* The shared flag is raised before this thread has processed the worker's
|
|
280
|
+
* 'error' event, and once the workers are terminated that event is often
|
|
281
|
+
* never delivered, so neither it nor emnapi's `_fatalError` can be counted
|
|
282
|
+
* on. The cause is filled in from the first source that has it: the error the
|
|
283
|
+
* 'error' listener kept, else the worker's shared crash report, else
|
|
284
|
+
* `_fatalError` — here, and again from the 'error' listener when it arrives
|
|
285
|
+
* later. Until then the error carries only its message.
|
|
286
|
+
*/
|
|
287
|
+
function __getWasiThreadCrashError() {
|
|
288
|
+
if (__wasiThreadCrashError === undefined) {
|
|
289
|
+
__wasiThreadCrashError = new Error(
|
|
290
|
+
'napi-rs: WASI binding cannot be disposed after a worker thread crashed',
|
|
291
|
+
)
|
|
292
|
+
}
|
|
293
|
+
__fillWasiThreadCrashError()
|
|
294
|
+
return __wasiThreadCrashError
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function __fillWasiThreadCrashError() {
|
|
298
|
+
const crashError = __wasiThreadCrashError
|
|
299
|
+
if (crashError === undefined) {
|
|
300
|
+
return
|
|
301
|
+
}
|
|
302
|
+
try {
|
|
303
|
+
if (crashError.cause === undefined) {
|
|
304
|
+
let cause = __wasiThreadCrashWorkerError
|
|
305
|
+
if (cause === undefined) {
|
|
306
|
+
const report = __readWasiThreadCrashReport()
|
|
307
|
+
cause = report ? report.error : undefined
|
|
308
|
+
}
|
|
309
|
+
if (cause === undefined) {
|
|
310
|
+
const manager = __getWasiThreadManager()
|
|
311
|
+
cause = manager ? manager._fatalError : undefined
|
|
312
|
+
}
|
|
313
|
+
if (cause !== undefined && cause !== null) {
|
|
314
|
+
crashError.cause = cause
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (crashError.workerThreadId === undefined) {
|
|
318
|
+
let workerId = __wasiThreadCrashWorkerId
|
|
319
|
+
if (workerId === undefined) {
|
|
320
|
+
const report = __readWasiThreadCrashReport()
|
|
321
|
+
workerId = report && report.threadId > 0 ? report.threadId : undefined
|
|
322
|
+
}
|
|
323
|
+
if (workerId !== undefined) {
|
|
324
|
+
crashError.workerThreadId = workerId
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
} catch {}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Let the event loop drain after a wasm thread died, without entering wasm.
|
|
332
|
+
*
|
|
333
|
+
* emnapi's `Context` keeps a `NodejsWaitingRequestCounter` on Node: a
|
|
334
|
+
* `MessagePort` (`refCounter.refHandle`) that it refs when the count of
|
|
335
|
+
* in-flight async work and threadsafe-function requests leaves zero and unrefs
|
|
336
|
+
* when it comes back. The requests the dead thread held never complete, so the
|
|
337
|
+
* count never returns to zero and the port holds the process open for good.
|
|
338
|
+
* `Context.destroy()` would not release it either — and it runs cleanup
|
|
339
|
+
* hooks, which is exactly what must not happen now. So unref the port directly.
|
|
340
|
+
* The count is left as is: it stays above zero, so a later request never refs
|
|
341
|
+
* the port again.
|
|
342
|
+
*
|
|
343
|
+
* The field is private in emnapi's typings, so read it defensively; a context
|
|
344
|
+
* without it (a non-Node host, a future emnapi) is left alone.
|
|
345
|
+
*/
|
|
346
|
+
function __releaseEmnapiWaitingRequestHandle() {
|
|
347
|
+
try {
|
|
348
|
+
const refCounter = __emnapiContext && __emnapiContext.refCounter
|
|
349
|
+
const refHandle = refCounter && refCounter.refHandle
|
|
350
|
+
if (refHandle && typeof refHandle.unref === 'function') {
|
|
351
|
+
refHandle.unref()
|
|
352
|
+
}
|
|
353
|
+
} catch {}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* The public disposer after a wasm thread died. The normal chain drains async
|
|
358
|
+
* work, runs the environment cleanup barrier and destroys the context — every
|
|
359
|
+
* one of those re-enters wasm, and the barrier's shutdown waits for the dead
|
|
360
|
+
* thread's work in the same raw atomic wait `__disposeWasiBindingAtExit`
|
|
361
|
+
* avoids. An app that handled the worker's error and then disposes would block
|
|
362
|
+
* there for good. Only stop the workers, then reject: the binding was not
|
|
363
|
+
* cleaned up and cannot be, so reporting success would be a lie. The context
|
|
364
|
+
* is not destroyed — only its waiting-request port is unrefed, so the process
|
|
365
|
+
* can exit on its own — and the 'exit' listener takes its short path too.
|
|
366
|
+
*
|
|
367
|
+
* Latched: every later call returns the same promise.
|
|
368
|
+
*/
|
|
369
|
+
function __disposeWasiBindingAfterThreadCrash() {
|
|
370
|
+
if (__wasiThreadCrashDisposePromise) {
|
|
371
|
+
return __wasiThreadCrashDisposePromise
|
|
372
|
+
}
|
|
373
|
+
__releaseEmnapiWaitingRequestHandle()
|
|
374
|
+
let workerResult
|
|
375
|
+
try {
|
|
376
|
+
workerResult = __terminateWasiWorkers()
|
|
377
|
+
} catch (terminateError) {
|
|
378
|
+
workerResult = Promise.reject(terminateError)
|
|
379
|
+
}
|
|
380
|
+
// Built at settlement, after the workers stopped: by then the 'error' event
|
|
381
|
+
// has usually delivered the worker's error.
|
|
382
|
+
__wasiThreadCrashDisposePromise = Promise.resolve(workerResult).then(
|
|
383
|
+
() => {
|
|
384
|
+
throw __getWasiThreadCrashError()
|
|
385
|
+
},
|
|
386
|
+
(terminateError) => {
|
|
387
|
+
throw __attachCleanupErrors(__getWasiThreadCrashError(), [
|
|
388
|
+
terminateError,
|
|
389
|
+
])
|
|
390
|
+
},
|
|
391
|
+
)
|
|
392
|
+
return __wasiThreadCrashDisposePromise
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Stops a public disposal that a thread crash overtook.
|
|
397
|
+
*
|
|
398
|
+
* The check at the top of `__disposeWasiBinding` only sees a crash that came
|
|
399
|
+
* first. A thread that dies once the chain is running leaves it polling for
|
|
400
|
+
* work the dead thread still counts — the async-work drain for
|
|
401
|
+
* `napi_wasm_async_work_pending`, the barrier's poll for
|
|
402
|
+
* `napi_wasm_runtime_work_pending` — and neither count ever reaches zero, so
|
|
403
|
+
* the poll's referenced timers keep the process alive forever with the
|
|
404
|
+
* disposal promise pending. Each poll turn and each step boundary calls this,
|
|
405
|
+
* and the throw ends the chain before anything re-enters wasm again; the
|
|
406
|
+
* disposer then settles it through `__settleWasiDisposalAfterThreadCrash`.
|
|
407
|
+
*
|
|
408
|
+
* The initialization rollback runs the same polls and steps, so they stop while
|
|
409
|
+
* it runs too; its wrapper, `__rollbackWasiInitialization`, then ends it
|
|
410
|
+
* through `__rollbackWasiInitializationAfterThreadCrash`. A bare poll with
|
|
411
|
+
* neither in flight is left alone.
|
|
412
|
+
*/
|
|
413
|
+
function __abortWasiDisposalIfThreadCrashed() {
|
|
414
|
+
if (
|
|
415
|
+
(__wasiDisposePromise !== undefined ||
|
|
416
|
+
__wasiInitializationRollbackActive) &&
|
|
417
|
+
__hasWasiThreadCrashed()
|
|
418
|
+
) {
|
|
419
|
+
throw new Error(
|
|
420
|
+
'napi-rs: WASI disposal stopped because a worker thread crashed',
|
|
421
|
+
)
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Settles an in-flight public disposal whose chain failed after a thread
|
|
427
|
+
* died — stopped by `__abortWasiDisposalIfThreadCrashed` or failing any other
|
|
428
|
+
* way — through the crash disposal: it releases the waiting-request port,
|
|
429
|
+
* terminates the workers and rejects with the same error an entry-time crash
|
|
430
|
+
* gets. `__wasiDisposePromise` stays set, so this caller, every caller that
|
|
431
|
+
* joined it and every later one hold the same promise. Returns false when no
|
|
432
|
+
* thread died, leaving the ordinary failure handling alone.
|
|
433
|
+
*/
|
|
434
|
+
function __settleWasiDisposalAfterThreadCrash(resolve, reject) {
|
|
435
|
+
if (!__hasWasiThreadCrashed()) {
|
|
436
|
+
return false
|
|
437
|
+
}
|
|
438
|
+
__disposeWasiBindingAfterThreadCrash().then(resolve, reject)
|
|
439
|
+
return true
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Ends an initialization rollback after a wasm thread died, without entering
|
|
444
|
+
* wasm: the barrier, its finish and `Context.destroy()` are skipped, and the
|
|
445
|
+
* crash disposal releases the waiting-request port and terminates the workers,
|
|
446
|
+
* once. The initialization error itself is left to propagate: the crash error
|
|
447
|
+
* is returned as the rollback's cleanup error, so
|
|
448
|
+
* `__completeWasiInitializationRollback` attaches it — as the cause when the
|
|
449
|
+
* error has none — and keeps the record, since the context was not destroyed.
|
|
450
|
+
*/
|
|
451
|
+
function __rollbackWasiInitializationAfterThreadCrash() {
|
|
452
|
+
const crashError = __getWasiThreadCrashError()
|
|
453
|
+
// Its rejection is `crashError` itself, which the caller reports.
|
|
454
|
+
void __disposeWasiBindingAfterThreadCrash().catch(() => {})
|
|
455
|
+
return [crashError]
|
|
456
|
+
}
|
|
457
|
+
|
|
158
458
|
const __cwd = process.cwd()
|
|
159
459
|
const __rootDir = __nodePath.parse(__cwd).root
|
|
160
460
|
const __hostRoot =
|
|
@@ -756,6 +1056,15 @@ function __prepareWasmEnvCleanupWithTurns() {
|
|
|
756
1056
|
// Publish the closer before yielding: from here until `finishCleanup` runs,
|
|
757
1057
|
// a caller that cannot yield is entitled to end this handshake itself.
|
|
758
1058
|
__finishParkedWasmEnvCleanup = finishCleanup
|
|
1059
|
+
const finishCleanupUnlessCrashed = () => {
|
|
1060
|
+
try {
|
|
1061
|
+
__abortWasiDisposalIfThreadCrashed()
|
|
1062
|
+
} catch (error) {
|
|
1063
|
+
__finishParkedWasmEnvCleanup = undefined
|
|
1064
|
+
throw error
|
|
1065
|
+
}
|
|
1066
|
+
finishCleanup()
|
|
1067
|
+
}
|
|
759
1068
|
return (async () => {
|
|
760
1069
|
// Unbounded, exactly like the async-work drain below. The wait ends when
|
|
761
1070
|
// the addon reports its runtime work finished; the turns spent here are
|
|
@@ -763,6 +1072,7 @@ function __prepareWasmEnvCleanupWithTurns() {
|
|
|
763
1072
|
const pace = __createWasmRuntimePollPace()
|
|
764
1073
|
for (;;) {
|
|
765
1074
|
await __yieldWasmRuntimePollTurn(pace)
|
|
1075
|
+
__abortWasiDisposalIfThreadCrashed()
|
|
766
1076
|
try {
|
|
767
1077
|
if (!workPending()) {
|
|
768
1078
|
return
|
|
@@ -773,7 +1083,7 @@ function __prepareWasmEnvCleanupWithTurns() {
|
|
|
773
1083
|
return
|
|
774
1084
|
}
|
|
775
1085
|
}
|
|
776
|
-
})().then(
|
|
1086
|
+
})().then(finishCleanupUnlessCrashed, finishCleanupUnlessCrashed)
|
|
777
1087
|
}
|
|
778
1088
|
|
|
779
1089
|
// Turns to wait for while the addon still reports queued settlements. Reaching
|
|
@@ -1096,6 +1406,7 @@ function __drainWasiAsyncWork() {
|
|
|
1096
1406
|
await new Promise((resolve) => {
|
|
1097
1407
|
__scheduleTimer(resolve, __WASI_ASYNC_WORK_POLL_INTERVAL_MS)
|
|
1098
1408
|
})
|
|
1409
|
+
__abortWasiDisposalIfThreadCrashed()
|
|
1099
1410
|
}
|
|
1100
1411
|
})(),
|
|
1101
1412
|
).then(
|
|
@@ -1191,6 +1502,7 @@ function __finishWasiDisposal() {
|
|
|
1191
1502
|
}
|
|
1192
1503
|
|
|
1193
1504
|
function __continueWasiDisposal() {
|
|
1505
|
+
__abortWasiDisposalIfThreadCrashed()
|
|
1194
1506
|
const destroyResult = __destroyEmnapiContext()
|
|
1195
1507
|
if (__isThenable(destroyResult)) {
|
|
1196
1508
|
return Promise.resolve(destroyResult).then(__finishWasiDisposal)
|
|
@@ -1199,6 +1511,7 @@ function __continueWasiDisposal() {
|
|
|
1199
1511
|
}
|
|
1200
1512
|
|
|
1201
1513
|
function __drainWasmEnvForWasiDisposal() {
|
|
1514
|
+
__abortWasiDisposalIfThreadCrashed()
|
|
1202
1515
|
const drainResult = __drainWasmEnvCleanup()
|
|
1203
1516
|
if (__isThenable(drainResult)) {
|
|
1204
1517
|
return Promise.resolve(drainResult).then(__continueWasiDisposal)
|
|
@@ -1207,6 +1520,7 @@ function __drainWasmEnvForWasiDisposal() {
|
|
|
1207
1520
|
}
|
|
1208
1521
|
|
|
1209
1522
|
function __cleanUpWasmEnvForWasiDisposal() {
|
|
1523
|
+
__abortWasiDisposalIfThreadCrashed()
|
|
1210
1524
|
// Run the pre-teardown barrier — yielding the turns its two-phase form asks
|
|
1211
1525
|
// for, when the addon has one — then let the settlements it queued actually
|
|
1212
1526
|
// reach JavaScript, and only then destroy the environment. Doing any two of
|
|
@@ -1243,6 +1557,9 @@ function __disposeWasiBinding() {
|
|
|
1243
1557
|
if (__wasiDisposePromise) {
|
|
1244
1558
|
return __wasiDisposePromise
|
|
1245
1559
|
}
|
|
1560
|
+
if (!__wasiDisposed && __hasWasiThreadCrashed()) {
|
|
1561
|
+
return __disposeWasiBindingAfterThreadCrash()
|
|
1562
|
+
}
|
|
1246
1563
|
if (__wasiDisposed) {
|
|
1247
1564
|
return Promise.resolve()
|
|
1248
1565
|
}
|
|
@@ -1259,6 +1576,9 @@ function __disposeWasiBinding() {
|
|
|
1259
1576
|
try {
|
|
1260
1577
|
result = __startWasiDisposal()
|
|
1261
1578
|
} catch (error) {
|
|
1579
|
+
if (__settleWasiDisposalAfterThreadCrash(resolveDispose, rejectDispose)) {
|
|
1580
|
+
return disposePromise
|
|
1581
|
+
}
|
|
1262
1582
|
__wasiDisposePromise = undefined
|
|
1263
1583
|
rejectDispose(error)
|
|
1264
1584
|
return disposePromise
|
|
@@ -1270,6 +1590,9 @@ function __disposeWasiBinding() {
|
|
|
1270
1590
|
resolveDispose(value)
|
|
1271
1591
|
},
|
|
1272
1592
|
(error) => {
|
|
1593
|
+
if (__settleWasiDisposalAfterThreadCrash(resolveDispose, rejectDispose)) {
|
|
1594
|
+
return
|
|
1595
|
+
}
|
|
1273
1596
|
__wasiDisposePromise = undefined
|
|
1274
1597
|
rejectDispose(error)
|
|
1275
1598
|
},
|
|
@@ -1305,6 +1628,7 @@ function __finishWasiInitializationRollback(cleanupErrors) {
|
|
|
1305
1628
|
}
|
|
1306
1629
|
|
|
1307
1630
|
function __destroyContextForWasiRollback(cleanupErrors) {
|
|
1631
|
+
__abortWasiDisposalIfThreadCrashed()
|
|
1308
1632
|
let destroyResult
|
|
1309
1633
|
try {
|
|
1310
1634
|
destroyResult = __destroyEmnapiContext()
|
|
@@ -1373,10 +1697,11 @@ function __retainFailedWasiRollback(cleanupErrors) {
|
|
|
1373
1697
|
* goes away. That is the deliberate choice: a hung promise is a silent liveness
|
|
1374
1698
|
* bug with no upper bound, while the retained bookkeeping is bounded by the page.
|
|
1375
1699
|
*/
|
|
1376
|
-
function
|
|
1700
|
+
function __runWasiInitializationRollbackSteps() {
|
|
1377
1701
|
// The environment teardown this rollback performs, kept nested so it cannot
|
|
1378
1702
|
// be reached without the async-work drain below running first.
|
|
1379
1703
|
function __rollbackWasmEnvForWasiInitialization() {
|
|
1704
|
+
__abortWasiDisposalIfThreadCrashed()
|
|
1380
1705
|
const cleanupErrors = []
|
|
1381
1706
|
let prepareResult
|
|
1382
1707
|
try {
|
|
@@ -1402,6 +1727,7 @@ function __rollbackWasiInitialization() {
|
|
|
1402
1727
|
// did not finish never gets here: it retains instead, exactly as a drain that
|
|
1403
1728
|
// did not finish does.
|
|
1404
1729
|
function __drainWasmEnvForWasiRollback(cleanupErrors) {
|
|
1730
|
+
__abortWasiDisposalIfThreadCrashed()
|
|
1405
1731
|
let drainResult
|
|
1406
1732
|
try {
|
|
1407
1733
|
drainResult = __drainWasmEnvCleanup()
|
|
@@ -1449,6 +1775,55 @@ function __rollbackWasiInitialization() {
|
|
|
1449
1775
|
return __rollbackWasmEnvForWasiInitialization()
|
|
1450
1776
|
}
|
|
1451
1777
|
|
|
1778
|
+
/**
|
|
1779
|
+
* The rollback above, stopped when a wasm thread has died.
|
|
1780
|
+
*
|
|
1781
|
+
* Its polls wait for work the dead thread still counts, so they never end, and
|
|
1782
|
+
* the barrier's `…_finish` and `Context.destroy()` would re-enter wasm
|
|
1783
|
+
* and wait on that thread for good. While the rollback runs, the poll turns
|
|
1784
|
+
* and step boundaries stop it (`__abortWasiDisposalIfThreadCrashed`), and a
|
|
1785
|
+
* crash seen before, during or after it ends it through
|
|
1786
|
+
* `__rollbackWasiInitializationAfterThreadCrash`. Without a crash the
|
|
1787
|
+
* result — synchronous or not — is passed through unchanged.
|
|
1788
|
+
*/
|
|
1789
|
+
function __rollbackWasiInitialization() {
|
|
1790
|
+
if (__hasWasiThreadCrashed()) {
|
|
1791
|
+
return __rollbackWasiInitializationAfterThreadCrash()
|
|
1792
|
+
}
|
|
1793
|
+
__wasiInitializationRollbackActive = true
|
|
1794
|
+
let result
|
|
1795
|
+
try {
|
|
1796
|
+
result = __runWasiInitializationRollbackSteps()
|
|
1797
|
+
} catch (error) {
|
|
1798
|
+
__wasiInitializationRollbackActive = false
|
|
1799
|
+
if (__hasWasiThreadCrashed()) {
|
|
1800
|
+
return __rollbackWasiInitializationAfterThreadCrash()
|
|
1801
|
+
}
|
|
1802
|
+
throw error
|
|
1803
|
+
}
|
|
1804
|
+
if (!__isThenable(result)) {
|
|
1805
|
+
__wasiInitializationRollbackActive = false
|
|
1806
|
+
return __hasWasiThreadCrashed()
|
|
1807
|
+
? __rollbackWasiInitializationAfterThreadCrash()
|
|
1808
|
+
: result
|
|
1809
|
+
}
|
|
1810
|
+
return Promise.resolve(result).then(
|
|
1811
|
+
(cleanupErrors) => {
|
|
1812
|
+
__wasiInitializationRollbackActive = false
|
|
1813
|
+
return __hasWasiThreadCrashed()
|
|
1814
|
+
? __rollbackWasiInitializationAfterThreadCrash()
|
|
1815
|
+
: cleanupErrors
|
|
1816
|
+
},
|
|
1817
|
+
(error) => {
|
|
1818
|
+
__wasiInitializationRollbackActive = false
|
|
1819
|
+
if (__hasWasiThreadCrashed()) {
|
|
1820
|
+
return __rollbackWasiInitializationAfterThreadCrash()
|
|
1821
|
+
}
|
|
1822
|
+
throw error
|
|
1823
|
+
},
|
|
1824
|
+
)
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1452
1827
|
const __wasiRollbackRegistrySymbol = Symbol.for('napi.rs.wasi.rollback.registry.v1')
|
|
1453
1828
|
const __wasiRollbackRegistryKey =
|
|
1454
1829
|
typeof __filename === 'string' ? __filename : __wasmFilePath
|
|
@@ -1552,6 +1927,16 @@ function __removeWasiExitListener() {
|
|
|
1552
1927
|
|
|
1553
1928
|
function __disposeWasiBindingAtExit() {
|
|
1554
1929
|
__wasiExitListenerRegistered = false
|
|
1930
|
+
if (__hasWasiThreadCrashed()) {
|
|
1931
|
+
// Never re-enter wasm after a thread died: the environment cleanup joins
|
|
1932
|
+
// the dead thread's work and would hang the exit forever — SIGTERM
|
|
1933
|
+
// included, since its JavaScript listener never gets a turn. Stop the
|
|
1934
|
+
// workers and leave.
|
|
1935
|
+
try {
|
|
1936
|
+
void Promise.resolve(__terminateWasiWorkers()).catch(() => {})
|
|
1937
|
+
} catch {}
|
|
1938
|
+
return
|
|
1939
|
+
}
|
|
1555
1940
|
// An 'exit' handler cannot yield, so it cannot wait for queued promise
|
|
1556
1941
|
// settlements the way __startWasiDisposal does — the process is leaving and
|
|
1557
1942
|
// those promises have no observer left anyway. Run the synchronous teardown
|
|
@@ -1666,6 +2051,11 @@ try {
|
|
|
1666
2051
|
onCreateWorker() {
|
|
1667
2052
|
const worker = __createWasiWorker(__nodePath.join(__dirname, 'wasi-worker.mjs'))
|
|
1668
2053
|
__wasiWorkers.add(worker)
|
|
2054
|
+
// Registered before emnapi's own listeners, which rethrow the error.
|
|
2055
|
+
worker.on('error', (error) => {
|
|
2056
|
+
__wasiThreadCrashed = true
|
|
2057
|
+
__recordWasiThreadCrashError(error, worker.threadId)
|
|
2058
|
+
})
|
|
1669
2059
|
worker.onmessage = ({ data }) => {
|
|
1670
2060
|
__wasmCreateOnMessageForFsProxy(__nodeFs)(data)
|
|
1671
2061
|
}
|
|
@@ -1709,6 +2099,7 @@ try {
|
|
|
1709
2099
|
},
|
|
1710
2100
|
beforeInit({ instance }) {
|
|
1711
2101
|
__napiInstance = instance
|
|
2102
|
+
__captureWasiAddonCrashFlag(instance)
|
|
1712
2103
|
for (const name of Object.keys(instance.exports)) {
|
|
1713
2104
|
if (name.startsWith('__napi_register__')) {
|
|
1714
2105
|
instance.exports[name]()
|
package/minify.wasm32-wasi.wasm
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxc-minify/binding-wasm32-wasi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.153.0",
|
|
4
4
|
"main": "minify.wasi.cjs",
|
|
5
5
|
"files": [
|
|
6
6
|
"minify.wasm32-wasi.wasm",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"browser": "minify.wasi-browser.js",
|
|
41
41
|
"type": "module",
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@napi-rs/wasm-runtime": "~1.2.
|
|
43
|
+
"@napi-rs/wasm-runtime": "~1.2.5",
|
|
44
44
|
"@emnapi/core": "2.0.0-alpha.3",
|
|
45
45
|
"@emnapi/runtime": "2.0.0-alpha.3"
|
|
46
46
|
}
|
package/wasi-worker-browser.mjs
CHANGED
|
@@ -38,6 +38,30 @@ const handler = new MessageHandler({
|
|
|
38
38
|
},
|
|
39
39
|
})
|
|
40
40
|
|
|
41
|
+
// When this wasm thread dies, raise the addon's crash flag before emnapi reports
|
|
42
|
+
// it. The loader thread may be inside wasm, in a cleanup call that waits on this
|
|
43
|
+
// thread; the flag is what those waits check between short slices, and once it
|
|
44
|
+
// is up they trap instead of waiting for good. It is a word in the shared wasm
|
|
45
|
+
// memory, and the loader posts a view of it (see
|
|
46
|
+
// napi_wasm_thread_crash_flag_address) after it instantiated the wasm, or right
|
|
47
|
+
// after it created this worker. No view, no flag: an addon built with an older
|
|
48
|
+
// napi, or a failure before the view arrived.
|
|
49
|
+
let __addonCrashFlag
|
|
50
|
+
const __beforeReportError = handler.beforeReportError
|
|
51
|
+
handler.beforeReportError = function (...args) {
|
|
52
|
+
if (__addonCrashFlag !== undefined) {
|
|
53
|
+
try {
|
|
54
|
+
Atomics.store(__addonCrashFlag, 0, 1)
|
|
55
|
+
} catch {}
|
|
56
|
+
}
|
|
57
|
+
return __beforeReportError.apply(this, args)
|
|
58
|
+
}
|
|
59
|
+
|
|
41
60
|
globalThis.onmessage = function (e) {
|
|
61
|
+
const data = e && e.data
|
|
62
|
+
if (data && data.__napiRsAddonCrashFlag instanceof Int32Array) {
|
|
63
|
+
__addonCrashFlag = data.__napiRsAddonCrashFlag
|
|
64
|
+
return
|
|
65
|
+
}
|
|
42
66
|
handler.handle(e)
|
|
43
67
|
}
|
package/wasi-worker.mjs
CHANGED
|
@@ -2,80 +2,184 @@ import fs from 'node:fs'
|
|
|
2
2
|
import { createRequire } from 'node:module'
|
|
3
3
|
import { parse } from 'node:path'
|
|
4
4
|
import { WASI } from 'node:wasi'
|
|
5
|
-
import { parentPort, Worker, workerData } from 'node:worker_threads'
|
|
5
|
+
import { parentPort, threadId, Worker, workerData } from 'node:worker_threads'
|
|
6
6
|
|
|
7
7
|
const require = createRequire(import.meta.url)
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
// The thread spawn that created this worker has already returned, so the loader
|
|
10
|
+
// thread may be waiting on this thread. A failure before the crash hook below is
|
|
11
|
+
// in place (a missing '@napi-rs/wasm-runtime', say) has to raise the crash flags
|
|
12
|
+
// too, then fail the worker as before.
|
|
13
|
+
let handler
|
|
14
|
+
try {
|
|
15
|
+
const {
|
|
16
|
+
instantiateNapiModuleSync,
|
|
17
|
+
MessageHandler,
|
|
18
|
+
getDefaultContext,
|
|
19
|
+
emnapiAsyncWorkPlugin,
|
|
20
|
+
emnapiTSFNPlugin,
|
|
21
|
+
} = require('@napi-rs/wasm-runtime')
|
|
16
22
|
|
|
17
|
-
if (parentPort) {
|
|
18
|
-
|
|
19
|
-
|
|
23
|
+
if (parentPort) {
|
|
24
|
+
parentPort.on('message', (data) => {
|
|
25
|
+
globalThis.onmessage({ data })
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
Object.assign(globalThis, {
|
|
30
|
+
self: globalThis,
|
|
31
|
+
require,
|
|
32
|
+
Worker,
|
|
33
|
+
importScripts: function (f) {
|
|
34
|
+
// oxlint-disable-next-line no-eval -- WASI importScripts polyfill
|
|
35
|
+
;(0, eval)(fs.readFileSync(f, 'utf8') + '//# sourceURL=' + f)
|
|
36
|
+
},
|
|
37
|
+
postMessage: function (msg) {
|
|
38
|
+
if (parentPort) {
|
|
39
|
+
parentPort.postMessage(msg)
|
|
40
|
+
}
|
|
41
|
+
},
|
|
20
42
|
})
|
|
21
|
-
}
|
|
22
43
|
|
|
23
|
-
|
|
24
|
-
self: globalThis,
|
|
25
|
-
require,
|
|
26
|
-
Worker,
|
|
27
|
-
importScripts: function (f) {
|
|
28
|
-
// oxlint-disable-next-line no-eval -- WASI importScripts polyfill
|
|
29
|
-
;(0, eval)(fs.readFileSync(f, 'utf8') + '//# sourceURL=' + f)
|
|
30
|
-
},
|
|
31
|
-
postMessage: function (msg) {
|
|
32
|
-
if (parentPort) {
|
|
33
|
-
parentPort.postMessage(msg)
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
|
-
})
|
|
44
|
+
const emnapiContext = getDefaultContext()
|
|
37
45
|
|
|
38
|
-
const
|
|
46
|
+
const __cwd = process.cwd()
|
|
47
|
+
const __rootDir =
|
|
48
|
+
(workerData && typeof workerData.rootDir === 'string' && workerData.rootDir) ||
|
|
49
|
+
parse(__cwd).root
|
|
50
|
+
const __hostRoot =
|
|
51
|
+
(workerData && typeof workerData.hostRoot === 'string' && workerData.hostRoot) ||
|
|
52
|
+
(process.platform === 'android' ? __cwd : __rootDir)
|
|
39
53
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
54
|
+
handler = new MessageHandler({
|
|
55
|
+
onLoad({ wasmModule, wasmMemory }) {
|
|
56
|
+
const wasi = new WASI({
|
|
57
|
+
version: 'preview1',
|
|
58
|
+
env: process.env,
|
|
59
|
+
preopens: {
|
|
60
|
+
[__rootDir]: __hostRoot,
|
|
61
|
+
[__hostRoot]: __hostRoot,
|
|
62
|
+
},
|
|
63
|
+
})
|
|
47
64
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
65
|
+
return instantiateNapiModuleSync(wasmModule, {
|
|
66
|
+
childThread: true,
|
|
67
|
+
wasi,
|
|
68
|
+
context: emnapiContext,
|
|
69
|
+
// The wasm links a "basic" emnapi archive (no C async-work /
|
|
70
|
+
// threadsafe-function implementations), so every thread that
|
|
71
|
+
// instantiates it must provide the JavaScript implementations
|
|
72
|
+
// through the emnapi plugins.
|
|
73
|
+
plugins: [emnapiAsyncWorkPlugin, emnapiTSFNPlugin],
|
|
74
|
+
overwriteImports(importObject) {
|
|
75
|
+
importObject.env = {
|
|
76
|
+
...importObject.env,
|
|
77
|
+
...importObject.napi,
|
|
78
|
+
...importObject.emnapi,
|
|
79
|
+
memory: wasmMemory,
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
})
|
|
83
|
+
},
|
|
84
|
+
})
|
|
85
|
+
} catch (error) {
|
|
86
|
+
if (workerData && workerData.crashFlag instanceof Int32Array) {
|
|
87
|
+
__raiseWasiThreadCrashFlags(error)
|
|
88
|
+
}
|
|
89
|
+
throw error
|
|
90
|
+
}
|
|
58
91
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
92
|
+
// Tell the loader thread synchronously that this wasm thread died, so its 'exit'
|
|
93
|
+
// teardown does not re-enter wasm and wait on this thread forever. emnapi calls
|
|
94
|
+
// beforeReportError (which runs emnapi_thread_crashed, so the main thread may
|
|
95
|
+
// throw 'unwind' and start exiting) before the 'error' event is even posted.
|
|
96
|
+
// A loader that predates the flag passes none.
|
|
97
|
+
if (workerData && workerData.crashFlag instanceof Int32Array) {
|
|
98
|
+
const __beforeReportError = handler.beforeReportError
|
|
99
|
+
handler.beforeReportError = function (...args) {
|
|
100
|
+
if (!__raiseWasiThreadCrashFlags(args[0])) {
|
|
101
|
+
// No view (the thread was spawned before the loader read the address,
|
|
102
|
+
// say): the export stores into the same word, but only a worker that
|
|
103
|
+
// loaded has an instance to call it on.
|
|
104
|
+
try {
|
|
105
|
+
const __napiThreadCrashed = this.instance?.exports?.napi_wasm_thread_crashed
|
|
106
|
+
if (typeof __napiThreadCrashed === 'function') {
|
|
107
|
+
__napiThreadCrashed()
|
|
74
108
|
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
}
|
|
109
|
+
} catch {}
|
|
110
|
+
}
|
|
111
|
+
return __beforeReportError.apply(this, args)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// The error goes into the shared crash report first. The 'error' event that
|
|
116
|
+
// would carry it is dropped when the loader terminates this worker before the
|
|
117
|
+
// event is sent, so the report is the only copy the loader can count on.
|
|
118
|
+
// Layout: three Int32 words — state (0 empty, 1 writing, 2 written), byte
|
|
119
|
+
// length, threadId — then the UTF-8 JSON of { name, message, stack }. Only the
|
|
120
|
+
// first worker to crash writes it, and it is complete before the flag is raised.
|
|
121
|
+
//
|
|
122
|
+
// Then the loader's flag, then the addon's. The loader thread may already be
|
|
123
|
+
// inside wasm, in a cleanup call that waits on this thread; the addon's flag is
|
|
124
|
+
// what those waits check between short slices. It is a word in the shared wasm
|
|
125
|
+
// memory (the loader passes a view of it, see napi_wasm_thread_crash_flag_address),
|
|
126
|
+
// so no instance is needed: a worker that failed while loading raises it too.
|
|
127
|
+
// Once it is up the waits trap, and the loader, whose flag is already up, turns
|
|
128
|
+
// the throw into its crash rejection. Returns whether the addon's flag was raised.
|
|
129
|
+
function __raiseWasiThreadCrashFlags(error) {
|
|
130
|
+
try {
|
|
131
|
+
__writeCrashReport(workerData.crashReport, error)
|
|
132
|
+
} catch {}
|
|
133
|
+
try {
|
|
134
|
+
Atomics.store(workerData.crashFlag, 0, 1)
|
|
135
|
+
} catch {}
|
|
136
|
+
const addonCrashFlag = workerData.addonCrashFlag
|
|
137
|
+
if (!(addonCrashFlag instanceof Int32Array)) {
|
|
138
|
+
return false
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
Atomics.store(addonCrashFlag, 0, 1)
|
|
142
|
+
return true
|
|
143
|
+
} catch {
|
|
144
|
+
return false
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function __writeCrashReport(report, error) {
|
|
149
|
+
if (!(report instanceof SharedArrayBuffer) || report.byteLength <= 12) {
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
const header = new Int32Array(report, 0, 3)
|
|
153
|
+
if (Atomics.compareExchange(header, 0, 0, 1) !== 0) {
|
|
154
|
+
return
|
|
155
|
+
}
|
|
156
|
+
let length = 0
|
|
157
|
+
try {
|
|
158
|
+
const body = new Uint8Array(report, 12)
|
|
159
|
+
const isObject =
|
|
160
|
+
error !== null && (typeof error === 'object' || typeof error === 'function')
|
|
161
|
+
const name = isObject && typeof error.name === 'string' ? error.name : 'Error'
|
|
162
|
+
const message = isObject && typeof error.message === 'string'
|
|
163
|
+
? error.message
|
|
164
|
+
: String(error)
|
|
165
|
+
const stack = isObject && typeof error.stack === 'string' ? error.stack : undefined
|
|
166
|
+
const encoder = new TextEncoder()
|
|
167
|
+
let bytes = encoder.encode(JSON.stringify({ name, message, stack }))
|
|
168
|
+
if (bytes.length > body.length) {
|
|
169
|
+
bytes = encoder.encode(
|
|
170
|
+
JSON.stringify({ name, message: message.slice(0, body.length >> 3) }),
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
if (bytes.length <= body.length) {
|
|
174
|
+
body.set(bytes)
|
|
175
|
+
length = bytes.length
|
|
176
|
+
}
|
|
177
|
+
Atomics.store(header, 2, threadId)
|
|
178
|
+
} finally {
|
|
179
|
+
Atomics.store(header, 1, length)
|
|
180
|
+
Atomics.store(header, 0, 2)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
79
183
|
|
|
80
184
|
globalThis.onmessage = function (e) {
|
|
81
185
|
handler.handle(e)
|