File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -944,6 +944,38 @@ within which it can operate. The process of creating the V8 Context and
944944associating 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
Original file line number Diff line number Diff line change 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+ } ) ;
Original file line number Diff line number Diff line change 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+ } ) ;
You can’t perform that action at this time.
0 commit comments