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: https://github.com/nodejs/node/issues/3020 PR-URL: https://github.com/nodejs/node/pull/23743 Refs: https://github.com/nodejs/node/issues/3020 Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com>
This commit is contained in:
parent
586daae7fd
commit
5e5a9455f8
@ -962,6 +962,38 @@ within which it can operate. The process of creating the V8 Context and
|
|||||||
associating it with the `sandbox` object is what this document refers to as
|
associating it with the `sandbox` object is what this document refers to as
|
||||||
"contextifying" the `sandbox`.
|
"contextifying" the `sandbox`.
|
||||||
|
|
||||||
|
## Timeout limitations when using process.nextTick(), Promises, and queueMicrotask()
|
||||||
|
|
||||||
|
Because of the internal mechanics of how the `process.nextTick()` queue and
|
||||||
|
the microtask queue that underlies Promises are implemented within V8 and
|
||||||
|
Node.js, it is possible for code running within a context to "escape" the
|
||||||
|
`timeout` set using `vm.runInContext()`, `vm.runInNewContext()`, and
|
||||||
|
`vm.runInThisContext()`.
|
||||||
|
|
||||||
|
For example, the following code executed by `vm.runInNewContext()` with a
|
||||||
|
timeout of 5 milliseconds schedules an infinite loop to run after a promise
|
||||||
|
resolves. The scheduled loop is never interrupted by the timeout:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const vm = require('vm');
|
||||||
|
|
||||||
|
function loop() {
|
||||||
|
while (1) console.log(Date.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
vm.runInNewContext(
|
||||||
|
'Promise.resolve().then(loop);',
|
||||||
|
{ loop, console },
|
||||||
|
{ timeout: 5 }
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
This issue also occurs when the `loop()` call is scheduled using
|
||||||
|
the `process.nextTick()` and `queueMicrotask()` functions.
|
||||||
|
|
||||||
|
This issue occurs because all contexts share the same microtask and nextTick
|
||||||
|
queues.
|
||||||
|
|
||||||
[`Error`]: errors.html#errors_class_error
|
[`Error`]: errors.html#errors_class_error
|
||||||
[`ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING`]: errors.html#ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING
|
[`ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING`]: errors.html#ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING
|
||||||
[`URL`]: url.html#url_class_url
|
[`URL`]: url.html#url_class_url
|
||||||
|
41
test/known_issues/test-vm-timeout-escape-nexttick.js
Normal file
41
test/known_issues/test-vm-timeout-escape-nexttick.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
// https://github.com/nodejs/node/issues/3020
|
||||||
|
// Promises, nextTick, and queueMicrotask allow code to escape the timeout
|
||||||
|
// set for runInContext, runInNewContext, and runInThisContext
|
||||||
|
|
||||||
|
require('../common');
|
||||||
|
const assert = require('assert');
|
||||||
|
const vm = require('vm');
|
||||||
|
|
||||||
|
const NS_PER_MS = 1000000n;
|
||||||
|
|
||||||
|
const hrtime = process.hrtime.bigint;
|
||||||
|
const nextTick = process.nextTick;
|
||||||
|
|
||||||
|
function loop() {
|
||||||
|
const start = hrtime();
|
||||||
|
while (1) {
|
||||||
|
const current = hrtime();
|
||||||
|
const span = (current - start) / NS_PER_MS;
|
||||||
|
if (span >= 100n) {
|
||||||
|
throw new Error(
|
||||||
|
`escaped timeout at ${span} milliseconds!`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.throws(() => {
|
||||||
|
vm.runInNewContext(
|
||||||
|
'nextTick(loop); loop();',
|
||||||
|
{
|
||||||
|
hrtime,
|
||||||
|
nextTick,
|
||||||
|
loop
|
||||||
|
},
|
||||||
|
{ timeout: 5 }
|
||||||
|
);
|
||||||
|
}, {
|
||||||
|
code: 'ERR_SCRIPT_EXECUTION_TIMEOUT',
|
||||||
|
message: 'Script execution timed out after 5ms'
|
||||||
|
});
|
39
test/known_issues/test-vm-timeout-escape-promise.js
Normal file
39
test/known_issues/test-vm-timeout-escape-promise.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
// https://github.com/nodejs/node/issues/3020
|
||||||
|
// Promises, nextTick, and queueMicrotask allow code to escape the timeout
|
||||||
|
// set for runInContext, runInNewContext, and runInThisContext
|
||||||
|
|
||||||
|
require('../common');
|
||||||
|
const assert = require('assert');
|
||||||
|
const vm = require('vm');
|
||||||
|
|
||||||
|
const NS_PER_MS = 1000000n;
|
||||||
|
|
||||||
|
const hrtime = process.hrtime.bigint;
|
||||||
|
|
||||||
|
function loop() {
|
||||||
|
const start = hrtime();
|
||||||
|
while (1) {
|
||||||
|
const current = hrtime();
|
||||||
|
const span = (current - start) / NS_PER_MS;
|
||||||
|
if (span >= 100n) {
|
||||||
|
throw new Error(
|
||||||
|
`escaped timeout at ${span} milliseconds!`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.throws(() => {
|
||||||
|
vm.runInNewContext(
|
||||||
|
'Promise.resolve().then(loop); loop();',
|
||||||
|
{
|
||||||
|
hrtime,
|
||||||
|
loop
|
||||||
|
},
|
||||||
|
{ timeout: 5 }
|
||||||
|
);
|
||||||
|
}, {
|
||||||
|
code: 'ERR_SCRIPT_EXECUTION_TIMEOUT',
|
||||||
|
message: 'Script execution timed out after 5ms'
|
||||||
|
});
|
40
test/known_issues/test-vm-timeout-escape-queuemicrotask.js
Normal file
40
test/known_issues/test-vm-timeout-escape-queuemicrotask.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
// https://github.com/nodejs/node/issues/3020
|
||||||
|
// Promises, nextTick, and queueMicrotask allow code to escape the timeout
|
||||||
|
// set for runInContext, runInNewContext, and runInThisContext
|
||||||
|
|
||||||
|
require('../common');
|
||||||
|
const assert = require('assert');
|
||||||
|
const vm = require('vm');
|
||||||
|
|
||||||
|
const NS_PER_MS = 1000000n;
|
||||||
|
|
||||||
|
const hrtime = process.hrtime.bigint;
|
||||||
|
|
||||||
|
function loop() {
|
||||||
|
const start = hrtime();
|
||||||
|
while (1) {
|
||||||
|
const current = hrtime();
|
||||||
|
const span = (current - start) / NS_PER_MS;
|
||||||
|
if (span >= 100n) {
|
||||||
|
throw new Error(
|
||||||
|
`escaped timeout at ${span} milliseconds!`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.throws(() => {
|
||||||
|
vm.runInNewContext(
|
||||||
|
'queueMicrotask(loop); loop();',
|
||||||
|
{
|
||||||
|
hrtime,
|
||||||
|
queueMicrotask,
|
||||||
|
loop
|
||||||
|
},
|
||||||
|
{ timeout: 5 }
|
||||||
|
);
|
||||||
|
}, {
|
||||||
|
code: 'ERR_SCRIPT_EXECUTION_TIMEOUT',
|
||||||
|
message: 'Script execution timed out after 5ms'
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user