Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 9173b09

Browse filesBrowse files
committed
worker: add stack size resource limit option
Add `stackSizeMb` to the `resourceLimit` option group. Refs: #31593 (comment) PR-URL: #33085 Reviewed-By: Gireesh Punathil <gpunathi@in.ibm.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
1 parent b0c1aca commit 9173b09
Copy full SHA for 9173b09

File tree

Expand file treeCollapse file tree

6 files changed

+61
-20
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

6 files changed

+61
-20
lines changed
Open diff view settings
Collapse file

‎doc/api/worker_threads.md‎

Copy file name to clipboardExpand all lines: doc/api/worker_threads.md
+4Lines changed: 4 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ added: v12.16.0
174174
* `maxYoungGenerationSizeMb` {number}
175175
* `maxOldGenerationSizeMb` {number}
176176
* `codeRangeSizeMb` {number}
177+
* `stackSizeMb` {number}
177178

178179
Provides the set of JS engine resource constraints inside this Worker thread.
179180
If the `resourceLimits` option was passed to the [`Worker`][] constructor,
@@ -625,6 +626,8 @@ changes:
625626
recently created objects.
626627
* `codeRangeSizeMb` {number} The size of a pre-allocated memory range
627628
used for generated code.
629+
* `stackSizeMb` {number} The default maximum stack size for the thread.
630+
Small values may lead to unusable Worker instances. **Default:** `4`.
628631

629632
### Event: `'error'`
630633
<!-- YAML
@@ -718,6 +721,7 @@ added: v12.16.0
718721
* `maxYoungGenerationSizeMb` {number}
719722
* `maxOldGenerationSizeMb` {number}
720723
* `codeRangeSizeMb` {number}
724+
* `stackSizeMb` {number}
721725

722726
Provides the set of JS engine resource constraints for this Worker thread.
723727
If the `resourceLimits` option was passed to the [`Worker`][] constructor,
Collapse file

‎lib/internal/worker.js‎

Copy file name to clipboardExpand all lines: lib/internal/worker.js
+5-1Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ const {
5454
kMaxYoungGenerationSizeMb,
5555
kMaxOldGenerationSizeMb,
5656
kCodeRangeSizeMb,
57+
kStackSizeMb,
5758
kTotalResourceLimitCount
5859
} = internalBinding('worker');
5960

@@ -385,14 +386,17 @@ function parseResourceLimits(obj) {
385386
ret[kMaxYoungGenerationSizeMb] = obj.maxYoungGenerationSizeMb;
386387
if (typeof obj.codeRangeSizeMb === 'number')
387388
ret[kCodeRangeSizeMb] = obj.codeRangeSizeMb;
389+
if (typeof obj.stackSizeMb === 'number')
390+
ret[kStackSizeMb] = obj.stackSizeMb;
388391
return ret;
389392
}
390393

391394
function makeResourceLimits(float64arr) {
392395
return {
393396
maxYoungGenerationSizeMb: float64arr[kMaxYoungGenerationSizeMb],
394397
maxOldGenerationSizeMb: float64arr[kMaxOldGenerationSizeMb],
395-
codeRangeSizeMb: float64arr[kCodeRangeSizeMb]
398+
codeRangeSizeMb: float64arr[kCodeRangeSizeMb],
399+
stackSizeMb: float64arr[kStackSizeMb]
396400
};
397401
}
398402

Collapse file

‎src/node_worker.cc‎

Copy file name to clipboardExpand all lines: src/node_worker.cc
+16-4Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ using v8::Value;
3939
namespace node {
4040
namespace worker {
4141

42+
constexpr double kMB = 1024 * 1024;
43+
4244
Worker::Worker(Environment* env,
4345
Local<Object> wrap,
4446
const std::string& url,
@@ -98,8 +100,6 @@ std::shared_ptr<ArrayBufferAllocator> Worker::array_buffer_allocator() {
98100
void Worker::UpdateResourceConstraints(ResourceConstraints* constraints) {
99101
constraints->set_stack_limit(reinterpret_cast<uint32_t*>(stack_base_));
100102

101-
constexpr double kMB = 1024 * 1024;
102-
103103
if (resource_limits_[kMaxYoungGenerationSizeMb] > 0) {
104104
constraints->set_max_young_generation_size_in_bytes(
105105
resource_limits_[kMaxYoungGenerationSizeMb] * kMB);
@@ -595,9 +595,20 @@ void Worker::StartThread(const FunctionCallbackInfo<Value>& args) {
595595

596596
w->stopped_ = false;
597597

598+
if (w->resource_limits_[kStackSizeMb] > 0) {
599+
if (w->resource_limits_[kStackSizeMb] * kMB < kStackBufferSize) {
600+
w->resource_limits_[kStackSizeMb] = kStackBufferSize / kMB;
601+
w->stack_size_ = kStackBufferSize;
602+
} else {
603+
w->stack_size_ = w->resource_limits_[kStackSizeMb] * kMB;
604+
}
605+
} else {
606+
w->resource_limits_[kStackSizeMb] = w->stack_size_ / kMB;
607+
}
608+
598609
uv_thread_options_t thread_options;
599610
thread_options.flags = UV_THREAD_HAS_STACK_SIZE;
600-
thread_options.stack_size = kStackSize;
611+
thread_options.stack_size = w->stack_size_;
601612
int ret = uv_thread_create_ex(&w->tid_, &thread_options, [](void* arg) {
602613
// XXX: This could become a std::unique_ptr, but that makes at least
603614
// gcc 6.3 detect undefined behaviour when there shouldn't be any.
@@ -607,7 +618,7 @@ void Worker::StartThread(const FunctionCallbackInfo<Value>& args) {
607618

608619
// Leave a few kilobytes just to make sure we're within limits and have
609620
// some space to do work in C++ land.
610-
w->stack_base_ = stack_top - (kStackSize - kStackBufferSize);
621+
w->stack_base_ = stack_top - (w->stack_size_ - kStackBufferSize);
611622

612623
w->Run();
613624

@@ -837,6 +848,7 @@ void InitWorker(Local<Object> target,
837848
NODE_DEFINE_CONSTANT(target, kMaxYoungGenerationSizeMb);
838849
NODE_DEFINE_CONSTANT(target, kMaxOldGenerationSizeMb);
839850
NODE_DEFINE_CONSTANT(target, kCodeRangeSizeMb);
851+
NODE_DEFINE_CONSTANT(target, kStackSizeMb);
840852
NODE_DEFINE_CONSTANT(target, kTotalResourceLimitCount);
841853
}
842854

Collapse file

‎src/node_worker.h‎

Copy file name to clipboardExpand all lines: src/node_worker.h
+2-1Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ enum ResourceLimits {
1616
kMaxYoungGenerationSizeMb,
1717
kMaxOldGenerationSizeMb,
1818
kCodeRangeSizeMb,
19+
kStackSizeMb,
1920
kTotalResourceLimitCount
2021
};
2122

@@ -97,7 +98,7 @@ class Worker : public AsyncWrap {
9798
void UpdateResourceConstraints(v8::ResourceConstraints* constraints);
9899

99100
// Full size of the thread's stack.
100-
static constexpr size_t kStackSize = 4 * 1024 * 1024;
101+
size_t stack_size_ = 4 * 1024 * 1024;
101102
// Stack buffer size that is not available to the JS engine.
102103
static constexpr size_t kStackBufferSize = 192 * 1024;
103104

Collapse file

‎test/parallel/test-worker-resource-limits.js‎

Copy file name to clipboardExpand all lines: test/parallel/test-worker-resource-limits.js
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const testResourceLimits = {
1212
maxOldGenerationSizeMb: 16,
1313
maxYoungGenerationSizeMb: 4,
1414
codeRangeSizeMb: 16,
15+
stackSizeMb: 1,
1516
};
1617

1718
// Do not use isMainThread so that this test itself can be run inside a Worker.
Collapse file

‎test/parallel/test-worker-stack-overflow-stack-size.js‎

Copy file name to clipboardExpand all lines: test/parallel/test-worker-stack-overflow-stack-size.js
+33-14Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const { Worker } = require('worker_threads');
88
// Verify that Workers don't care about --stack-size, as they have their own
99
// fixed and known stack sizes.
1010

11-
async function runWorker() {
11+
async function runWorker(options = {}) {
1212
const empiricalStackDepth = new Uint32Array(new SharedArrayBuffer(4));
1313
const worker = new Worker(`
1414
const { workerData: { empiricalStackDepth } } = require('worker_threads');
@@ -18,26 +18,45 @@ async function runWorker() {
1818
}
1919
f();`, {
2020
eval: true,
21-
workerData: { empiricalStackDepth }
21+
workerData: { empiricalStackDepth },
22+
...options
2223
});
2324

2425
const [ error ] = await once(worker, 'error');
2526

26-
common.expectsError({
27-
constructor: RangeError,
28-
message: 'Maximum call stack size exceeded'
29-
})(error);
27+
if (!options.skipErrorCheck) {
28+
common.expectsError({
29+
constructor: RangeError,
30+
message: 'Maximum call stack size exceeded'
31+
})(error);
32+
}
3033

3134
return empiricalStackDepth[0];
3235
}
3336

3437
(async function() {
35-
v8.setFlagsFromString('--stack-size=500');
36-
const w1stack = await runWorker();
37-
v8.setFlagsFromString('--stack-size=1000');
38-
const w2stack = await runWorker();
39-
// Make sure the two stack sizes are within 10 % of each other, i.e. not
40-
// affected by the different `--stack-size` settings.
41-
assert(Math.max(w1stack, w2stack) / Math.min(w1stack, w2stack) < 1.1,
42-
`w1stack = ${w1stack}, w2stack ${w2stack} are too far apart`);
38+
{
39+
v8.setFlagsFromString('--stack-size=500');
40+
const w1stack = await runWorker();
41+
v8.setFlagsFromString('--stack-size=1000');
42+
const w2stack = await runWorker();
43+
// Make sure the two stack sizes are within 10 % of each other, i.e. not
44+
// affected by the different `--stack-size` settings.
45+
assert(Math.max(w1stack, w2stack) / Math.min(w1stack, w2stack) < 1.1,
46+
`w1stack = ${w1stack}, w2stack = ${w2stack} are too far apart`);
47+
}
48+
49+
{
50+
const w1stack = await runWorker({ resourceLimits: { stackSizeMb: 0.5 } });
51+
const w2stack = await runWorker({ resourceLimits: { stackSizeMb: 1.0 } });
52+
// Make sure the two stack sizes are at least 40 % apart from each other,
53+
// i.e. affected by the different `stackSizeMb` settings.
54+
assert(w2stack > w1stack * 1.4,
55+
`w1stack = ${w1stack}, w2stack = ${w2stack} are too close`);
56+
}
57+
58+
// Test that various low stack sizes result in an 'error' event.
59+
for (const stackSizeMb of [ 0.001, 0.01, 0.1, 0.2, 0.3, 0.5 ]) {
60+
await runWorker({ resourceLimits: { stackSizeMb }, skipErrorCheck: true });
61+
}
4362
})().then(common.mustCall());

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.