Skip to content

Commit e8cc269

Browse files
addaleaxcodebytere
authored andcommitted
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). PR-URL: #31745 Reviewed-By: Stephen Belanger <[email protected]> Reviewed-By: David Carlier <[email protected]> Reviewed-By: Denys Otrishko <[email protected]> Reviewed-By: Chengzhong Wu <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Minwoo Jung <[email protected]> Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Franziska Hinkelmann <[email protected]> Reviewed-By: Gerhard Stöbich <[email protected]>
1 parent 3f971d8 commit e8cc269

File tree

5 files changed

+32
-28
lines changed

5 files changed

+32
-28
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 AliasedFloat64Array
4544
// in Environment::AsyncHooks::async_ids_stack_ which tracks the resource
@@ -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

+15-20
Original file line numberDiff line numberDiff line change
@@ -535,11 +535,15 @@ void AsyncWrap::GetProviderType(const FunctionCallbackInfo<Value>& args) {
535535
}
536536

537537

538-
void AsyncWrap::EmitDestroy() {
538+
void AsyncWrap::EmitDestroy(bool from_gc) {
539539
AsyncWrap::EmitDestroy(env(), async_id_);
540540
// Ensure no double destroy is emitted via AsyncReset().
541541
async_id_ = kInvalidAsyncId;
542-
resource_.Reset();
542+
543+
if (!persistent().IsEmpty() && !from_gc) {
544+
HandleScope handle_scope(env()->isolate());
545+
USE(object()->Set(env()->context(), env()->resource_symbol(), object()));
546+
}
543547
}
544548

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

620-
target->Set(context,
621-
FIXED_ONE_BYTE_STRING(env->isolate(), "owner_symbol"),
622-
env->owner_symbol()).Check();
623-
624624
Local<Object> constants = Object::New(isolate);
625625
#define SET_HOOKS_CONSTANT(name) \
626626
FORCE_SET_TARGET_FIELD( \
@@ -728,7 +728,7 @@ bool AsyncWrap::IsDoneInitializing() const {
728728

729729
AsyncWrap::~AsyncWrap() {
730730
EmitTraceEventDestroy();
731-
EmitDestroy();
731+
EmitDestroy(true /* from gc */);
732732
}
733733

734734
void AsyncWrap::EmitTraceEventDestroy() {
@@ -778,10 +778,13 @@ void AsyncWrap::AsyncReset(Local<Object> resource, double execution_async_id,
778778
: execution_async_id;
779779
trigger_async_id_ = env()->get_default_trigger_async_id();
780780

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

787790
switch (provider_type()) {
@@ -851,7 +854,7 @@ MaybeLocal<Value> AsyncWrap::MakeCallback(const Local<Function> cb,
851854
ProviderType provider = provider_type();
852855
async_context context { get_async_id(), get_trigger_async_id() };
853856
MaybeLocal<Value> ret = InternalMakeCallback(
854-
env(), GetResource(), object(), cb, argc, argv, context);
857+
env(), object(), object(), cb, argc, argv, context);
855858

856859
// This is a static call with cached values because the `this` object may
857860
// no longer be alive at this point.
@@ -890,14 +893,6 @@ Local<Object> AsyncWrap::GetOwner(Environment* env, Local<Object> obj) {
890893
}
891894
}
892895

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

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

src/async_wrap.h

+1-3
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ class AsyncWrap : public BaseObject {
152152
static void EmitAfter(Environment* env, double async_id);
153153
static void EmitPromiseResolve(Environment* env, double async_id);
154154

155-
void EmitDestroy();
155+
void EmitDestroy(bool from_gc = false);
156156

157157
void EmitTraceEventBefore();
158158
static void EmitTraceEventAfter(ProviderType type, double async_id);
@@ -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)