Skip to content

Commit f6059e3

Browse files
committed
async_hooks: fix id assignment in fast-path promise hook
Native side of fast-path promise hook was not calling JS fastPromiseHook function when there were no async ids previously assigned to the promise. Because of that already created promises could not get id assigned in situations when an async hook without a before listener function is enabled after their creation. As the result executionAsyncId could return wrong id when called within promise's .then(). Refs: #34512
1 parent 1e47051 commit f6059e3

File tree

2 files changed

+69
-38
lines changed

2 files changed

+69
-38
lines changed

src/async_wrap.cc

Lines changed: 44 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,12 @@ using v8::Global;
3939
using v8::HandleScope;
4040
using v8::Integer;
4141
using v8::Isolate;
42+
using v8::Just;
4243
using v8::Local;
44+
using v8::Maybe;
4345
using v8::MaybeLocal;
4446
using v8::Name;
47+
using v8::Nothing;
4548
using v8::Number;
4649
using v8::Object;
4750
using v8::ObjectTemplate;
@@ -189,6 +192,21 @@ void AsyncWrap::EmitAfter(Environment* env, double async_id) {
189192
env->async_hooks_after_function());
190193
}
191194

195+
// TODO(addaleax): Remove once we're on C++17.
196+
constexpr double AsyncWrap::kInvalidAsyncId;
197+
198+
static Maybe<double> GetAssignedPromiseAsyncId(Environment* env,
199+
Local<Promise> promise,
200+
Local<Value> id_symbol) {
201+
Local<Value> maybe_async_id;
202+
if (!promise->Get(env->context(), id_symbol).ToLocal(&maybe_async_id)) {
203+
return Nothing<double>();
204+
}
205+
return maybe_async_id->IsNumber()
206+
? maybe_async_id->NumberValue(env->context())
207+
: Just(AsyncWrap::kInvalidAsyncId);
208+
}
209+
192210
class PromiseWrap : public AsyncWrap {
193211
public:
194212
PromiseWrap(Environment* env, Local<Object> object, bool silent)
@@ -231,18 +249,17 @@ PromiseWrap* PromiseWrap::New(Environment* env,
231249

232250
// Skip for init events
233251
if (silent) {
234-
Local<Value> maybe_async_id = promise
235-
->Get(context, env->async_id_symbol())
236-
.ToLocalChecked();
237-
238-
Local<Value> maybe_trigger_async_id = promise
239-
->Get(context, env->trigger_async_id_symbol())
240-
.ToLocalChecked();
241-
242-
if (maybe_async_id->IsNumber() && maybe_trigger_async_id->IsNumber()) {
243-
double async_id = maybe_async_id.As<Number>()->Value();
244-
double trigger_async_id = maybe_trigger_async_id.As<Number>()->Value();
245-
return new PromiseWrap(env, obj, async_id, trigger_async_id);
252+
double async_id;
253+
double trigger_async_id;
254+
if (!GetAssignedPromiseAsyncId(env, promise, env->async_id_symbol())
255+
.To(&async_id)) return nullptr;
256+
if (!GetAssignedPromiseAsyncId(env, promise, env->trigger_async_id_symbol())
257+
.To(&trigger_async_id)) return nullptr;
258+
259+
if (async_id != AsyncWrap::kInvalidAsyncId &&
260+
trigger_async_id != AsyncWrap::kInvalidAsyncId) {
261+
return new PromiseWrap(
262+
env, obj, async_id, trigger_async_id);
246263
}
247264
}
248265

@@ -321,46 +338,35 @@ static void FastPromiseHook(PromiseHookType type, Local<Promise> promise,
321338

322339
if (type == PromiseHookType::kBefore &&
323340
env->async_hooks()->fields()[AsyncHooks::kBefore] == 0) {
324-
Local<Value> maybe_async_id;
325-
if (!promise->Get(context, env->async_id_symbol())
326-
.ToLocal(&maybe_async_id)) {
327-
return;
328-
}
329-
330-
Local<Value> maybe_trigger_async_id;
331-
if (!promise->Get(context, env->trigger_async_id_symbol())
332-
.ToLocal(&maybe_trigger_async_id)) {
333-
return;
334-
}
335-
336-
if (maybe_async_id->IsNumber() && maybe_trigger_async_id->IsNumber()) {
337-
double async_id = maybe_async_id.As<Number>()->Value();
338-
double trigger_async_id = maybe_trigger_async_id.As<Number>()->Value();
341+
double async_id;
342+
double trigger_async_id;
343+
if (!GetAssignedPromiseAsyncId(env, promise, env->async_id_symbol())
344+
.To(&async_id)) return;
345+
if (!GetAssignedPromiseAsyncId(env, promise, env->trigger_async_id_symbol())
346+
.To(&trigger_async_id)) return;
347+
348+
if (async_id != AsyncWrap::kInvalidAsyncId &&
349+
trigger_async_id != AsyncWrap::kInvalidAsyncId) {
339350
env->async_hooks()->push_async_context(
340351
async_id, trigger_async_id, promise);
352+
return;
341353
}
342-
343-
return;
344354
}
345355

346356
if (type == PromiseHookType::kAfter &&
347357
env->async_hooks()->fields()[AsyncHooks::kAfter] == 0) {
348-
Local<Value> maybe_async_id;
349-
if (!promise->Get(context, env->async_id_symbol())
350-
.ToLocal(&maybe_async_id)) {
351-
return;
352-
}
358+
double async_id;
359+
if (!GetAssignedPromiseAsyncId(env, promise, env->async_id_symbol())
360+
.To(&async_id)) return;
353361

354-
if (maybe_async_id->IsNumber()) {
355-
double async_id = maybe_async_id.As<Number>()->Value();
362+
if (async_id != AsyncWrap::kInvalidAsyncId) {
356363
if (env->execution_async_id() == async_id) {
357364
// This condition might not be true if async_hooks was enabled during
358365
// the promise callback execution.
359366
env->async_hooks()->pop_async_context(async_id);
360367
}
368+
return;
361369
}
362-
363-
return;
364370
}
365371

366372
if (type == PromiseHookType::kResolve &&
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const async_hooks = require('async_hooks');
5+
6+
// This test ensures that fast-path PromiseHook assigns async ids
7+
// to already created promises when the native hook function is
8+
// triggered on before event.
9+
10+
let initialAsyncId;
11+
const promise = new Promise((resolve) => {
12+
setTimeout(() => {
13+
initialAsyncId = async_hooks.executionAsyncId();
14+
async_hooks.createHook({
15+
after: common.mustCall(() => {}, 2)
16+
}).enable();
17+
resolve();
18+
}, 0);
19+
});
20+
21+
promise.then(common.mustCall(() => {
22+
const id = async_hooks.executionAsyncId();
23+
assert.notStrictEqual(id, initialAsyncId);
24+
assert.ok(id > 0);
25+
}));

0 commit comments

Comments
 (0)