mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 10:59:27 +00:00
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: https://github.com/nodejs/node/pull/32101 Fixes: https://github.com/nodejs/node/issues/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>
This commit is contained in:
parent
4d93e105bf
commit
6aff62fcb3
@ -58,6 +58,10 @@ added: v13.3.0
|
||||
sandbox directory structure. The string keys of `preopens` are treated as
|
||||
directories within the sandbox. The corresponding values in `preopens` are
|
||||
the real paths to those directories on the host machine.
|
||||
* `returnOnExit` {boolean} By default, WASI applications terminate the Node.js
|
||||
process via the `__wasi_proc_exit()` function. Setting this option to `true`
|
||||
causes `wasi.start()` to return the exit code rather than terminate the
|
||||
process. **Default:** `false`.
|
||||
|
||||
### `wasi.start(instance)`
|
||||
<!-- YAML
|
||||
|
39
lib/wasi.js
39
lib/wasi.js
@ -15,6 +15,7 @@ const {
|
||||
} = require('internal/errors').codes;
|
||||
const { emitExperimentalWarning } = require('internal/util');
|
||||
const { WASI: _WASI } = internalBinding('wasi');
|
||||
const kExitCode = Symbol('exitCode');
|
||||
const kSetMemory = Symbol('setMemory');
|
||||
const kStarted = Symbol('started');
|
||||
|
||||
@ -26,7 +27,7 @@ class WASI {
|
||||
if (options === null || typeof options !== 'object')
|
||||
throw new ERR_INVALID_ARG_TYPE('options', 'object', options);
|
||||
|
||||
const { env, preopens } = options;
|
||||
const { env, preopens, returnOnExit = false } = options;
|
||||
let { args = [] } = options;
|
||||
|
||||
if (ArrayIsArray(args))
|
||||
@ -56,16 +57,26 @@ class WASI {
|
||||
throw new ERR_INVALID_ARG_TYPE('options.preopens', 'Object', preopens);
|
||||
}
|
||||
|
||||
if (typeof returnOnExit !== 'boolean') {
|
||||
throw new ERR_INVALID_ARG_TYPE(
|
||||
'options.returnOnExit', 'boolean', returnOnExit);
|
||||
}
|
||||
|
||||
const wrap = new _WASI(args, envPairs, preopenArray);
|
||||
|
||||
for (const prop in wrap) {
|
||||
wrap[prop] = FunctionPrototypeBind(wrap[prop], wrap);
|
||||
}
|
||||
|
||||
if (returnOnExit) {
|
||||
wrap.proc_exit = FunctionPrototypeBind(wasiReturnOnProcExit, this);
|
||||
}
|
||||
|
||||
this[kSetMemory] = wrap._setMemory;
|
||||
delete wrap._setMemory;
|
||||
this.wasiImport = wrap;
|
||||
this[kStarted] = false;
|
||||
this[kExitCode] = 0;
|
||||
}
|
||||
|
||||
start(instance) {
|
||||
@ -93,12 +104,30 @@ class WASI {
|
||||
this[kStarted] = true;
|
||||
this[kSetMemory](memory);
|
||||
|
||||
if (exports._start)
|
||||
exports._start();
|
||||
else if (exports.__wasi_unstable_reactor_start)
|
||||
exports.__wasi_unstable_reactor_start();
|
||||
try {
|
||||
if (exports._start)
|
||||
exports._start();
|
||||
else if (exports.__wasi_unstable_reactor_start)
|
||||
exports.__wasi_unstable_reactor_start();
|
||||
} catch (err) {
|
||||
if (err !== kExitCode) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
return this[kExitCode];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = { WASI };
|
||||
|
||||
|
||||
function wasiReturnOnProcExit(rval) {
|
||||
// If __wasi_proc_exit() does not terminate the process, an assertion is
|
||||
// triggered in the wasm runtime. Node can sidestep the assertion and return
|
||||
// an exit code by recording the exit code, and throwing a JavaScript
|
||||
// exception that WebAssembly cannot catch.
|
||||
this[kExitCode] = rval;
|
||||
throw kExitCode;
|
||||
}
|
||||
|
18
test/wasi/test-return-on-exit.js
Normal file
18
test/wasi/test-return-on-exit.js
Normal file
@ -0,0 +1,18 @@
|
||||
// Flags: --experimental-wasi-unstable-preview1 --experimental-wasm-bigint
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { WASI } = require('wasi');
|
||||
const wasi = new WASI({ returnOnExit: true });
|
||||
const importObject = { wasi_snapshot_preview1: wasi.wasiImport };
|
||||
const wasmDir = path.join(__dirname, 'wasm');
|
||||
const modulePath = path.join(wasmDir, 'exitcode.wasm');
|
||||
const buffer = fs.readFileSync(modulePath);
|
||||
|
||||
(async () => {
|
||||
const { instance } = await WebAssembly.instantiate(buffer, importObject);
|
||||
|
||||
assert.strictEqual(wasi.start(instance), 120);
|
||||
})().then(common.mustCall());
|
@ -21,6 +21,10 @@ assert.throws(() => { new WASI({ env: 'fhqwhgads' }); },
|
||||
assert.throws(() => { new WASI({ preopens: 'fhqwhgads' }); },
|
||||
{ code: 'ERR_INVALID_ARG_TYPE', message: /\bpreopens\b/ });
|
||||
|
||||
// If returnOnExit is not a boolean and not undefined, it should throw.
|
||||
assert.throws(() => { new WASI({ returnOnExit: 'fhqwhgads' }); },
|
||||
{ code: 'ERR_INVALID_ARG_TYPE', message: /\breturnOnExit\b/ });
|
||||
|
||||
// If options is provided, but not an object, the constructor should throw.
|
||||
[null, 'foo', '', 0, NaN, Symbol(), true, false, () => {}].forEach((value) => {
|
||||
assert.throws(() => { new WASI(value); },
|
||||
|
Loading…
Reference in New Issue
Block a user