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 1851cf4

Browse filesBrowse files
jasnelltargos
authored andcommitted
doc, test: document and test vm timeout escapes
Using `process.nextTick()`, `Promise`, or `queueMicrotask()`, it is possible to escape the `timeout` set when running code with `vm.runInContext()`, `vm.runInThisContext()`, and `vm.runInNewContext()`. This documents the issue and adds three known_issues tests. Refs: #3020 PR-URL: #23743 Refs: #3020 Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com>
1 parent 97496f0 commit 1851cf4
Copy full SHA for 1851cf4

File tree

Expand file treeCollapse file tree

4 files changed

+152
-0
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

4 files changed

+152
-0
lines changed
Open diff view settings
Collapse file

‎doc/api/vm.md‎

Copy file name to clipboardExpand all lines: doc/api/vm.md
+32Lines changed: 32 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -962,6 +962,38 @@ within which it can operate. The process of creating the V8 Context and
962962
associating it with the `sandbox` object is what this document refers to as
963963
"contextifying" the `sandbox`.
964964

965+
## Timeout limitations when using process.nextTick(), Promises, and queueMicrotask()
966+
967+
Because of the internal mechanics of how the `process.nextTick()` queue and
968+
the microtask queue that underlies Promises are implemented within V8 and
969+
Node.js, it is possible for code running within a context to "escape" the
970+
`timeout` set using `vm.runInContext()`, `vm.runInNewContext()`, and
971+
`vm.runInThisContext()`.
972+
973+
For example, the following code executed by `vm.runInNewContext()` with a
974+
timeout of 5 milliseconds schedules an infinite loop to run after a promise
975+
resolves. The scheduled loop is never interrupted by the timeout:
976+
977+
```js
978+
const vm = require('vm');
979+
980+
function loop() {
981+
while (1) console.log(Date.now());
982+
}
983+
984+
vm.runInNewContext(
985+
'Promise.resolve().then(loop);',
986+
{ loop, console },
987+
{ timeout: 5 }
988+
);
989+
```
990+
991+
This issue also occurs when the `loop()` call is scheduled using
992+
the `process.nextTick()` and `queueMicrotask()` functions.
993+
994+
This issue occurs because all contexts share the same microtask and nextTick
995+
queues.
996+
965997
[`Error`]: errors.html#errors_class_error
966998
[`ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING`]: errors.html#ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING
967999
[`URL`]: url.html#url_class_url
Collapse file
+41Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
'use strict';
2+
3+
// https://github.com/nodejs/node/issues/3020
4+
// Promises, nextTick, and queueMicrotask allow code to escape the timeout
5+
// set for runInContext, runInNewContext, and runInThisContext
6+
7+
require('../common');
8+
const assert = require('assert');
9+
const vm = require('vm');
10+
11+
const NS_PER_MS = 1000000n;
12+
13+
const hrtime = process.hrtime.bigint;
14+
const nextTick = process.nextTick;
15+
16+
function loop() {
17+
const start = hrtime();
18+
while (1) {
19+
const current = hrtime();
20+
const span = (current - start) / NS_PER_MS;
21+
if (span >= 100n) {
22+
throw new Error(
23+
`escaped timeout at ${span} milliseconds!`);
24+
}
25+
}
26+
}
27+
28+
assert.throws(() => {
29+
vm.runInNewContext(
30+
'nextTick(loop); loop();',
31+
{
32+
hrtime,
33+
nextTick,
34+
loop
35+
},
36+
{ timeout: 5 }
37+
);
38+
}, {
39+
code: 'ERR_SCRIPT_EXECUTION_TIMEOUT',
40+
message: 'Script execution timed out after 5ms'
41+
});
Collapse file
+39Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
'use strict';
2+
3+
// https://github.com/nodejs/node/issues/3020
4+
// Promises, nextTick, and queueMicrotask allow code to escape the timeout
5+
// set for runInContext, runInNewContext, and runInThisContext
6+
7+
require('../common');
8+
const assert = require('assert');
9+
const vm = require('vm');
10+
11+
const NS_PER_MS = 1000000n;
12+
13+
const hrtime = process.hrtime.bigint;
14+
15+
function loop() {
16+
const start = hrtime();
17+
while (1) {
18+
const current = hrtime();
19+
const span = (current - start) / NS_PER_MS;
20+
if (span >= 100n) {
21+
throw new Error(
22+
`escaped timeout at ${span} milliseconds!`);
23+
}
24+
}
25+
}
26+
27+
assert.throws(() => {
28+
vm.runInNewContext(
29+
'Promise.resolve().then(loop); loop();',
30+
{
31+
hrtime,
32+
loop
33+
},
34+
{ timeout: 5 }
35+
);
36+
}, {
37+
code: 'ERR_SCRIPT_EXECUTION_TIMEOUT',
38+
message: 'Script execution timed out after 5ms'
39+
});
Collapse file
+40Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
'use strict';
2+
3+
// https://github.com/nodejs/node/issues/3020
4+
// Promises, nextTick, and queueMicrotask allow code to escape the timeout
5+
// set for runInContext, runInNewContext, and runInThisContext
6+
7+
require('../common');
8+
const assert = require('assert');
9+
const vm = require('vm');
10+
11+
const NS_PER_MS = 1000000n;
12+
13+
const hrtime = process.hrtime.bigint;
14+
15+
function loop() {
16+
const start = hrtime();
17+
while (1) {
18+
const current = hrtime();
19+
const span = (current - start) / NS_PER_MS;
20+
if (span >= 100n) {
21+
throw new Error(
22+
`escaped timeout at ${span} milliseconds!`);
23+
}
24+
}
25+
}
26+
27+
assert.throws(() => {
28+
vm.runInNewContext(
29+
'queueMicrotask(loop); loop();',
30+
{
31+
hrtime,
32+
queueMicrotask,
33+
loop
34+
},
35+
{ timeout: 5 }
36+
);
37+
}, {
38+
code: 'ERR_SCRIPT_EXECUTION_TIMEOUT',
39+
message: 'Script execution timed out after 5ms'
40+
});

0 commit comments

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