From db3daaaeeef1ac81104b8a58922da932ccdbadd9 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Tue, 27 Apr 2021 15:22:23 -0500 Subject: [PATCH] feat(xsnap): specify exit codes for meter exhaustion etc. - export api constants so consumers can avoid node.js modules - export METER_TYPE --- packages/xsnap/src/api.js | 53 +++++++++++++++++++++++ packages/xsnap/src/index.js | 7 ++++ packages/xsnap/src/xsnap.c | 70 +++++++++++++++++++------------ packages/xsnap/src/xsnap.js | 19 ++++++--- packages/xsnap/test/test-xsnap.js | 17 ++++---- 5 files changed, 128 insertions(+), 38 deletions(-) create mode 100644 packages/xsnap/src/api.js diff --git a/packages/xsnap/src/api.js b/packages/xsnap/src/api.js new file mode 100644 index 00000000000..d00f16a8831 --- /dev/null +++ b/packages/xsnap/src/api.js @@ -0,0 +1,53 @@ +/* eslint-disable max-classes-per-file */ + +/** The version identifier for our meter type. + * TODO Bump this whenever there's a change to metering semantics. + */ +export const METER_TYPE = 'xs-meter-2'; + +export const ExitCode = { + E_UNKNOWN_ERROR: -1, + E_SUCCESS: 0, + E_BAD_USAGE: 1, + E_IO_ERROR: 2, + E_NOT_ENOUGH_MEMORY: 11, + E_STACK_OVERFLOW: 12, + E_UNHANDLED_EXCEPTION: 15, + E_NO_MORE_KEYS: 16, + E_TOO_MUCH_COMPUTATION: 17, +}; + +export const ErrorMessage = { + [ExitCode.E_UNKNOWN_ERROR]: 'unknown error', + [ExitCode.E_BAD_USAGE]: 'bad argument usage', + [ExitCode.E_IO_ERROR]: 'I/O error', + [ExitCode.E_NOT_ENOUGH_MEMORY]: 'not enough memory', + [ExitCode.E_STACK_OVERFLOW]: 'stack overflow', + [ExitCode.E_UNHANDLED_EXCEPTION]: 'unhandled exception', + [ExitCode.E_NO_MORE_KEYS]: 'property (key) name space exhausted', + [ExitCode.E_TOO_MUCH_COMPUTATION]: 'too much computation', +}; + +export class ErrorSignal extends Error { + /** + * @param { string } signal + * @param {...string | undefined} params + */ + constructor(signal, ...params) { + super(...params); + this.name = 'ExitSignal'; + this.code = signal; + } +} + +export class ErrorCode extends Error { + /** + * @param { number } code + * @param {...string | undefined} params + */ + constructor(code, ...params) { + super(...params); + this.name = 'ExitCode'; + this.code = code; + } +} diff --git a/packages/xsnap/src/index.js b/packages/xsnap/src/index.js index e25ebe7d81c..ac8f182e52b 100644 --- a/packages/xsnap/src/index.js +++ b/packages/xsnap/src/index.js @@ -1,2 +1,9 @@ export { xsnap } from './xsnap'; +export { + ExitCode, + ErrorMessage, + ErrorCode, + ErrorSignal, + METER_TYPE, +} from './api'; export { makeSnapstore } from './snapStore'; diff --git a/packages/xsnap/src/xsnap.c b/packages/xsnap/src/xsnap.c index 1afe9581af9..2e888f0968e 100644 --- a/packages/xsnap/src/xsnap.c +++ b/packages/xsnap/src/xsnap.c @@ -197,7 +197,20 @@ static xsBooleanValue gxMeteringPrint = 0; static FILE *fromParent; static FILE *toParent; -int main(int argc, char* argv[]) +typedef enum { + E_UNKNOWN_ERROR = -1, + E_SUCCESS = 0, + E_BAD_USAGE, + E_IO_ERROR, + // 10 + XS_NOT_ENOUGH_MEMORY_EXIT + E_NOT_ENOUGH_MEMORY = 11, + E_STACK_OVERFLOW = 12, + E_UNHANDLED_EXCEPTION = 15, + E_NO_MORE_KEYS = 16, + E_TOO_MUCH_COMPUTATION = 17, +} ExitCode; + +ExitCode main(int argc, char* argv[]) { int argi; int argr = 0; @@ -239,7 +252,7 @@ int main(int argc, char* argv[]) interval = atoi(argv[argi]); else { fxPrintUsage(); - return 1; + return E_BAD_USAGE; } } else if (!strcmp(argv[argi], "-l")) { @@ -249,11 +262,11 @@ int main(int argc, char* argv[]) gxCrankMeteringLimit = atoi(argv[argi]); else { fxPrintUsage(); - return 1; + return E_BAD_USAGE; } #else fprintf(stderr, "%s flag not implemented; mxMetering is not enabled\n", argv[argi]); - return 1; + return E_BAD_USAGE; #endif } else if (!strcmp(argv[argi], "-p")) @@ -264,7 +277,7 @@ int main(int argc, char* argv[]) argr = argi; else { fxPrintUsage(); - return 1; + return E_BAD_USAGE; } } else if (!strcmp(argv[argi], "-s")) { @@ -273,15 +286,15 @@ int main(int argc, char* argv[]) parserBufferSize = 1024 * atoi(argv[argi]); else { fxPrintUsage(); - return 1; + return E_BAD_USAGE; } } else if (!strcmp(argv[argi], "-v")) { printf("xsnap %s (XS %d.%d.%d)\n", XSNAP_VERSION, XS_MAJOR_VERSION, XS_MINOR_VERSION, XS_PATCH_VERSION); - return 0; + return E_SUCCESS; } else { fxPrintUsage(); - return 1; + return E_BAD_USAGE; } } xsCreation _creation = { @@ -313,7 +326,7 @@ int main(int argc, char* argv[]) snapshot.error = errno; if (snapshot.error) { fprintf(stderr, "cannot read snapshot %s: %s\n", argv[argr], strerror(snapshot.error)); - return 1; + return E_IO_ERROR; } } else { @@ -329,11 +342,11 @@ int main(int argc, char* argv[]) } if (!(fromParent = fdopen(3, "rb"))) { fprintf(stderr, "fdopen(3) from parent failed\n"); - c_exit(1); + c_exit(E_IO_ERROR); } if (!(toParent = fdopen(4, "wb"))) { fprintf(stderr, "fdopen(4) to parent failed\n"); - c_exit(1); + c_exit(E_IO_ERROR); } xsBeginMetering(machine, fxMeteringCallback, interval); { @@ -353,7 +366,7 @@ int main(int argc, char* argv[]) break; } else { fprintf(stderr, "%s\n", fxReadNetStringError(readError)); - c_exit(1); + c_exit(E_IO_ERROR); } } char command = *nsbuf; @@ -378,7 +391,7 @@ int main(int argc, char* argv[]) xsCatch { if (xsTypeOf(xsException) != xsUndefinedType) { // fprintf(stderr, "%c: %s\n", command, xsToString(xsException)); - error = 1; + error = E_UNHANDLED_EXCEPTION; xsVar(1) = xsException; xsException = xsUndefined; } @@ -423,7 +436,7 @@ int main(int argc, char* argv[]) xsEndHost(machine); if (writeError != 0) { fprintf(stderr, "%s\n", fxWriteNetStringError(writeError)); - c_exit(1); + c_exit(E_IO_ERROR); } break; case 's': @@ -444,7 +457,7 @@ int main(int argc, char* argv[]) xsCatch { if (xsTypeOf(xsException) != xsUndefinedType) { fprintf(stderr, "%s\n", xsToString(xsException)); - error = 1; + error = E_UNHANDLED_EXCEPTION; xsException = xsUndefined; } } @@ -456,14 +469,14 @@ int main(int argc, char* argv[]) int writeError = fxWriteOkay(toParent, meterIndex, "", 0); if (writeError != 0) { fprintf(stderr, "%s\n", fxWriteNetStringError(writeError)); - c_exit(1); + c_exit(E_IO_ERROR); } } else { // TODO: dynamically build error message including Exception message. int writeError = fxWriteNetString(toParent, "!", "", 0); if (writeError != 0) { fprintf(stderr, "%s\n", fxWriteNetStringError(writeError)); - c_exit(1); + c_exit(E_IO_ERROR); } } break; @@ -480,19 +493,20 @@ int main(int argc, char* argv[]) if (snapshot.error) { fprintf(stderr, "cannot write snapshot %s: %s\n", path, strerror(snapshot.error)); + c_exit(E_IO_ERROR); } if (snapshot.error == 0) { int writeError = fxWriteOkay(toParent, meterIndex, "", 0); if (writeError != 0) { fprintf(stderr, "%s\n", fxWriteNetStringError(writeError)); - c_exit(1); + c_exit(E_IO_ERROR); } } else { // TODO: dynamically build error message including Exception message. int writeError = fxWriteNetString(toParent, "!", "", 0); if (writeError != 0) { fprintf(stderr, "%s\n", fxWriteNetStringError(writeError)); - c_exit(1); + c_exit(E_IO_ERROR); } } break; @@ -507,7 +521,7 @@ int main(int argc, char* argv[]) { if (xsTypeOf(xsException) != xsUndefinedType) { fprintf(stderr, "%s\n", xsToString(xsException)); - error = 1; + error = E_UNHANDLED_EXCEPTION; } } xsEndHost(machine); @@ -515,7 +529,10 @@ int main(int argc, char* argv[]) xsEndMetering(machine); xsDeleteMachine(machine); fxTerminateSharedCluster(); - return error; + if (error != E_SUCCESS) { + c_exit(error); + } + return E_SUCCESS; } void fxBuildAgent(xsMachine* the) @@ -1004,28 +1021,28 @@ void fxAbort(txMachine* the, int status) #ifdef mxDebug fxDebugger(the, (char *)__FILE__, __LINE__); #endif - c_exit(status); + c_exit(E_STACK_OVERFLOW); break; case XS_NOT_ENOUGH_MEMORY_EXIT: xsLog("memory full\n"); #ifdef mxDebug fxDebugger(the, (char *)__FILE__, __LINE__); #endif - c_exit(status); + c_exit(E_NOT_ENOUGH_MEMORY); break; case XS_NO_MORE_KEYS_EXIT: xsLog("not enough keys\n"); #ifdef mxDebug fxDebugger(the, (char *)__FILE__, __LINE__); #endif - c_exit(status); + c_exit(E_NO_MORE_KEYS); break; case XS_TOO_MUCH_COMPUTATION_EXIT: xsLog("too much computation\n"); #ifdef mxDebug fxDebugger(the, (char *)__FILE__, __LINE__); #endif - c_exit(status); + c_exit(E_TOO_MUCH_COMPUTATION); break; case XS_UNHANDLED_EXCEPTION_EXIT: case XS_UNHANDLED_REJECTION_EXIT: @@ -1033,7 +1050,8 @@ void fxAbort(txMachine* the, int status) xsException = xsUndefined; break; default: - c_exit(status); + xsLog("fxAbort(%d) - %s\n", status, xsToString(xsException)); + c_exit(E_UNKNOWN_ERROR); break; } } diff --git a/packages/xsnap/src/xsnap.js b/packages/xsnap/src/xsnap.js index 338973e5c1b..a1f5cf94df2 100644 --- a/packages/xsnap/src/xsnap.js +++ b/packages/xsnap/src/xsnap.js @@ -11,6 +11,7 @@ * @typedef {import('./defer').Deferred} Deferred */ +import { ErrorCode, ErrorSignal, ErrorMessage } from './api'; import { defer } from './defer'; import * as netstring from './netstring'; import * as node from './node-stream'; @@ -112,9 +113,19 @@ export function xsnap(options) { if (code === 0) { vatExit.resolve(); } else if (signal !== null) { - vatExit.reject(new Error(`${name} exited due to signal ${signal}`)); + const reason = new ErrorSignal( + signal, + `${name} exited due to signal ${signal}`, + ); + vatExit.reject(reason); + } else if (code === null) { + throw TypeError('null code???'); } else { - vatExit.reject(new Error(`${name} exited with code ${code}`)); + const reason = new ErrorCode( + code, + `${name} exited: ${ErrorMessage[code] || 'unknown error'}`, + ); + vatExit.reject(reason); } }); @@ -168,9 +179,7 @@ export function xsnap(options) { compute = JSON.parse(decoder.decode(meterData)); } const meterUsage = { - // The version identifier for our meter type. - // TODO Bump this whenever there's a change to metering semantics. - meterType: 'xs-meter-1', + meterType: METER_TYPE, allocate: null, // No allocation meter yet. compute, }; diff --git a/packages/xsnap/test/test-xsnap.js b/packages/xsnap/test/test-xsnap.js index 8758e54fcd3..76ef44a5169 100644 --- a/packages/xsnap/test/test-xsnap.js +++ b/packages/xsnap/test/test-xsnap.js @@ -4,6 +4,7 @@ import test from 'ava'; import * as childProcess from 'child_process'; import * as os from 'os'; import { xsnap } from '../src/xsnap'; +import { ExitCode, ErrorCode } from '../src/api'; const importMetaUrl = `file://${__filename}`; @@ -52,8 +53,8 @@ test('evaluate infinite loop', async t => { const vat = xsnap(opts); t.teardown(vat.terminate); await t.throwsAsync(vat.evaluate(`for (;;) {}`), { - message: /xsnap test worker exited with code 7/, - instanceOf: Error, + code: ExitCode.E_TOO_MUCH_COMPUTATION, + instanceOf: ErrorCode, }); t.deepEqual([], opts.messages); }); @@ -71,8 +72,8 @@ test('evaluate promise loop', async t => { f(); `), { - message: /exited with code 7/, - instanceOf: Error, + code: ExitCode.E_TOO_MUCH_COMPUTATION, + instanceOf: ErrorCode, }, ); t.deepEqual([], opts.messages); @@ -402,7 +403,9 @@ test('heap exhaustion: orderly fail-stop', async t => { const vat = xsnap({ ...xsnapOptions, meteringLimit: 0, debug }); t.teardown(() => vat.terminate()); // eslint-disable-next-line no-await-in-loop - await t.throwsAsync(vat.evaluate(grow), { message: /exited with code 1$/ }); + await t.throwsAsync(vat.evaluate(grow), { + code: ExitCode.E_NOT_ENOUGH_MEMORY, + }); } }); @@ -432,7 +435,7 @@ test('property name space exhaustion: orderly fail-stop', async t => { console.log({ debug, qty: 4000000000 }); // eslint-disable-next-line no-await-in-loop await t.throwsAsync(vat.evaluate(grow(4000000000)), { - message: /exited with code 6/, + code: ExitCode.E_NO_MORE_KEYS, }); } }); @@ -497,7 +500,7 @@ test('property name space exhaustion: orderly fail-stop', async t => { // ignore } })()`), - { message: /exited with code 1$/ }, + { code: ExitCode.E_NOT_ENOUGH_MEMORY }, ); }); }