Skip to content

Commit eb20179

Browse files
leah-1eeagape1225
authored andcommitted
vm: avoid repeated microtask mode parsing
Parse microtaskMode only in createContext() so convenience APIs do not read type and queue getters repeatedly. Add regression tests for getter access, invalid options, and shared manual queues during module evaluation. Clarify that the queue constructor is not exported and fix the C++ macro line length. Refs: #65555 Signed-off-by: leah-1ee <selee3196@gmail.com>
1 parent d32c6f6 commit eb20179

6 files changed

Lines changed: 141 additions & 35 deletions

File tree

‎doc/api/vm.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1341,8 +1341,8 @@ share where their microtasks (`Promise` reactions and `async function`
13411341
continuations) are placed, and so that those microtasks are drained together,
13421342
explicitly, by the embedder, instead of automatically by Node.js.
13431343

1344-
Instances are created with [`vm.createMicrotaskQueue()`][]; there is no
1345-
public constructor.
1344+
Instances are created with [`vm.createMicrotaskQueue()`][]; the constructor is
1345+
not exported by the `node:vm` module.
13461346

13471347
### `microtaskQueue.runMicrotasks()`
13481348

‎lib/vm.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,6 @@ function getContextOptions(options) {
263263
validateBoolean(wasm, 'options.contextCodeGeneration.wasm');
264264
contextOptions.codeGeneration = { strings, wasm };
265265
}
266-
getMicrotaskModeOptions(options.microtaskMode);
267266
return contextOptions;
268267
}
269268

‎src/env_properties.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,7 @@
437437
V(lock_info_template, v8::DictionaryTemplate) \
438438
V(lock_query_template, v8::DictionaryTemplate) \
439439
V(message_port_constructor_template, v8::FunctionTemplate) \
440-
V(microtask_queue_constructor_template, v8::FunctionTemplate) \
440+
V(microtask_queue_constructor_template, v8::FunctionTemplate) \
441441
V(module_wrap_constructor_template, v8::FunctionTemplate) \
442442
V(mx_record_template, v8::DictionaryTemplate) \
443443
V(naptr_record_template, v8::DictionaryTemplate) \
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Flags: --experimental-vm-modules
2+
'use strict';
3+
4+
const common = require('../common');
5+
const assert = require('assert');
6+
const vm = require('vm');
7+
8+
(async () => {
9+
const queue = vm.createMicrotaskQueue();
10+
const trace = [];
11+
const record = (entry) => trace.push(entry);
12+
const microtaskMode = { type: 'manual', queue };
13+
const contextA = vm.createContext({ record }, { microtaskMode });
14+
const contextB = vm.createContext({ record }, { microtaskMode });
15+
const moduleA = new vm.SourceTextModule(`
16+
await Promise.resolve();
17+
record('a');
18+
Promise.resolve().then(() => record('a-follow-up'));
19+
`, { context: contextA });
20+
const moduleB = new vm.SourceTextModule(`
21+
await Promise.resolve();
22+
record('b');
23+
Promise.resolve().then(() => record('b-follow-up'));
24+
`, { context: contextB });
25+
26+
await moduleA.link(common.mustNotCall());
27+
await moduleB.link(common.mustNotCall());
28+
29+
const evaluationA = moduleA.evaluate();
30+
const evaluationB = moduleB.evaluate();
31+
let completed = 0;
32+
let resolveCompleted;
33+
const allCompleted = new Promise((resolve) => {
34+
resolveCompleted = resolve;
35+
});
36+
const onCompleted = () => {
37+
if (++completed === 2) resolveCompleted();
38+
};
39+
evaluationA.then(common.mustCall(() => {
40+
onCompleted();
41+
}));
42+
evaluationB.then(common.mustCall(() => {
43+
onCompleted();
44+
}));
45+
46+
assert.deepStrictEqual(trace, []);
47+
queue.runMicrotasks();
48+
assert.deepStrictEqual(trace, [
49+
'a',
50+
'b',
51+
'a-follow-up',
52+
'b-follow-up',
53+
]);
54+
55+
await allCompleted;
56+
assert.strictEqual(completed, 2);
57+
})().then(common.mustCall());

‎test/parallel/test-vm-shared-microtask-queue-validation.js‎

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,24 @@ const fake = { __proto__: Object.getPrototypeOf(queue) };
88
const proxy = new Proxy(queue, {});
99

1010
const script = new vm.Script('');
11-
for (const invalid of [fake, proxy, {}, null, undefined, 1, 'queue']) {
12-
const options = { microtaskMode: { type: 'manual', queue: invalid } };
13-
for (const create of [
14-
() => vm.createContext({}, options),
15-
() => vm.runInNewContext('', {}, options),
16-
() => script.runInNewContext({}, options),
17-
]) {
18-
assert.throws(create, { code: 'ERR_INVALID_ARG_TYPE' });
11+
const createContext = (options) => vm.createContext({}, options);
12+
const runInNewContext = (options) => vm.runInNewContext('', {}, options);
13+
const scriptRunInNewContext =
14+
(options) => script.runInNewContext({}, options);
15+
const contextCreators = [
16+
createContext,
17+
runInNewContext,
18+
scriptRunInNewContext,
19+
];
20+
21+
for (const create of contextCreators) {
22+
assert.throws(() => {
23+
create({ microtaskMode: { type: 'automatic', queue } });
24+
}, { code: 'ERR_INVALID_ARG_VALUE' });
25+
26+
for (const invalid of [fake, proxy, {}, null, undefined, 1, 'queue']) {
27+
const options = { microtaskMode: { type: 'manual', queue: invalid } };
28+
assert.throws(() => create(options), { code: 'ERR_INVALID_ARG_TYPE' });
1929
}
2030
}
2131

‎test/parallel/test-vm-shared-microtask-queue.js‎

Lines changed: 63 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,8 @@ const vm = require('vm');
4242
assert.notStrictEqual(queue, other);
4343
}
4444

45-
// `vm.MicrotaskQueue` (the constructor) is intentionally not exposed: only
46-
// `vm.createMicrotaskQueue()` is public, so there is no way to construct or
47-
// name the class directly from user code.
45+
// The constructor is intentionally not exported from `vm`; the public API for
46+
// creating queues is `vm.createMicrotaskQueue()`.
4847
assert.strictEqual(vm.MicrotaskQueue, undefined);
4948

5049
// `microtaskQueue.runMicrotasks()` throws when called on an unrelated `this`.
@@ -129,28 +128,69 @@ assert.strictEqual(vm.MicrotaskQueue, undefined);
129128
assert.deepStrictEqual(trace, ['a', 'b']);
130129
}
131130

132-
// `vm.runInNewContext()` and `script.runInNewContext()` also accept the
133-
// object form (they build their context via `getContextOptions()` +
134-
// `createContext()`, both of which now go through `getMicrotaskModeOptions()`).
131+
// All context-creation APIs parse the object form once. In particular, they
132+
// must not read either property a second time after validation.
135133
{
136-
const queue = vm.createMicrotaskQueue();
137-
const trace = [];
138-
const record = (entry) => trace.push(entry);
139-
140-
vm.runInNewContext(
141-
"Promise.resolve().then(() => record('runInNewContext'));",
142-
{ record },
143-
{ microtaskMode: { type: 'manual', queue } },
134+
const script = new vm.Script(
135+
"Promise.resolve().then(() => record('script.runInNewContext'));",
144136
);
145-
assert.deepStrictEqual(trace, []);
146-
queue.runMicrotasks();
147-
assert.deepStrictEqual(trace, ['runInNewContext']);
148-
149-
const script = new vm.Script("Promise.resolve().then(() => record('script.runInNewContext'));");
150-
script.runInNewContext({ record }, { microtaskMode: { type: 'manual', queue } });
151-
assert.deepStrictEqual(trace, ['runInNewContext']);
152-
queue.runMicrotasks();
153-
assert.deepStrictEqual(trace, ['runInNewContext', 'script.runInNewContext']);
137+
const createAndRun = [
138+
{
139+
expected: 'createContext',
140+
run(sandbox, options) {
141+
const context = vm.createContext(sandbox, options);
142+
vm.runInContext(
143+
"Promise.resolve().then(() => record('createContext'));",
144+
context,
145+
);
146+
},
147+
},
148+
{
149+
expected: 'runInNewContext',
150+
run(sandbox, options) {
151+
vm.runInNewContext(
152+
"Promise.resolve().then(() => record('runInNewContext'));",
153+
sandbox,
154+
options,
155+
);
156+
},
157+
},
158+
{
159+
expected: 'script.runInNewContext',
160+
run(sandbox, options) {
161+
script.runInNewContext(sandbox, options);
162+
},
163+
},
164+
];
165+
166+
for (const { expected, run } of createAndRun) {
167+
const queue = vm.createMicrotaskQueue();
168+
const trace = [];
169+
const record = (entry) => trace.push(entry);
170+
let typeGetCount = 0;
171+
let queueGetCount = 0;
172+
const microtaskMode = {
173+
get type() {
174+
if (++typeGetCount > 1) {
175+
throw new Error('microtaskMode.type was read more than once');
176+
}
177+
return 'manual';
178+
},
179+
get queue() {
180+
if (++queueGetCount > 1) {
181+
throw new Error('microtaskMode.queue was read more than once');
182+
}
183+
return queue;
184+
},
185+
};
186+
187+
run({ record }, { microtaskMode });
188+
assert.strictEqual(typeGetCount, 1);
189+
assert.strictEqual(queueGetCount, 1);
190+
assert.deepStrictEqual(trace, []);
191+
queue.runMicrotasks();
192+
assert.deepStrictEqual(trace, [expected]);
193+
}
154194
}
155195

156196
// `vm.constants.DONT_CONTEXTIFY` sandboxes (no wrapper object, the V8

0 commit comments

Comments
 (0)