Skip to content

Commit 0de2556

Browse files
committed
improve threads termination
1 parent 0fa53a7 commit 0de2556

File tree

7 files changed

+91
-19
lines changed

7 files changed

+91
-19
lines changed

packages/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export type {
2424
export type {
2525
LoadOptions,
2626
InstantiateOptions,
27+
LoadedSource,
2728
InstantiatedSource
2829
} from './load'
2930

packages/core/src/load.ts

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@ import { createNapiModule } from './emnapi/index'
44
import type { CreateOptions, NapiModule } from './emnapi/index'
55

66
/** @public */
7-
export interface InstantiatedSource extends WebAssembly.WebAssemblyInstantiatedSource {
7+
export interface LoadedSource extends WebAssembly.WebAssemblyInstantiatedSource {
8+
usedInstance: WebAssembly.Instance
9+
}
10+
11+
/** @public */
12+
export interface InstantiatedSource extends LoadedSource {
813
napiModule: NapiModule
914
}
1015

@@ -70,13 +75,6 @@ function loadNapiModuleImpl (loadFn: Function, userNapiModule: NapiModule | unde
7075
}
7176

7277
if (wasi) {
73-
Object.assign(
74-
importObject,
75-
typeof wasi.getImportObject === 'function'
76-
? wasi.getImportObject()
77-
: { wasi_snapshot_preview1: wasi.wasiImport }
78-
)
79-
8078
wasiThreads = new WASIThreads(
8179
napiModule.childThread
8280
? {
@@ -88,6 +86,15 @@ function loadNapiModuleImpl (loadFn: Function, userNapiModule: NapiModule | unde
8886
waitThreadStart: napiModule.waitThreadStart
8987
}
9088
)
89+
wasiThreads.patchWasiInstance(wasi)
90+
91+
Object.assign(
92+
importObject,
93+
typeof wasi.getImportObject === 'function'
94+
? wasi.getImportObject()
95+
: { wasi_snapshot_preview1: wasi.wasiImport }
96+
)
97+
9198
Object.assign(importObject, wasiThreads.getImportObject())
9299
}
93100

@@ -127,13 +134,20 @@ function loadNapiModuleImpl (loadFn: Function, userNapiModule: NapiModule | unde
127134
instance = { exports }
128135
}
129136
const module = source.module
137+
138+
const isCommand = ('_start' in originalExports) && (typeof originalExports._start === 'function')
139+
130140
if (wasi) {
131141
if (napiModule.childThread) {
132142
instance = createInstanceProxy(instance, memory)
133143
}
134144
wasiThreads!.setup(instance, module, memory)
135-
if ('_start' in originalExports) {
136-
wasi.start(instance)
145+
if (isCommand) {
146+
if (napiModule.childThread) {
147+
wasi.start(instance)
148+
} else {
149+
setupInstance(wasi, instance)
150+
}
137151
} else {
138152
wasi.initialize(instance)
139153
}
@@ -155,7 +169,11 @@ function loadNapiModuleImpl (loadFn: Function, userNapiModule: NapiModule | unde
155169
table
156170
})
157171

158-
const ret: any = { instance: originalInstance, module }
172+
const ret: any = {
173+
instance: originalInstance,
174+
module,
175+
usedInstance: instance
176+
}
159177
if (!isLoad) {
160178
ret.napiModule = napiModule
161179
}
@@ -192,7 +210,7 @@ export function loadNapiModule (
192210
/** Only support `BufferSource` or `WebAssembly.Module` on Node.js */
193211
wasmInput: InputType | Promise<InputType>,
194212
options?: LoadOptions
195-
): Promise<WebAssembly.WebAssemblyInstantiatedSource> {
213+
): Promise<LoadedSource> {
196214
if (typeof napiModule !== 'object' || napiModule === null) {
197215
throw new TypeError('Invalid napiModule')
198216
}
@@ -204,7 +222,7 @@ export function loadNapiModuleSync (
204222
napiModule: NapiModule,
205223
wasmInput: BufferSource | WebAssembly.Module,
206224
options?: LoadOptions
207-
): WebAssembly.WebAssemblyInstantiatedSource {
225+
): LoadedSource {
208226
if (typeof napiModule !== 'object' || napiModule === null) {
209227
throw new TypeError('Invalid napiModule')
210228
}
@@ -227,3 +245,18 @@ export function instantiateNapiModuleSync (
227245
): InstantiatedSource {
228246
return loadNapiModuleImpl(loadSyncCallback, undefined, wasmInput, options)
229247
}
248+
249+
function setupInstance (wasi: WASIInstance, instance: WebAssembly.Instance): void {
250+
const symbols = Object.getOwnPropertySymbols(wasi)
251+
const selectDescription = (description: string) => (s: symbol) => {
252+
if (s.description) {
253+
return s.description === description
254+
}
255+
return s.toString() === `Symbol(${description})`
256+
}
257+
const kInstance = symbols.filter(selectDescription('kInstance'))[0]
258+
const kSetMemory = symbols.filter(selectDescription('kSetMemory'))[0];
259+
260+
(wasi as any)[kInstance] = instance;
261+
(wasi as any)[kSetMemory](instance.exports.memory)
262+
}

packages/wasi-threads/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,5 @@ export { ThreadMessageHandler } from './worker'
1818
export type { InstantiatePayload, ThreadMessageHandlerOptions } from './worker'
1919

2020
export { createInstanceProxy } from './proxy'
21+
22+
export { isTrapError } from './util'

packages/wasi-threads/src/thread-manager.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,13 +99,20 @@ export class ThreadManager {
9999
if (worker.whenLoaded) return worker.whenLoaded
100100
const err = this.printErr
101101
const beforeLoad = this._beforeLoad
102+
// eslint-disable-next-line @typescript-eslint/no-this-alias
103+
const _this = this
102104
worker.whenLoaded = new Promise<WorkerLike>((resolve, reject) => {
103105
const handleError = function (e: { message: string }): void {
104106
let message = 'worker sent an error!'
105107
if (worker.__emnapi_tid !== undefined) {
106108
message = 'worker (tid = ' + worker.__emnapi_tid + ') sent an error!'
107109
}
108110
err(message + ' ' + e.message)
111+
if (e.message === 'unreachable') {
112+
try {
113+
_this.terminateAllThreads()
114+
} catch (_) {}
115+
}
109116
reject(e)
110117
throw e as Error
111118
}

packages/wasi-threads/src/util.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,8 @@ export function deserizeErrorFromBuffer (sab: SharedArrayBuffer): Error | null {
5757
})
5858
return error
5959
}
60+
61+
/** @public */
62+
export function isTrapError (e: Error): e is WebAssembly.RuntimeError {
63+
return (e instanceof WebAssembly.RuntimeError) && (e.message === 'unreachable')
64+
}

packages/wasi-threads/src/wasi-threads.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import { ENVIRONMENT_IS_NODE, deserizeErrorFromBuffer, getPostMessage } from './util'
1+
import { ENVIRONMENT_IS_NODE, deserizeErrorFromBuffer, getPostMessage, isTrapError } from './util'
22
import { checkSharedWasmMemory, ThreadManager } from './thread-manager'
33
import type { WorkerMessageEvent, ThreadManagerOptions } from './thread-manager'
44

55
/** @public */
66
export interface WASIInstance {
77
readonly wasiImport?: Record<string, any>
88
initialize (instance: object): void
9-
start (instance: object): void
9+
start (instance: object): number
1010
getImportObject? (): any
1111
}
1212

@@ -244,16 +244,29 @@ export class WASIThreads {
244244

245245
public patchWasiInstance<T extends WASIInstance> (wasi: T): T {
246246
if (!wasi) return wasi
247+
// eslint-disable-next-line @typescript-eslint/no-this-alias
248+
const _this = this
247249
const wasiImport = wasi.wasiImport
248250
if (wasiImport) {
249251
const proc_exit = wasiImport.proc_exit
250-
// eslint-disable-next-line @typescript-eslint/no-this-alias
251-
const _this = this
252252
wasiImport.proc_exit = function (code: number): number {
253253
_this.terminateAllThreads()
254254
return proc_exit.call(this, code)
255255
}
256256
}
257+
const start = wasi.start
258+
if (typeof start === 'function') {
259+
wasi.start = function (instance: object): number {
260+
try {
261+
return start.call(this, instance)
262+
} catch (err) {
263+
if (isTrapError(err)) {
264+
_this.terminateAllThreads()
265+
}
266+
throw err
267+
}
268+
}
269+
}
257270
return wasi
258271
}
259272

packages/wasi-threads/src/worker.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { WorkerMessageEvent } from './thread-manager'
2-
import { getPostMessage, serizeErrorToBuffer } from './util'
2+
import { getPostMessage, isTrapError, serizeErrorToBuffer } from './util'
33

44
/** @public */
55
export interface InstantiatePayload {
@@ -95,7 +95,18 @@ export class ThreadMessageHandler {
9595
const tid = payload.tid
9696
const startArg = payload.arg
9797
notifyPthreadCreateResult(payload.sab, 1)
98-
;(this.instance!.exports.wasi_thread_start as Function)(tid, startArg)
98+
try {
99+
(this.instance!.exports.wasi_thread_start as Function)(tid, startArg)
100+
} catch (err) {
101+
if (isTrapError(err)) {
102+
postMessage({
103+
__emnapi__: {
104+
type: 'terminate-all-threads'
105+
}
106+
})
107+
}
108+
throw err
109+
}
99110
postMessage({
100111
__emnapi__: {
101112
type: 'cleanup-thread',

0 commit comments

Comments
 (0)