Skip to content

Commit 886ef09

Browse files
addaleaxMylesBorins
authored andcommitted
worker: allow specifying resource limits
Allow specifying resource limits for the JS engine instance created as part of a Worker. PR-URL: #26628 Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Gireesh Punathil <gpunathi@in.ibm.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Franziska Hinkelmann <franziska.hinkelmann@gmail.com>
1 parent fcc4bf9 commit 886ef09

8 files changed

Lines changed: 315 additions & 26 deletions

File tree

‎doc/api/errors.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1990,6 +1990,11 @@ meaning of the error depends on the specific function.
19901990
The `execArgv` option passed to the `Worker` constructor contains
19911991
invalid flags.
19921992

1993+
<aid="ERR_WORKER_OUT_OF_MEMORY"></a>
1994+
### `ERR_WORKER_OUT_OF_MEMORY`
1995+
1996+
The `Worker` instance terminated because it reached its memory limit.
1997+
19931998
<aid="ERR_WORKER_PATH"></a>
19941999
### `ERR_WORKER_PATH`
19952000

‎doc/api/worker_threads.md‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,22 @@ console.log(receiveMessageOnPort(port2));
157157
When this function is used, no `'message'` event will be emitted and the
158158
`onmessage` listener will not be invoked.
159159

160+
### `worker.resourceLimits`
161+
<!-- YAML
162+
added: REPLACEME
163+
-->
164+
165+
* {Object|undefined}
166+
*`maxYoungGenerationSizeMb` {number}
167+
*`maxOldGenerationSizeMb` {number}
168+
*`codeRangeSizeMb` {number}
169+
170+
Provides the set of JS engine resource constraints inside this Worker thread.
171+
If the `resourceLimits` option was passed to the [`Worker`][] constructor,
172+
this matches its values.
173+
174+
If this is used in the main thread, its value is an empty object.
175+
160176
## `worker.SHARE_ENV`
161177
<!-- YAML
162178
added: v11.14.0
@@ -488,6 +504,13 @@ if (isMainThread) {
488504
```
489505

490506
### `new Worker(filename[, options])`
507+
<!-- YAML
508+
added: v10.5.0
509+
changes:
510+
- version: REPLACEME
511+
pr-url: https://github.com/nodejs/node/pull/26628
512+
description: The `resourceLimits` option was introduced.
513+
-->
491514

492515
*`filename` {string} The path to the Worker’s main script. Must be
493516
either an absolute path or a relative path (i.e. relative to the
@@ -519,6 +542,16 @@ if (isMainThread) {
519542
occur as described in the [HTML structured clone algorithm][], and an error
520543
will be thrown if the object cannot be cloned (e.g. because it contains
521544
`function`s).
545+
*`resourceLimits` {Object} An optional set of resource limits for the new
546+
JS engine instance. Reaching these limits will lead to termination of the
547+
`Worker` instance. These limits only affect the JS engine, and no external
548+
data, including no `ArrayBuffer`s. Even if these limits are set, the process
549+
may still abort if it encounters a global out-of-memory situation.
550+
*`maxOldGenerationSizeMb` {number} The maximum size of the main heap in MB.
551+
*`maxYoungGenerationSizeMb` {number} The maximum size of a heap space for
552+
recently created objects.
553+
*`codeRangeSizeMb` {number} The size of a pre-allocated memory range
554+
used for generated code.
522555

523556
### Event: `'error'`
524557
<!-- YAML
@@ -583,6 +616,21 @@ Opposite of `unref()`, calling `ref()` on a previously `unref()`ed worker will
583616
behavior). If the worker is `ref()`ed, calling `ref()` again will have
584617
no effect.
585618

619+
### `worker.resourceLimits`
620+
<!-- YAML
621+
added: REPLACEME
622+
-->
623+
624+
* {Object}
625+
*`maxYoungGenerationSizeMb` {number}
626+
*`maxOldGenerationSizeMb` {number}
627+
*`codeRangeSizeMb` {number}
628+
629+
Provides the set of JS engine resource constraints for this Worker thread.
630+
If the `resourceLimits` option was passed to the [`Worker`][] constructor,
631+
this matches its values.
632+
633+
If the worker has stopped, the return value is an empty object.
586634
### `worker.stderr`
587635
<!-- YAML
588636
added: v10.5.0

‎lib/internal/errors.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1218,6 +1218,8 @@ E('ERR_VM_MODULE_STATUS', 'Module status %s', Error);
12181218
E('ERR_WORKER_INVALID_EXEC_ARGV',(errors)=>
12191219
`Initiated Worker with invalid execArgv flags: ${errors.join(', ')}`,
12201220
Error);
1221+
E('ERR_WORKER_OUT_OF_MEMORY','Worker terminated due to reaching memory limit',
1222+
Error);
12211223
E('ERR_WORKER_PATH',
12221224
'The worker script filename must be an absolute path or a relative '+
12231225
'path starting with \'./\' or \'../\'. Received "%s"',

‎lib/internal/worker.js‎

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,20 @@
22

33
/* global SharedArrayBuffer */
44

5-
const{ Object }=primordials;
5+
const{Math,Object }=primordials;
66

77
constEventEmitter=require('events');
88
constassert=require('internal/assert');
99
constpath=require('path');
1010

11+
consterrorCodes=require('internal/errors').codes;
1112
const{
1213
ERR_WORKER_PATH,
1314
ERR_WORKER_UNSERIALIZABLE_ERROR,
1415
ERR_WORKER_UNSUPPORTED_EXTENSION,
1516
ERR_WORKER_INVALID_EXEC_ARGV,
1617
ERR_INVALID_ARG_TYPE,
17-
}=require('internal/errors').codes;
18+
}=errorCodes;
1819
const{ validateString }=require('internal/validators');
1920
const{ getOptionValue }=require('internal/options');
2021

@@ -37,8 +38,13 @@ const { pathToFileURL } = require('url');
3738
const{
3839
ownsProcessState,
3940
isMainThread,
41+
resourceLimits: resourceLimitsRaw,
4042
threadId,
4143
Worker: WorkerImpl,
44+
kMaxYoungGenerationSizeMb,
45+
kMaxOldGenerationSizeMb,
46+
kCodeRangeSizeMb,
47+
kTotalResourceLimitCount
4248
}=internalBinding('worker');
4349

4450
constkHandle=Symbol('kHandle');
@@ -102,7 +108,8 @@ class Worker extends EventEmitter {
102108

103109
consturl=options.eval ? null : pathToFileURL(filename);
104110
// Set up the C++ handle for the worker, as well as some internal wiring.
105-
this[kHandle]=newWorkerImpl(url,options.execArgv);
111+
this[kHandle]=newWorkerImpl(url,options.execArgv,
112+
parseResourceLimits(options.resourceLimits));
106113
if(this[kHandle].invalidExecArgv){
107114
thrownewERR_WORKER_INVALID_EXEC_ARGV(this[kHandle].invalidExecArgv);
108115
}
@@ -113,7 +120,7 @@ class Worker extends EventEmitter {
113120
}elseif(env!==undefined){
114121
this[kHandle].setEnvVars(env);
115122
}
116-
this[kHandle].onexit=(code)=>this[kOnExit](code);
123+
this[kHandle].onexit=(code,customErr)=>this[kOnExit](code,customErr);
117124
this[kPort]=this[kHandle].messagePort;
118125
this[kPort].on('message',(data)=>this[kOnMessage](data));
119126
this[kPort].start();
@@ -157,11 +164,15 @@ class Worker extends EventEmitter {
157164
this[kHandle].startThread();
158165
}
159166

160-
[kOnExit](code){
167+
[kOnExit](code,customErr){
161168
debug(`[${threadId}] hears end event for Worker ${this.threadId}`);
162169
drainMessagePort(this[kPublicPort]);
163170
drainMessagePort(this[kPort]);
164171
this[kDispose]();
172+
if(customErr){
173+
debug(`[${threadId}] failing with custom error ${customErr}`);
174+
this.emit('error',newerrorCodes[customErr]());
175+
}
165176
this.emit('exit',code);
166177
this.removeAllListeners();
167178
}
@@ -280,6 +291,12 @@ class Worker extends EventEmitter {
280291
getstderr(){
281292
returnthis[kParentSideStdio].stderr;
282293
}
294+
295+
getresourceLimits(){
296+
if(this[kHandle]===null)return{};
297+
298+
returnmakeResourceLimits(this[kHandle].getResourceLimits());
299+
}
283300
}
284301

285302
functionpipeWithoutWarning(source,dest){
@@ -294,10 +311,35 @@ function pipeWithoutWarning(source, dest) {
294311
dest._maxListeners=destMaxListeners;
295312
}
296313

314+
constresourceLimitsArray=newFloat64Array(kTotalResourceLimitCount);
315+
functionparseResourceLimits(obj){
316+
constret=resourceLimitsArray;
317+
ret.fill(-1);
318+
if(typeofobj!=='object'||obj===null)returnret;
319+
320+
if(typeofobj.maxOldGenerationSizeMb==='number')
321+
ret[kMaxOldGenerationSizeMb]=Math.max(obj.maxOldGenerationSizeMb,2);
322+
if(typeofobj.maxYoungGenerationSizeMb==='number')
323+
ret[kMaxYoungGenerationSizeMb]=obj.maxYoungGenerationSizeMb;
324+
if(typeofobj.codeRangeSizeMb==='number')
325+
ret[kCodeRangeSizeMb]=obj.codeRangeSizeMb;
326+
returnret;
327+
}
328+
329+
functionmakeResourceLimits(float64arr){
330+
return{
331+
maxYoungGenerationSizeMb: float64arr[kMaxYoungGenerationSizeMb],
332+
maxOldGenerationSizeMb: float64arr[kMaxOldGenerationSizeMb],
333+
codeRangeSizeMb: float64arr[kCodeRangeSizeMb]
334+
};
335+
}
336+
297337
module.exports={
298338
ownsProcessState,
299339
isMainThread,
300340
SHARE_ENV,
341+
resourceLimits:
342+
!isMainThread ? makeResourceLimits(resourceLimitsRaw) : {},
301343
threadId,
302344
Worker,
303345
};

‎lib/worker_threads.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
const{
44
isMainThread,
55
SHARE_ENV,
6+
resourceLimits,
67
threadId,
78
Worker
89
}=require('internal/worker');
@@ -20,6 +21,7 @@ module.exports = {
2021
MessageChannel,
2122
moveMessagePortToContext,
2223
receiveMessageOnPort,
24+
resourceLimits,
2325
threadId,
2426
SHARE_ENV,
2527
Worker,

0 commit comments

Comments
 (0)