Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit dd83bd2

Browse filesBrowse files
cjihrigMylesBorins
authored andcommitted
wasi: add returnOnExit option
This commit adds a WASI option allowing the __wasi_proc_exit() function to return an exit code instead of forcefully terminating the process. PR-URL: #32101 Fixes: #32093 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Gus Caplan <me@gus.host> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Tobias Nießen <tniessen@tnie.de>
1 parent 7c739aa commit dd83bd2
Copy full SHA for dd83bd2

File tree

Expand file treeCollapse file tree

4 files changed

+60
-5
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

4 files changed

+60
-5
lines changed
Open diff view settings
Collapse file

‎doc/api/wasi.md‎

Copy file name to clipboardExpand all lines: doc/api/wasi.md
+4Lines changed: 4 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ added: v13.3.0
5858
sandbox directory structure. The string keys of `preopens` are treated as
5959
directories within the sandbox. The corresponding values in `preopens` are
6060
the real paths to those directories on the host machine.
61+
* `returnOnExit` {boolean} By default, WASI applications terminate the Node.js
62+
process via the `__wasi_proc_exit()` function. Setting this option to `true`
63+
causes `wasi.start()` to return the exit code rather than terminate the
64+
process. **Default:** `false`.
6165

6266
### `wasi.start(instance)`
6367
<!-- YAML
Collapse file

‎lib/wasi.js‎

Copy file name to clipboardExpand all lines: lib/wasi.js
+34-5Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const {
1515
} = require('internal/errors').codes;
1616
const { emitExperimentalWarning } = require('internal/util');
1717
const { WASI: _WASI } = internalBinding('wasi');
18+
const kExitCode = Symbol('exitCode');
1819
const kSetMemory = Symbol('setMemory');
1920
const kStarted = Symbol('started');
2021

@@ -26,7 +27,7 @@ class WASI {
2627
if (options === null || typeof options !== 'object')
2728
throw new ERR_INVALID_ARG_TYPE('options', 'object', options);
2829

29-
const { env, preopens } = options;
30+
const { env, preopens, returnOnExit = false } = options;
3031
let { args = [] } = options;
3132

3233
if (ArrayIsArray(args))
@@ -56,16 +57,26 @@ class WASI {
5657
throw new ERR_INVALID_ARG_TYPE('options.preopens', 'Object', preopens);
5758
}
5859

60+
if (typeof returnOnExit !== 'boolean') {
61+
throw new ERR_INVALID_ARG_TYPE(
62+
'options.returnOnExit', 'boolean', returnOnExit);
63+
}
64+
5965
const wrap = new _WASI(args, envPairs, preopenArray);
6066

6167
for (const prop in wrap) {
6268
wrap[prop] = FunctionPrototypeBind(wrap[prop], wrap);
6369
}
6470

71+
if (returnOnExit) {
72+
wrap.proc_exit = FunctionPrototypeBind(wasiReturnOnProcExit, this);
73+
}
74+
6575
this[kSetMemory] = wrap._setMemory;
6676
delete wrap._setMemory;
6777
this.wasiImport = wrap;
6878
this[kStarted] = false;
79+
this[kExitCode] = 0;
6980
}
7081

7182
start(instance) {
@@ -93,12 +104,30 @@ class WASI {
93104
this[kStarted] = true;
94105
this[kSetMemory](memory);
95106

96-
if (exports._start)
97-
exports._start();
98-
else if (exports.__wasi_unstable_reactor_start)
99-
exports.__wasi_unstable_reactor_start();
107+
try {
108+
if (exports._start)
109+
exports._start();
110+
else if (exports.__wasi_unstable_reactor_start)
111+
exports.__wasi_unstable_reactor_start();
112+
} catch (err) {
113+
if (err !== kExitCode) {
114+
throw err;
115+
}
116+
}
117+
118+
return this[kExitCode];
100119
}
101120
}
102121

103122

104123
module.exports = { WASI };
124+
125+
126+
function wasiReturnOnProcExit(rval) {
127+
// If __wasi_proc_exit() does not terminate the process, an assertion is
128+
// triggered in the wasm runtime. Node can sidestep the assertion and return
129+
// an exit code by recording the exit code, and throwing a JavaScript
130+
// exception that WebAssembly cannot catch.
131+
this[kExitCode] = rval;
132+
throw kExitCode;
133+
}
Collapse file

‎test/wasi/test-return-on-exit.js‎

Copy file name to clipboard
+18Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Flags: --experimental-wasi-unstable-preview1 --experimental-wasm-bigint
2+
'use strict';
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const fs = require('fs');
6+
const path = require('path');
7+
const { WASI } = require('wasi');
8+
const wasi = new WASI({ returnOnExit: true });
9+
const importObject = { wasi_snapshot_preview1: wasi.wasiImport };
10+
const wasmDir = path.join(__dirname, 'wasm');
11+
const modulePath = path.join(wasmDir, 'exitcode.wasm');
12+
const buffer = fs.readFileSync(modulePath);
13+
14+
(async () => {
15+
const { instance } = await WebAssembly.instantiate(buffer, importObject);
16+
17+
assert.strictEqual(wasi.start(instance), 120);
18+
})().then(common.mustCall());
Collapse file

‎test/wasi/test-wasi-options-validation.js‎

Copy file name to clipboardExpand all lines: test/wasi/test-wasi-options-validation.js
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ assert.throws(() => { new WASI({ env: 'fhqwhgads' }); },
2121
assert.throws(() => { new WASI({ preopens: 'fhqwhgads' }); },
2222
{ code: 'ERR_INVALID_ARG_TYPE', message: /\bpreopens\b/ });
2323

24+
// If returnOnExit is not a boolean and not undefined, it should throw.
25+
assert.throws(() => { new WASI({ returnOnExit: 'fhqwhgads' }); },
26+
{ code: 'ERR_INVALID_ARG_TYPE', message: /\breturnOnExit\b/ });
27+
2428
// If options is provided, but not an object, the constructor should throw.
2529
[null, 'foo', '', 0, NaN, Symbol(), true, false, () => {}].forEach((value) => {
2630
assert.throws(() => { new WASI(value); },

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.