Skip to content

Commit 30b0339

Browse files
addaleaxBethGriggs
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 Backport-PR-URL: #33962 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 97a3f7b commit 30b0339

File tree

6 files changed

+34
-28
lines changed

6 files changed

+34
-28
lines changed

lib/internal/async_hooks.js

+13-3
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,7 @@ const async_wrap = internalBinding('async_wrap');
3636
const {
3737
async_hook_fields,
3838
async_id_fields,
39-
execution_async_resources,
40-
owner_symbol
39+
execution_async_resources
4140
} = async_wrap;
4241
// Store the pair executionAsyncId and triggerAsyncId in a std::stack on
4342
// Environment::AsyncHooks::async_ids_stack_ tracks the resource responsible for
@@ -78,6 +77,7 @@ const active_hooks = {
7877

7978
const { registerDestroyHook } = async_wrap;
8079
const { enqueueMicrotask } = internalBinding('task_queue');
80+
const { resource_symbol, owner_symbol } = internalBinding('symbols');
8181

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

112112
// Used to fatally abort the process if a callback throws.
@@ -127,6 +127,15 @@ function fatalError(e) {
127127
process.exit(1);
128128
}
129129

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

131140
// Emit From Native //
132141

@@ -135,6 +144,7 @@ function fatalError(e) {
135144
// emitInitScript.
136145
function emitInitNative(asyncId, type, triggerAsyncId, resource) {
137146
active_hooks.call_depth += 1;
147+
resource = lookupPublicResource(resource);
138148
// Use a single try/catch for all hooks to avoid setting up one per iteration.
139149
try {
140150
// 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
@@ -451,11 +451,15 @@ void AsyncWrap::GetProviderType(const FunctionCallbackInfo<Value>& args) {
451451
}
452452

453453

454-
void AsyncWrap::EmitDestroy() {
454+
void AsyncWrap::EmitDestroy(bool from_gc) {
455455
AsyncWrap::EmitDestroy(env(), async_id_);
456456
// Ensure no double destroy is emitted via AsyncReset().
457457
async_id_ = kInvalidAsyncId;
458-
resource_.Reset();
458+
459+
if (!persistent().IsEmpty() && !from_gc) {
460+
HandleScope handle_scope(env()->isolate());
461+
USE(object()->Set(env()->context(), env()->resource_symbol(), object()));
462+
}
459463
}
460464

461465
void AsyncWrap::QueueDestroyAsyncId(const FunctionCallbackInfo<Value>& args) {
@@ -533,10 +537,6 @@ void AsyncWrap::Initialize(Local<Object> target,
533537
env->async_ids_stack_string(),
534538
env->async_hooks()->async_ids_stack().GetJSArray()).Check();
535539

536-
target->Set(context,
537-
FIXED_ONE_BYTE_STRING(env->isolate(), "owner_symbol"),
538-
env->owner_symbol()).Check();
539-
540540
Local<Object> constants = Object::New(isolate);
541541
#define SET_HOOKS_CONSTANT(name) \
542542
FORCE_SET_TARGET_FIELD( \
@@ -632,7 +632,7 @@ bool AsyncWrap::IsDoneInitializing() const {
632632

633633
AsyncWrap::~AsyncWrap() {
634634
EmitTraceEventDestroy();
635-
EmitDestroy();
635+
EmitDestroy(true /* from gc */);
636636
}
637637

638638
void AsyncWrap::EmitTraceEventDestroy() {
@@ -682,10 +682,13 @@ void AsyncWrap::AsyncReset(Local<Object> resource, double execution_async_id,
682682
: execution_async_id;
683683
trigger_async_id_ = env()->get_default_trigger_async_id();
684684

685-
if (resource != object()) {
686-
// TODO(addaleax): Using a strong reference here makes it very easy to
687-
// introduce memory leaks. Move away from using a strong reference.
688-
resource_.Reset(env()->isolate(), resource);
685+
{
686+
HandleScope handle_scope(env()->isolate());
687+
Local<Object> obj = object();
688+
CHECK(!obj.IsEmpty());
689+
if (resource != obj) {
690+
USE(obj->Set(env()->context(), env()->resource_symbol(), resource));
691+
}
689692
}
690693

691694
switch (provider_type()) {
@@ -755,7 +758,7 @@ MaybeLocal<Value> AsyncWrap::MakeCallback(const Local<Function> cb,
755758
ProviderType provider = provider_type();
756759
async_context context { get_async_id(), get_trigger_async_id() };
757760
MaybeLocal<Value> ret = InternalMakeCallback(
758-
env(), GetResource(), object(), cb, argc, argv, context);
761+
env(), object(), object(), cb, argc, argv, context);
759762

760763
// This is a static call with cached values because the `this` object may
761764
// no longer be alive at this point.
@@ -794,14 +797,6 @@ Local<Object> AsyncWrap::GetOwner(Environment* env, Local<Object> obj) {
794797
}
795798
}
796799

797-
Local<Object> AsyncWrap::GetResource() {
798-
if (resource_.IsEmpty()) {
799-
return object();
800-
}
801-
802-
return resource_.Get(env()->isolate());
803-
}
804-
805800
} // namespace node
806801

807802
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;
@@ -214,7 +213,6 @@ class AsyncWrap : public BaseObject {
214213
// Because the values may be Reset(), cannot be made const.
215214
double async_id_ = kInvalidAsyncId;
216215
double trigger_async_id_;
217-
v8::Global<v8::Object> resource_;
218216
};
219217

220218
} // namespace node

src/env.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,10 @@ constexpr size_t kFsStatsBufferLength =
167167
V(handle_onclose_symbol, "handle_onclose") \
168168
V(no_message_symbol, "no_message_symbol") \
169169
V(oninit_symbol, "oninit") \
170-
V(owner_symbol, "owner") \
170+
V(owner_symbol, "owner_symbol") \
171171
V(onpskexchange_symbol, "onpskexchange") \
172+
V(resource_symbol, "resource_symbol") \
173+
V(trigger_async_id_symbol, "trigger_async_id_symbol") \
172174

173175
// Strings are per-isolate primitives but Environment proxies them
174176
// for the sake of convenience. Strings should be ASCII-only.

test/parallel/test-bootstrap-modules.js

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const expectedModules = new Set([
2424
'Internal Binding process_methods',
2525
'Internal Binding report',
2626
'Internal Binding string_decoder',
27+
'Internal Binding symbols',
2728
'Internal Binding task_queue',
2829
'Internal Binding timers',
2930
'Internal Binding trace_events',

0 commit comments

Comments
 (0)