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 0ab1ebd

Browse filesBrowse files
cjihrigtargos
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: nodejs#32101 Fixes: nodejs#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 372d30c commit 0ab1ebd
Copy full SHA for 0ab1ebd

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: v12.16.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
@@ -14,6 +14,7 @@ const {
1414
} = require('internal/errors').codes;
1515
const { emitExperimentalWarning } = require('internal/util');
1616
const { WASI: _WASI } = internalBinding('wasi');
17+
const kExitCode = Symbol('exitCode');
1718
const kSetMemory = Symbol('setMemory');
1819
const kStarted = Symbol('started');
1920

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

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

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

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

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

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

7081
start(instance) {
@@ -92,12 +103,30 @@ class WASI {
92103
this[kStarted] = true;
93104
this[kSetMemory](memory);
94105

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

102121

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