Skip to content

Commit dd44bb9

Browse files
committed
src: use symbol to store AsyncWrap resource
Use a symbol on the bindings object to store the public resource object, rather than a `v8::Global` Persistent. This has several advantages: - It’s harder to inadvertently create memory leaks this way. The garbage collector sees the `AsyncWrap` → resource link like a regular JS property, and can collect the objects as a group, even if the resource object should happen to point back to the `AsyncWrap` object. - This will make it easier in the future to use `owner_symbol` for this purpose, which is generally the direction we should be moving the `async_hooks` API into (i.e. using more public objects instead of letting internal wires stick out).
1 parent 1c61914 commit dd44bb9

File tree

5 files changed

+28
-24
lines changed

5 files changed

+28
-24
lines changed

lib/internal/async_hooks.js

+13-3
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,7 @@ const async_wrap = internalBinding('async_wrap');
3838
const {
3939
async_hook_fields,
4040
async_id_fields,
41-
execution_async_resources,
42-
owner_symbol
41+
execution_async_resources
4342
} = async_wrap;
4443
// Store the pair executionAsyncId and triggerAsyncId in a std::stack on
4544
// Environment::AsyncHooks::async_ids_stack_ tracks the resource responsible for
@@ -80,6 +79,7 @@ const active_hooks = {
8079

8180
const { registerDestroyHook } = async_wrap;
8281
const { enqueueMicrotask } = internalBinding('task_queue');
82+
const { resource_symbol, owner_symbol } = internalBinding('symbols');
8383

8484
// Each constant tracks how many callbacks there are for any given step of
8585
// async execution. These are tracked so if the user didn't include callbacks
@@ -109,7 +109,7 @@ function executionAsyncResource() {
109109
const index = async_hook_fields[kStackLength] - 1;
110110
if (index === -1) return topLevelResource;
111111
const resource = execution_async_resources[index];
112-
return resource;
112+
return lookupPublicResource(resource);
113113
}
114114

115115
// Used to fatally abort the process if a callback throws.
@@ -130,6 +130,15 @@ function fatalError(e) {
130130
process.exit(1);
131131
}
132132

133+
function lookupPublicResource(resource) {
134+
if (typeof resource !== 'object' || resource === null) return resource;
135+
// TODO(addaleax): Merge this with owner_symbol and use it across all
136+
// AsyncWrap instances.
137+
const publicResource = resource[resource_symbol];
138+
if (publicResource !== undefined)
139+
return publicResource;
140+
return resource;
141+
}
133142

134143
// Emit From Native //
135144

@@ -138,6 +147,7 @@ function fatalError(e) {
138147
// emitInitScript.
139148
function emitInitNative(asyncId, type, triggerAsyncId, resource) {
140149
active_hooks.call_depth += 1;
150+
resource = lookupPublicResource(resource);
141151
// Use a single try/catch for all hooks to avoid setting up one per iteration.
142152
try {
143153
// Using var here instead of let because "for (var ...)" is faster than let.

src/api/callback.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ CallbackScope::~CallbackScope() {
3636

3737
InternalCallbackScope::InternalCallbackScope(AsyncWrap* async_wrap, int flags)
3838
: InternalCallbackScope(async_wrap->env(),
39-
async_wrap->GetResource(),
39+
async_wrap->object(),
4040
{ async_wrap->get_async_id(),
4141
async_wrap->get_trigger_async_id() },
4242
flags) {}

src/async_wrap.cc

+12-17
Original file line numberDiff line numberDiff line change
@@ -538,7 +538,11 @@ void AsyncWrap::EmitDestroy() {
538538
AsyncWrap::EmitDestroy(env(), async_id_);
539539
// Ensure no double destroy is emitted via AsyncReset().
540540
async_id_ = kInvalidAsyncId;
541-
resource_.Reset();
541+
542+
if (!persistent().IsEmpty()) {
543+
HandleScope handle_scope(env()->isolate());
544+
USE(object()->Set(env()->context(), env()->resource_symbol(), object()));
545+
}
542546
}
543547

544548
void AsyncWrap::QueueDestroyAsyncId(const FunctionCallbackInfo<Value>& args) {
@@ -616,10 +620,6 @@ void AsyncWrap::Initialize(Local<Object> target,
616620
env->async_ids_stack_string(),
617621
env->async_hooks()->async_ids_stack().GetJSArray()).Check();
618622

619-
target->Set(context,
620-
FIXED_ONE_BYTE_STRING(env->isolate(), "owner_symbol"),
621-
env->owner_symbol()).Check();
622-
623623
Local<Object> constants = Object::New(isolate);
624624
#define SET_HOOKS_CONSTANT(name) \
625625
FORCE_SET_TARGET_FIELD( \
@@ -777,10 +777,13 @@ void AsyncWrap::AsyncReset(Local<Object> resource, double execution_async_id,
777777
: execution_async_id;
778778
trigger_async_id_ = env()->get_default_trigger_async_id();
779779

780-
if (resource != object()) {
781-
// TODO(addaleax): Using a strong reference here makes it very easy to
782-
// introduce memory leaks. Move away from using a strong reference.
783-
resource_.Reset(env()->isolate(), resource);
780+
{
781+
HandleScope handle_scope(env()->isolate());
782+
Local<Object> obj = object();
783+
CHECK(!obj.IsEmpty());
784+
if (resource != obj) {
785+
USE(obj->Set(env()->context(), env()->resource_symbol(), resource));
786+
}
784787
}
785788

786789
switch (provider_type()) {
@@ -889,14 +892,6 @@ Local<Object> AsyncWrap::GetOwner(Environment* env, Local<Object> obj) {
889892
}
890893
}
891894

892-
Local<Object> AsyncWrap::GetResource() {
893-
if (resource_.IsEmpty()) {
894-
return object();
895-
}
896-
897-
return resource_.Get(env()->isolate());
898-
}
899-
900895
} // namespace node
901896

902897
NODE_MODULE_CONTEXT_AWARE_INTERNAL(async_wrap, node::AsyncWrap::Initialize)

src/async_wrap.h

-2
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,6 @@ class AsyncWrap : public BaseObject {
199199
v8::Local<v8::Object> obj);
200200

201201
bool IsDoneInitializing() const override;
202-
v8::Local<v8::Object> GetResource();
203202

204203
private:
205204
friend class PromiseWrap;
@@ -219,7 +218,6 @@ class AsyncWrap : public BaseObject {
219218
// Because the values may be Reset(), cannot be made const.
220219
double async_id_ = kInvalidAsyncId;
221220
double trigger_async_id_;
222-
v8::Global<v8::Object> resource_;
223221
};
224222

225223
} // namespace node

src/env.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,9 @@ constexpr size_t kFsStatsBufferLength =
160160
V(handle_onclose_symbol, "handle_onclose") \
161161
V(no_message_symbol, "no_message_symbol") \
162162
V(oninit_symbol, "oninit") \
163-
V(owner_symbol, "owner") \
163+
V(owner_symbol, "owner_symbol") \
164164
V(onpskexchange_symbol, "onpskexchange") \
165+
V(resource_symbol, "resource_symbol") \
165166
V(trigger_async_id_symbol, "trigger_async_id_symbol") \
166167

167168
// Strings are per-isolate primitives but Environment proxies them

0 commit comments

Comments
 (0)