summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/api/vm.md32
-rw-r--r--test/known_issues/test-vm-timeout-escape-nexttick.js41
-rw-r--r--test/known_issues/test-vm-timeout-escape-promise.js39
-rw-r--r--test/known_issues/test-vm-timeout-escape-queuemicrotask.js40
4 files changed, 152 insertions, 0 deletions
diff --git a/doc/api/vm.md b/doc/api/vm.md
index 5bf09de379..842ae3b81a 100644
--- a/doc/api/vm.md
+++ b/doc/api/vm.md
@@ -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
"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
[`ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING`]: errors.html#ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING
[`URL`]: url.html#url_class_url
diff --git a/test/known_issues/test-vm-timeout-escape-nexttick.js b/test/known_issues/test-vm-timeout-escape-nexttick.js
new file mode 100644
index 0000000000..8afe2fb8ce
--- /dev/null
+++ b/test/known_issues/test-vm-timeout-escape-nexttick.js
@@ -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'
+});
diff --git a/test/known_issues/test-vm-timeout-escape-promise.js b/test/known_issues/test-vm-timeout-escape-promise.js
new file mode 100644
index 0000000000..4452c83cd1
--- /dev/null
+++ b/test/known_issues/test-vm-timeout-escape-promise.js
@@ -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'
+});
diff --git a/test/known_issues/test-vm-timeout-escape-queuemicrotask.js b/test/known_issues/test-vm-timeout-escape-queuemicrotask.js
new file mode 100644
index 0000000000..8de33bc24d
--- /dev/null
+++ b/test/known_issues/test-vm-timeout-escape-queuemicrotask.js
@@ -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'
+});