Skip to content

Commit 99effe2

Browse files
authored
fix(wasm32-wasip1-threads): process never exit if trap in threads (#156)
1 parent b0c688b commit 99effe2

File tree

8 files changed

+173
-22
lines changed

8 files changed

+173
-22
lines changed

packages/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"./package.json": "./package.json"
2828
},
2929
"dependencies": {
30-
"@emnapi/wasi-threads": "1.0.1",
30+
"@emnapi/wasi-threads": "1.0.4",
3131
"tslib": "^2.4.0"
3232
},
3333
"scripts": {

packages/test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ if((NOT IS_WASM) OR IS_EMSCRIPTEN OR IS_WASI_THREADS)
287287
add_test("tsfn" "./tsfn/binding.c" ON)
288288
add_test("async_cleanup_hook" "./async_cleanup_hook/binding.c" ON)
289289
add_test("uv_threadpool_size" "./uv_threadpool_size/binding.c" ON)
290+
add_test("trap_in_thread" "./trap_in_thread/binding.c" ON)
290291
endif()
291292

292293
add_test("arg" "./arg/binding.c" OFF)

packages/test/script/test.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ const pthread = [
2121
'tsfn/**/*',
2222
'async_cleanup_hook/**/*',
2323
'string/string-pthread.test.js',
24-
'uv_threadpool_size/**/*'
24+
'uv_threadpool_size/**/*',
25+
'trap_in_thread/**/*',
2526
]
2627

2728
if (process.env.EMNAPI_TEST_NATIVE) {
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
#include <pthread.h>
2+
#include <node_api.h>
3+
#include <stdlib.h>
4+
#include "../common.h"
5+
6+
void Callback(napi_env env, napi_value js_callback, void* context, void* data) {
7+
8+
}
9+
10+
void* ThreadRelease(void* data) {
11+
napi_threadsafe_function ts_fn = (napi_threadsafe_function)data;
12+
napi_release_threadsafe_function(ts_fn, napi_tsfn_release);
13+
return NULL;
14+
}
15+
16+
void* ThreadAbort(void* data) {
17+
abort();
18+
return NULL;
19+
}
20+
21+
static napi_value Abort(napi_env env, napi_callback_info info) {
22+
size_t argc = 1;
23+
napi_value argv;
24+
napi_value async_name;
25+
napi_threadsafe_function ts_fn;
26+
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, &argv, NULL, NULL));
27+
NODE_API_CALL(env, napi_create_string_utf8(env,
28+
"N-API Thread-safe Function Test", NAPI_AUTO_LENGTH, &async_name));
29+
NODE_API_CALL(env, napi_create_threadsafe_function(env,
30+
argv,
31+
NULL,
32+
async_name,
33+
0,
34+
1,
35+
NULL,
36+
NULL,
37+
NULL,
38+
Callback,
39+
&ts_fn));
40+
abort();
41+
return NULL;
42+
}
43+
44+
static napi_value ReleaseInThread(napi_env env, napi_callback_info info) {
45+
size_t argc = 1;
46+
napi_value argv;
47+
napi_value async_name;
48+
napi_threadsafe_function ts_fn;
49+
pthread_t uv_threads;
50+
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, &argv, NULL, NULL));
51+
NODE_API_CALL(env, napi_create_string_utf8(env,
52+
"N-API Thread-safe Function Test", NAPI_AUTO_LENGTH, &async_name));
53+
NODE_API_CALL(env, napi_create_threadsafe_function(env,
54+
argv,
55+
NULL,
56+
async_name,
57+
0,
58+
1,
59+
NULL,
60+
NULL,
61+
NULL,
62+
Callback,
63+
&ts_fn));
64+
pthread_create(&uv_threads, NULL, ThreadRelease, ts_fn);
65+
pthread_detach(uv_threads);
66+
return NULL;
67+
}
68+
69+
static napi_value AbortInThread(napi_env env, napi_callback_info info) {
70+
size_t argc = 1;
71+
napi_value argv;
72+
napi_value async_name;
73+
napi_threadsafe_function ts_fn;
74+
pthread_t uv_threads;
75+
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, &argv, NULL, NULL));
76+
NODE_API_CALL(env, napi_create_string_utf8(env,
77+
"N-API Thread-safe Function Test", NAPI_AUTO_LENGTH, &async_name));
78+
NODE_API_CALL(env, napi_create_threadsafe_function(env,
79+
argv,
80+
NULL,
81+
async_name,
82+
0,
83+
1,
84+
NULL,
85+
NULL,
86+
NULL,
87+
Callback,
88+
&ts_fn));
89+
pthread_create(&uv_threads, NULL, ThreadAbort, ts_fn);
90+
pthread_detach(uv_threads);
91+
return NULL;
92+
}
93+
94+
static napi_value Init(napi_env env, napi_value exports) {
95+
napi_property_descriptor descriptors[] = {
96+
DECLARE_NODE_API_PROPERTY("abort", Abort),
97+
DECLARE_NODE_API_PROPERTY("releaseInThread", ReleaseInThread),
98+
DECLARE_NODE_API_PROPERTY("abortInThread", AbortInThread),
99+
};
100+
101+
NODE_API_CALL(env, napi_define_properties(
102+
env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors));
103+
104+
return exports;
105+
}
106+
107+
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/* eslint-disable camelcase */
2+
'use strict'
3+
const assert = require('assert')
4+
const { load } = require('../util')
5+
6+
async function main () {
7+
if (process.argv[2] === 'child') {
8+
const binding = await load('trap_in_thread')
9+
binding[process.argv[3]](() => {})
10+
return
11+
}
12+
13+
const test = async (f, signal) => {
14+
const { spawn } = require('child_process')
15+
const child = spawn(process.execPath, [
16+
'--expose-gc',
17+
...(process.env.EMNAPI_TEST_WASI ? ['--experimental-wasi-unstable-preview1'] : []),
18+
...(process.env.MEMORY64 ? ['--experimental-wasm-memory64'] : []),
19+
__filename,
20+
'child', f
21+
], { stdio: 'inherit' })
22+
const result = await new Promise((resolve, reject) => {
23+
let exit = false
24+
child.on('exit', (code, signal) => {
25+
console.log(`Child exited with code: ${code}, signal: ${signal}`)
26+
exit = true
27+
resolve({
28+
code,
29+
signal
30+
})
31+
})
32+
setTimeout(() => {
33+
if (exit) return
34+
reject(new Error(`Test timed out: ${f}`))
35+
child.kill('SIGKILL')
36+
}, 2000)
37+
})
38+
assert.strictEqual(result.signal, process.env.EMNAPI_TEST_NATIVE ? signal : null)
39+
}
40+
41+
await test('abort', 'SIGABRT')
42+
await test('releaseInThread', null)
43+
await test('abortInThread','SIGABRT')
44+
}
45+
46+
module.exports = main()

packages/wasi-threads/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@emnapi/wasi-threads",
3-
"version": "1.0.3",
3+
"version": "1.0.4",
44
"description": "WASI threads proposal implementation in JavaScript",
55
"type": "module",
66
"main": "./dist/wasi-threads.cjs",

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

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -359,16 +359,18 @@ function patchWasiInstance (wasiThreads: WASIThreads, wasi: WASIInstance): void
359359
return proc_exit.call(this, code)
360360
}
361361
}
362-
const start = wasi.start
363-
if (typeof start === 'function') {
364-
wasi.start = function (instance: object): number {
365-
try {
366-
return start.call(this, instance)
367-
} catch (err) {
368-
if (isTrapError(err)) {
369-
_this.terminateAllThreads()
362+
if (!_this.childThread) {
363+
const start = wasi.start
364+
if (typeof start === 'function') {
365+
wasi.start = function (instance: object): number {
366+
try {
367+
return start.call(this, instance)
368+
} catch (err) {
369+
if (isTrapError(err)) {
370+
_this.terminateAllThreads()
371+
}
372+
throw err
370373
}
371-
throw err
372374
}
373375
}
374376
}

packages/wasi-threads/src/worker.ts

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { type LoadPayload, createMessage } from './command'
22
import type { WorkerMessageEvent } from './thread-manager'
3-
import { getPostMessage, isTrapError, serizeErrorToBuffer } from './util'
3+
import { getPostMessage, serizeErrorToBuffer } from './util'
44

55
export interface OnStartData {
66
tid: number
@@ -79,7 +79,8 @@ export class ThreadMessageHandler {
7979
}
8080

8181
private _start (payload: OnStartData): void {
82-
if (typeof this.instance!.exports.wasi_thread_start !== 'function') {
82+
const wasi_thread_start = this.instance!.exports.wasi_thread_start as (tid: number, startArg: number) => void
83+
if (typeof wasi_thread_start !== 'function') {
8384
const err = new TypeError('wasi_thread_start is not exported')
8485
notifyPthreadCreateResult(payload.sab, 2, err)
8586
throw err
@@ -88,14 +89,7 @@ export class ThreadMessageHandler {
8889
const tid = payload.tid
8990
const startArg = payload.arg
9091
notifyPthreadCreateResult(payload.sab, 1)
91-
try {
92-
(this.instance!.exports.wasi_thread_start as (tid: number, startArg: number) => void)(tid, startArg)
93-
} catch (err) {
94-
if (isTrapError(err)) {
95-
postMessage(createMessage('terminate-all-threads', {}))
96-
}
97-
throw err
98-
}
92+
wasi_thread_start(tid, startArg)
9993
postMessage(createMessage('cleanup-thread', { tid }))
10094
}
10195

0 commit comments

Comments
 (0)