Repository: nodejs/node
Pull request: src: add ExecutionAsyncId getter for any Context

Adds a variant of `node::AsyncHooksGetExecutionAsyncId` that takes a `v8::Local<v8::Context>` and returns the async ID belonging to the `node::Environment` (if any) of that Context. 

This is not dissimilar to how e.g. `node::Environment::GetCurrent` method also exists with overloads for both `v8::Isolate*` and `v8::Local<v8::Context>`.

In certain situations it would be useful to pass a Context object different from `v8::Isolate::GetCurrentContext`, e.g. the one returned by `v8::Isolate::GetEnteredOrMicrotaskContext` would be useful in recording the async ID in a V8 GC prologue callback when current context is not set.

Changed files:
filename: src/api/hooks.cc
patch: @@ -196,6 +196,12 @@ async_id AsyncHooksGetExecutionAsyncId(Isolate* isolate) {
   return env->execution_async_id();
 }
 
+async_id AsyncHooksGetExecutionAsyncId(Local<Context> context) {
+  Environment* env = Environment::GetCurrent(context);
+  if (env == nullptr) return -1;
+  return env->execution_async_id();
+}
+
 async_id AsyncHooksGetTriggerAsyncId(Isolate* isolate) {
   Environment* env = Environment::GetCurrent(isolate);
   if (env == nullptr) return -1;

filename: src/node.h
patch: @@ -1404,6 +1404,12 @@ NODE_EXTERN void RequestInterrupt(Environment* env,
  * I/O from native code. */
 NODE_EXTERN async_id AsyncHooksGetExecutionAsyncId(v8::Isolate* isolate);
 
+/* Returns the id of the current execution context. If the return value is
+ * zero then no execution has been set. This will happen if the user handles
+ * I/O from native code. */
+NODE_EXTERN async_id
+AsyncHooksGetExecutionAsyncId(v8::Local<v8::Context> context);
+
 /* Return same value as async_hooks.triggerAsyncId(); */
 NODE_EXTERN async_id AsyncHooksGetTriggerAsyncId(v8::Isolate* isolate);
 

filename: test/addons/async-hooks-id/binding.cc
patch: @@ -12,13 +12,21 @@ void GetExecutionAsyncId(const FunctionCallbackInfo<Value>& args) {
     node::AsyncHooksGetExecutionAsyncId(args.GetIsolate()));
 }
 
+void GetExecutionAsyncIdWithContext(const FunctionCallbackInfo<Value>& args) {
+  args.GetReturnValue().Set(node::AsyncHooksGetExecutionAsyncId(
+      args.GetIsolate()->GetCurrentContext()));
+}
+
 void GetTriggerAsyncId(const FunctionCallbackInfo<Value>& args) {
   args.GetReturnValue().Set(
     node::AsyncHooksGetTriggerAsyncId(args.GetIsolate()));
 }
 
 void Initialize(Local<Object> exports) {
   NODE_SET_METHOD(exports, "getExecutionAsyncId", GetExecutionAsyncId);
+  NODE_SET_METHOD(exports,
+                  "getExecutionAsyncIdWithContext",
+                  GetExecutionAsyncIdWithContext);
   NODE_SET_METHOD(exports, "getTriggerAsyncId", GetTriggerAsyncId);
 }
 

filename: test/addons/async-hooks-id/test.js
patch: @@ -9,6 +9,10 @@ assert.strictEqual(
   binding.getExecutionAsyncId(),
   async_hooks.executionAsyncId(),
 );
+assert.strictEqual(
+  binding.getExecutionAsyncIdWithContext(),
+  async_hooks.executionAsyncId(),
+);
 assert.strictEqual(
   binding.getTriggerAsyncId(),
   async_hooks.triggerAsyncId(),
@@ -19,6 +23,10 @@ process.nextTick(common.mustCall(() => {
     binding.getExecutionAsyncId(),
     async_hooks.executionAsyncId(),
   );
+  assert.strictEqual(
+    binding.getExecutionAsyncIdWithContext(),
+    async_hooks.executionAsyncId(),
+  );
   assert.strictEqual(
     binding.getTriggerAsyncId(),
     async_hooks.triggerAsyncId(),