Skip to content

Commit 7986a9b

Browse files
committed
src: set PromiseHooks by Environment
The new JS PromiseHooks introduced in the referenced PR are per v8::Context. This meant that code depending on them, such as AsyncLocalStorage, wouldn't behave correctly across vm.Context instances. PromiseHooks are now synchronized across the main Context and any Context created via vm.Context. Refs: #36394 Fixes: #38781 Signed-off-by: Bryan English <bryan@bryanenglish.com>
1 parent b6471c9 commit 7986a9b

File tree

5 files changed

+99
-2
lines changed

5 files changed

+99
-2
lines changed

src/async_wrap.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -454,8 +454,8 @@ static void EnablePromiseHook(const FunctionCallbackInfo<Value>& args) {
454454

455455
static void SetPromiseHooks(const FunctionCallbackInfo<Value>& args) {
456456
Environment* env = Environment::GetCurrent(args);
457-
Local<Context> ctx = env->context();
458-
ctx->SetPromiseHooks(
457+
458+
env->async_hooks()->SetJSPromiseHooks(
459459
args[0]->IsFunction() ? args[0].As<Function>() : Local<Function>(),
460460
args[1]->IsFunction() ? args[1].As<Function>() : Local<Function>(),
461461
args[2]->IsFunction() ? args[2].As<Function>() : Local<Function>(),

src/env-inl.h

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,20 @@ v8::Local<v8::Object> AsyncHooks::native_execution_async_resource(size_t i) {
9595
return PersistentToLocal::Strong(native_execution_async_resources_[i]);
9696
}
9797

98+
inline void AsyncHooks::SetJSPromiseHooks(v8::Local<v8::Function> init,
99+
v8::Local<v8::Function> before,
100+
v8::Local<v8::Function> after,
101+
v8::Local<v8::Function> resolve) {
102+
js_promise_hooks_[0].Reset(env()->isolate(), init);
103+
js_promise_hooks_[1].Reset(env()->isolate(), before);
104+
js_promise_hooks_[2].Reset(env()->isolate(), after);
105+
js_promise_hooks_[3].Reset(env()->isolate(), resolve);
106+
for (auto it = contexts_.begin(); it != contexts_.end(); it++) {
107+
PersistentToLocal::Strong(*it)
108+
->SetPromiseHooks(init, before, after, resolve);
109+
}
110+
}
111+
98112
inline v8::Local<v8::String> AsyncHooks::provider_string(int idx) {
99113
return env()->isolate_data()->async_wrap_provider(idx);
100114
}
@@ -217,6 +231,37 @@ void AsyncHooks::clear_async_id_stack() {
217231
fields_[kStackLength] = 0;
218232
}
219233

234+
inline void AsyncHooks::AddContext(v8::Local<v8::Context> ctx) {
235+
ctx->SetPromiseHooks(
236+
js_promise_hooks_[0].IsEmpty() ?
237+
v8::Local<v8::Function>() :
238+
PersistentToLocal::Strong(js_promise_hooks_[0]),
239+
js_promise_hooks_[1].IsEmpty() ?
240+
v8::Local<v8::Function>() :
241+
PersistentToLocal::Strong(js_promise_hooks_[1]),
242+
js_promise_hooks_[2].IsEmpty() ?
243+
v8::Local<v8::Function>() :
244+
PersistentToLocal::Strong(js_promise_hooks_[2]),
245+
js_promise_hooks_[3].IsEmpty() ?
246+
v8::Local<v8::Function>() :
247+
PersistentToLocal::Strong(js_promise_hooks_[3]));
248+
249+
size_t id = contexts_.size();
250+
contexts_.resize(id + 1);
251+
contexts_[id].Reset(env()->isolate(), ctx);
252+
}
253+
254+
inline void AsyncHooks::RemoveContext(v8::Local<v8::Context> ctx) {
255+
for (auto it = contexts_.begin(); it != contexts_.end(); it++) {
256+
v8::Local<v8::Context> saved_context = PersistentToLocal::Strong(*it);
257+
if (saved_context == ctx) {
258+
it->Reset();
259+
contexts_.erase(it);
260+
break;
261+
}
262+
}
263+
}
264+
220265
// The DefaultTriggerAsyncIdScope(AsyncWrap*) constructor is defined in
221266
// async_wrap-inl.h to avoid a circular dependency.
222267

@@ -304,6 +349,8 @@ inline void Environment::AssignToContext(v8::Local<v8::Context> context,
304349
#if HAVE_INSPECTOR
305350
inspector_agent()->ContextCreated(context, info);
306351
#endif // HAVE_INSPECTOR
352+
353+
this->async_hooks()->AddContext(context);
307354
}
308355

309356
inline Environment* Environment::GetCurrent(v8::Isolate* isolate) {

src/env.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -701,6 +701,11 @@ class AsyncHooks : public MemoryRetainer {
701701
// The `js_execution_async_resources` array contains the value in that case.
702702
inline v8::Local<v8::Object> native_execution_async_resource(size_t index);
703703

704+
inline void SetJSPromiseHooks(v8::Local<v8::Function> init,
705+
v8::Local<v8::Function> before,
706+
v8::Local<v8::Function> after,
707+
v8::Local<v8::Function> resolve);
708+
704709
inline v8::Local<v8::String> provider_string(int idx);
705710

706711
inline void no_force_checks();
@@ -711,6 +716,10 @@ class AsyncHooks : public MemoryRetainer {
711716
inline bool pop_async_context(double async_id);
712717
inline void clear_async_id_stack(); // Used in fatal exceptions.
713718

719+
inline void AddContext(v8::Local<v8::Context> ctx);
720+
inline void RemoveContext(v8::Local<v8::Context> ctx);
721+
722+
714723
AsyncHooks(const AsyncHooks&) = delete;
715724
AsyncHooks& operator=(const AsyncHooks&) = delete;
716725
AsyncHooks(AsyncHooks&&) = delete;
@@ -770,6 +779,10 @@ class AsyncHooks : public MemoryRetainer {
770779

771780
// Non-empty during deserialization
772781
const SerializeInfo* info_ = nullptr;
782+
783+
std::vector<v8::Global<v8::Context>> contexts_;
784+
785+
v8::Global<v8::Function> js_promise_hooks_[4];
773786
};
774787

775788
class ImmediateInfo : public MemoryRetainer {

src/node_contextify.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ ContextifyContext::ContextifyContext(
127127

128128
ContextifyContext::~ContextifyContext() {
129129
env()->RemoveCleanupHook(CleanupHook, this);
130+
131+
env()->async_hooks()->RemoveContext(PersistentToLocal::Strong(context_));
130132
}
131133

132134

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use strict';
2+
3+
require('../common');
4+
const assert = require('assert');
5+
const vm = require('vm');
6+
const { AsyncLocalStorage } = require('async_hooks');
7+
8+
// Regression test for https://github.com/nodejs/node/issues/38781
9+
10+
const context = vm.createContext({
11+
AsyncLocalStorage,
12+
assert
13+
});
14+
15+
vm.runInContext(`
16+
const storage = new AsyncLocalStorage()
17+
async function test() {
18+
return storage.run({ test: 'vm' }, async () => {
19+
assert.strictEqual(storage.getStore().test, 'vm');
20+
await 42;
21+
assert.strictEqual(storage.getStore().test, 'vm');
22+
});
23+
}
24+
test()
25+
`, context);
26+
27+
const storage = new AsyncLocalStorage();
28+
async function test() {
29+
return storage.run({ test: 'main context' }, async () => {
30+
assert.strictEqual(storage.getStore().test, 'main context');
31+
await 42;
32+
assert.strictEqual(storage.getStore().test, 'main context');
33+
});
34+
}
35+
test();

0 commit comments

Comments
 (0)