Skip to content

Commit

Permalink
feat(xsnap): specify exit codes for meter exhaustion etc.
Browse files Browse the repository at this point in the history
 - export api constants so consumers can avoid node.js modules
 - export METER_TYPE
  • Loading branch information
dckc committed Apr 28, 2021
1 parent 1e50fa4 commit db3daaa
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 38 deletions.
53 changes: 53 additions & 0 deletions packages/xsnap/src/api.js
Original file line number Diff line number Diff line change
@@ -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;
}
}
7 changes: 7 additions & 0 deletions packages/xsnap/src/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
export { xsnap } from './xsnap';
export {
ExitCode,
ErrorMessage,
ErrorCode,
ErrorSignal,
METER_TYPE,
} from './api';
export { makeSnapstore } from './snapStore';
70 changes: 44 additions & 26 deletions packages/xsnap/src/xsnap.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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")) {
Expand All @@ -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"))
Expand All @@ -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")) {
Expand All @@ -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 = {
Expand Down Expand Up @@ -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 {
Expand All @@ -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);
{
Expand All @@ -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;
Expand All @@ -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;
}
Expand Down Expand Up @@ -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':
Expand All @@ -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;
}
}
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -507,15 +521,18 @@ int main(int argc, char* argv[])
{
if (xsTypeOf(xsException) != xsUndefinedType) {
fprintf(stderr, "%s\n", xsToString(xsException));
error = 1;
error = E_UNHANDLED_EXCEPTION;
}
}
xsEndHost(machine);
}
xsEndMetering(machine);
xsDeleteMachine(machine);
fxTerminateSharedCluster();
return error;
if (error != E_SUCCESS) {
c_exit(error);
}
return E_SUCCESS;
}

void fxBuildAgent(xsMachine* the)
Expand Down Expand Up @@ -1004,36 +1021,37 @@ 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:
xsLog("%s\n", xsToString(xsException));
xsException = xsUndefined;
break;
default:
c_exit(status);
xsLog("fxAbort(%d) - %s\n", status, xsToString(xsException));
c_exit(E_UNKNOWN_ERROR);
break;
}
}
Expand Down
19 changes: 14 additions & 5 deletions packages/xsnap/src/xsnap.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* @typedef {import('./defer').Deferred<T>} Deferred
*/

import { ErrorCode, ErrorSignal, ErrorMessage } from './api';
import { defer } from './defer';
import * as netstring from './netstring';
import * as node from './node-stream';
Expand Down Expand Up @@ -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);
}
});

Expand Down Expand Up @@ -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,
};
Expand Down
17 changes: 10 additions & 7 deletions packages/xsnap/test/test-xsnap.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}`;

Expand Down Expand Up @@ -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);
});
Expand All @@ -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);
Expand Down Expand Up @@ -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,
});
}
});

Expand Down Expand Up @@ -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,
});
}
});
Expand Down Expand Up @@ -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 },
);
});
}
Expand Down

0 comments on commit db3daaa

Please sign in to comment.