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 6f3bc0d

Browse filesBrowse files
jasnellMylesBorins
authored andcommitted
doc, test: document and test vm timeout escapes
Using `process.nextTick()` or `Promise`, 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 two 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 3170cb4 commit 6f3bc0d
Copy full SHA for 6f3bc0d

File tree

Expand file treeCollapse file tree

3 files changed

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

3 files changed

+112
-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
@@ -944,6 +944,38 @@ within which it can operate. The process of creating the V8 Context and
944944
associating it with the `sandbox` object is what this document refers to as
945945
"contextifying" the `sandbox`.
946946

947+
## Timeout limitations when using process.nextTick(), and Promises
948+
949+
Because of the internal mechanics of how the `process.nextTick()` queue and
950+
the microtask queue that underlies Promises are implemented within V8 and
951+
Node.js, it is possible for code running within a context to "escape" the
952+
`timeout` set using `vm.runInContext()`, `vm.runInNewContext()`, and
953+
`vm.runInThisContext()`.
954+
955+
For example, the following code executed by `vm.runInNewContext()` with a
956+
timeout of 5 milliseconds schedules an infinite loop to run after a promise
957+
resolves. The scheduled loop is never interrupted by the timeout:
958+
959+
```js
960+
const vm = require('vm');
961+
962+
function loop() {
963+
while (1) console.log(Date.now());
964+
}
965+
966+
vm.runInNewContext(
967+
'Promise.resolve().then(loop);',
968+
{ loop, console },
969+
{ timeout: 5 }
970+
);
971+
```
972+
973+
This issue also occurs when the `loop()` call is scheduled using
974+
the `process.nextTick()` function.
975+
976+
This issue occurs because all contexts share the same microtask and nextTick
977+
queues.
978+
947979
[`Error`]: errors.html#errors_class_error
948980
[`URL`]: url.html#url_class_url
949981
[`eval()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
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+
});

0 commit comments

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