Skip to content

Commit

Permalink
process: implement resource limits.
Browse files Browse the repository at this point in the history
  • Loading branch information
chjj committed Mar 13, 2019
1 parent fb36b9d commit 2d6b7ab
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 2 deletions.
5 changes: 5 additions & 0 deletions lib/internal/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ const errors = {
'MessagePort was found in message but not listed in transferList'
],

OUT_OF_MEMORY: [
'ERR_WORKER_OUT_OF_MEMORY',
'Worker terminated due to reaching memory limit'
],

// Custom Worker Errors
BUNDLED_EVAL: [
'ERR_WORKER_BUNDLED_EVAL',
Expand Down
6 changes: 5 additions & 1 deletion lib/process/flags.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,11 @@ const invalidOptions = new Set([
'--completion-bash',
'-h', '--help',
'-v', '--version',
'--v8-options'
'--v8-options',

// To filter out resource limits:
'--max-old-space-size',
'--max-semi-space-size'
]);

// At some point in the future, --frozen-intrinsics
Expand Down
12 changes: 12 additions & 0 deletions lib/process/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,21 @@ class Parser extends EventEmitter {
this.header = null;
this.pending = [];
this.total = 0;
this.closed = false;
}

destroy() {
this.closed = true;
this.waiting = -1 >>> 0;
this.header = null;
this.pending.length = 0;
this.total = 0;
}

feed(data) {
if (this.closed)
return;

this.total += data.length;
this.pending.push(data);

Expand Down
39 changes: 38 additions & 1 deletion lib/process/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,16 @@ class Worker extends EventEmitter {
if (options.execArgv && !Array.isArray(options.execArgv))
throw new ArgError('execArgv', options.execArgv, 'Array');

if (options.resourceLimits && typeof options.resourceLimits !== 'object')
throw new ArgError('resourceLimits', options.resourceLimits, 'object');

this._child = null;
this._parser = new Parser(this);
this._ports = new Map();
this._writable = true;
this._exited = false;
this._killed = false;
this._limits = false;
this._exitCode = -1;
this._stdioRef = null;
this._stdioRefs = 0;
Expand All @@ -104,6 +108,7 @@ class Worker extends EventEmitter {

_init(file, options) {
const bin = process.execPath || process.argv[0];
const limits = options.resourceLimits;
const args = [];

// Validate filename.
Expand Down Expand Up @@ -181,6 +186,24 @@ class Worker extends EventEmitter {
}
}

// Enforce resource limits.
if (limits) {
const maxOld = limits.maxOldSpaceSizeMb;
const maxSemi = limits.maxSemiSpaceSizeMb;

if (typeof maxOld === 'number') {
args.push(`--max-old-space-size=${Math.max(maxOld, 2)}`);
this._limits = true;
}

if (typeof maxSemi === 'number') {
args.push(`--max-semi-space-size=${maxSemi}`);
this._limits = true;
}

// Todo: figure out how to do codeRangeSizeMb.
}

// Require bthreads on boot, but make
// sure we're not bundled or something.
if (!hasRequireArg(args, __dirname)) {
Expand Down Expand Up @@ -298,7 +321,18 @@ class Worker extends EventEmitter {
});

this._parser.on('error', (err) => {
this.emit('error', err);
this._parser.destroy();

if (this._limits) {
// Probably an OOM:
// v8 writes the last few GC attempts to stdout,
// and then writes some debugging info to stderr.
this.emit('error', new WorkerError(errors.OUT_OF_MEMORY));
} else {
this.emit('error', err);
}

this._kill(1);
});

this._parser.on('packet', (pkt) => {
Expand Down Expand Up @@ -403,6 +437,9 @@ class Worker extends EventEmitter {
}

_handleExit(code, signal) {
if (signal === 'SIGABRT')
code = 1;

// Child was terminated with signal handler.
if (code === 143 && signal == null) {
// Convert to SIGTERM signal.
Expand Down
40 changes: 40 additions & 0 deletions test/threads-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1074,4 +1074,44 @@ describe(`Threads (${threads.backend})`, (ctx) => {

await pool.close();
});

it('should die on resource limit excess', async (ctx) => {
if (threads.backend !== 'child_process')
ctx.skip();

const func = () => setInterval(() => {}, 1000);

const worker = new threads.Worker(`(${func})();`, {
eval: true,
resourceLimits: {
maxOldSpaceSizeMb: 2
}
});

const err = await waitFor(worker, 'error');

assert(err);
assert.strictEqual(err.code, 'ERR_WORKER_OUT_OF_MEMORY');
assert.strictEqual(await wait(worker), 1);
});

it('should clean reports', (ctx) => {
// v8 writes files like "report.20190313.143017.5753.001.json"
// when it dies from an oom.
if (threads.backend !== 'child_process')
ctx.skip();

const fs = require('fs');
const dir = join(__dirname, '..');

for (const name of fs.readdirSync(dir)) {
if (!name.endsWith('.json'))
continue;

if (!name.startsWith('report.'))
continue;

fs.unlinkSync(join(dir, name));
}
});
});

0 comments on commit 2d6b7ab

Please sign in to comment.