Skip to content

Commit bcb919d

Browse files
committed
src: use cppgc to manage ContextifyContext
This simplifies the memory management of ContextifyContext, making all references visible to V8.
1 parent 2f35b1f commit bcb919d

File tree

4 files changed

+126
-47
lines changed

4 files changed

+126
-47
lines changed

src/env.cc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,9 +230,13 @@ void Environment::TrackContext(Local<Context> context) {
230230
contexts_[id].SetWeak();
231231
}
232232

233+
void Environment::PurgeTrackedEmptyContexts() {
234+
std::erase_if(contexts_, [&](auto&& el) { return el.IsEmpty(); });
235+
}
236+
233237
void Environment::UntrackContext(Local<Context> context) {
234238
HandleScope handle_scope(isolate_);
235-
std::erase_if(contexts_, [&](auto&& el) { return el.IsEmpty(); });
239+
PurgeTrackedEmptyContexts();
236240
for (auto it = contexts_.begin(); it != contexts_.end(); it++) {
237241
if (Local<Context> saved_context = PersistentToLocal::Weak(isolate_, *it);
238242
saved_context == context) {

src/env.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,8 @@ class Environment final : public MemoryRetainer {
703703
Realm* realm,
704704
const ContextInfo& info);
705705
void UnassignFromContext(v8::Local<v8::Context> context);
706+
void PurgeTrackedEmptyContexts();
707+
706708
void TrackShadowRealm(shadow_realm::ShadowRealm* realm);
707709
void UntrackShadowRealm(shadow_realm::ShadowRealm* realm);
708710

src/node_contextify.cc

Lines changed: 50 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,9 @@ Local<Name> Uint32ToName(Local<Context> context, uint32_t index) {
118118

119119
} // anonymous namespace
120120

121-
BaseObjectPtr<ContextifyContext> ContextifyContext::New(
122-
Environment* env, Local<Object> sandbox_obj, ContextOptions* options) {
121+
ContextifyContext* ContextifyContext::New(Environment* env,
122+
Local<Object> sandbox_obj,
123+
ContextOptions* options) {
123124
Local<ObjectTemplate> object_template;
124125
HandleScope scope(env->isolate());
125126
CHECK_IMPLIES(sandbox_obj.IsEmpty(), options->vanilla);
@@ -140,43 +141,56 @@ BaseObjectPtr<ContextifyContext> ContextifyContext::New(
140141
if (!(CreateV8Context(env->isolate(), object_template, snapshot_data, queue)
141142
.ToLocal(&v8_context))) {
142143
// Allocation failure, maximum call stack size reached, termination, etc.
143-
return BaseObjectPtr<ContextifyContext>();
144+
return nullptr;
144145
}
145146
return New(v8_context, env, sandbox_obj, options);
146147
}
147148

148-
void ContextifyContext::MemoryInfo(MemoryTracker* tracker) const {}
149+
void ContextifyContext::Trace(cppgc::Visitor* visitor) const {
150+
CppgcMixin::Trace(visitor);
151+
visitor->Trace(context_);
152+
}
149153

150154
ContextifyContext::ContextifyContext(Environment* env,
151155
Local<Object> wrapper,
152156
Local<Context> v8_context,
153157
ContextOptions* options)
154-
: BaseObject(env, wrapper),
155-
microtask_queue_(options->own_microtask_queue
158+
: microtask_queue_(options->own_microtask_queue
156159
? options->own_microtask_queue.release()
157160
: nullptr) {
161+
CppgcMixin::Wrap(this, env, wrapper);
162+
158163
context_.Reset(env->isolate(), v8_context);
159164
// This should only be done after the initial initializations of the context
160165
// global object is finished.
161166
DCHECK_NULL(v8_context->GetAlignedPointerFromEmbedderData(
162167
ContextEmbedderIndex::kContextifyContext));
163168
v8_context->SetAlignedPointerInEmbedderData(
164169
ContextEmbedderIndex::kContextifyContext, this);
165-
// It's okay to make this reference weak - V8 would create an internal
166-
// reference to this context via the constructor of the wrapper.
167-
// As long as the wrapper is alive, it's constructor is alive, and so
168-
// is the context.
169-
context_.SetWeak();
170170
}
171171

172-
ContextifyContext::~ContextifyContext() {
173-
Isolate* isolate = env()->isolate();
172+
void ContextifyContext::CleanEnvResource(Environment* env) {
173+
Isolate* isolate = env->isolate();
174174
HandleScope scope(isolate);
175175

176-
env()->UnassignFromContext(PersistentToLocal::Weak(isolate, context_));
176+
// PurgeTrackedEmptyContexts() is all we need because when the
177+
// wrapper is unreachable, the v8::Context is also already going
178+
// away and there's no point to reset its internal pointers.
179+
// In the context list, the global handle to the original
180+
// context should have already been empty and will be removed
181+
// in PurgeTrackedEmptyContexts().
182+
// TODO(joyeecheung): consider just using a default destructor.
183+
// We don't even need to purge the empty contexts in the
184+
// destructors if TrackContext() purges them, restricting the
185+
// amount of empty contexts in the list.
186+
env->PurgeTrackedEmptyContexts();
177187
context_.Reset();
178188
}
179189

190+
ContextifyContext::~ContextifyContext() {
191+
Clean();
192+
}
193+
180194
void ContextifyContext::InitializeGlobalTemplates(IsolateData* isolate_data) {
181195
DCHECK(isolate_data->contextify_wrapper_template().IsEmpty());
182196
Local<FunctionTemplate> global_func_template =
@@ -251,19 +265,18 @@ MaybeLocal<Context> ContextifyContext::CreateV8Context(
251265
return scope.Escape(ctx);
252266
}
253267

254-
BaseObjectPtr<ContextifyContext> ContextifyContext::New(
255-
Local<Context> v8_context,
256-
Environment* env,
257-
Local<Object> sandbox_obj,
258-
ContextOptions* options) {
268+
ContextifyContext* ContextifyContext::New(Local<Context> v8_context,
269+
Environment* env,
270+
Local<Object> sandbox_obj,
271+
ContextOptions* options) {
259272
HandleScope scope(env->isolate());
260273
CHECK_IMPLIES(sandbox_obj.IsEmpty(), options->vanilla);
261274
// This only initializes part of the context. The primordials are
262275
// only initialized when needed because even deserializing them slows
263276
// things down significantly and they are only needed in rare occasions
264277
// in the vm contexts.
265278
if (InitializeContextRuntime(v8_context).IsNothing()) {
266-
return BaseObjectPtr<ContextifyContext>();
279+
return nullptr;
267280
}
268281

269282
Local<Context> main_context = env->context();
@@ -300,7 +313,7 @@ BaseObjectPtr<ContextifyContext> ContextifyContext::New(
300313
info.origin = *origin_val;
301314
}
302315

303-
BaseObjectPtr<ContextifyContext> result;
316+
ContextifyContext* result;
304317
Local<Object> wrapper;
305318
{
306319
Context::Scope context_scope(v8_context);
@@ -315,7 +328,7 @@ BaseObjectPtr<ContextifyContext> ContextifyContext::New(
315328
ctor_name,
316329
static_cast<v8::PropertyAttribute>(v8::DontEnum))
317330
.IsNothing()) {
318-
return BaseObjectPtr<ContextifyContext>();
331+
return nullptr;
319332
}
320333
}
321334

@@ -328,21 +341,23 @@ BaseObjectPtr<ContextifyContext> ContextifyContext::New(
328341
env->host_defined_option_symbol(),
329342
options->host_defined_options_id)
330343
.IsNothing()) {
331-
return BaseObjectPtr<ContextifyContext>();
344+
return nullptr;
332345
}
333346

334347
env->AssignToContext(v8_context, nullptr, info);
335348

336349
if (!env->contextify_wrapper_template()
337350
->NewInstance(v8_context)
338351
.ToLocal(&wrapper)) {
339-
return BaseObjectPtr<ContextifyContext>();
352+
return nullptr;
340353
}
341354

342-
result =
343-
MakeBaseObject<ContextifyContext>(env, wrapper, v8_context, options);
344-
// The only strong reference to the wrapper will come from the sandbox.
345-
result->MakeWeak();
355+
result = cppgc::MakeGarbageCollected<ContextifyContext>(
356+
env->isolate()->GetCppHeap()->GetAllocationHandle(),
357+
env,
358+
wrapper,
359+
v8_context,
360+
options);
346361
}
347362

348363
Local<Object> wrapper_holder =
@@ -352,7 +367,7 @@ BaseObjectPtr<ContextifyContext> ContextifyContext::New(
352367
->SetPrivate(
353368
v8_context, env->contextify_context_private_symbol(), wrapper)
354369
.IsNothing()) {
355-
return BaseObjectPtr<ContextifyContext>();
370+
return nullptr;
356371
}
357372

358373
// Assign host_defined_options_id to the sandbox object or the global object
@@ -364,7 +379,7 @@ BaseObjectPtr<ContextifyContext> ContextifyContext::New(
364379
env->host_defined_option_symbol(),
365380
options->host_defined_options_id)
366381
.IsNothing()) {
367-
return BaseObjectPtr<ContextifyContext>();
382+
return nullptr;
368383
}
369384
return result;
370385
}
@@ -438,7 +453,7 @@ void ContextifyContext::MakeContext(const FunctionCallbackInfo<Value>& args) {
438453
options.host_defined_options_id = args[6].As<Symbol>();
439454

440455
TryCatchScope try_catch(env);
441-
BaseObjectPtr<ContextifyContext> context_ptr =
456+
ContextifyContext* context_ptr =
442457
ContextifyContext::New(env, sandbox, &options);
443458

444459
if (try_catch.HasCaught()) {
@@ -469,6 +484,10 @@ ContextifyContext* ContextifyContext::ContextFromContextifiedSandbox(
469484

470485
template <typename T>
471486
ContextifyContext* ContextifyContext::Get(const PropertyCallbackInfo<T>& args) {
487+
// TODO(joyeecheung): it should be fine to simply use
488+
// args.GetIsolate()->GetCurrentContext() and take the pointer at
489+
// ContextEmbedderIndex::kContextifyContext, as V8 is supposed to
490+
// push the creation context before invoking these callbacks.
472491
return Get(args.This());
473492
}
474493

src/node_contextify.h

Lines changed: 69 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
55

66
#include "base_object-inl.h"
7+
#include "cppgc/prefinalizer.h"
78
#include "cppgc_helpers.h"
89
#include "node_context_data.h"
910
#include "node_errors.h"
@@ -23,17 +24,67 @@ struct ContextOptions {
2324
bool vanilla = false;
2425
};
2526

26-
class ContextifyContext : public BaseObject {
27+
/**
28+
* The memory management of a vm context is as follows:
29+
*
30+
* user code
31+
* │
32+
* As global proxy or ▼
33+
* ┌──────────────┐ kSandboxObject embedder data ┌────────────────┐
34+
* ┌─► │ V8 Context │────────────────────────────────►│ Wrapper holder │
35+
* │ └──────────────┘ └───────┬────────┘
36+
* │ ▲ Object constructor/creation context │
37+
* │ │ │
38+
* │ ┌──────┴────────────┐ contextify_context_private_symbol │
39+
* │ │ ContextifyContext │◄────────────────────────────────────┘
40+
* │ │ JS Wrapper │◄──────────► ┌─────────────────────────┐
41+
* │ └───────────────────┘ cppgc │ node::ContextifyContext │
42+
* │ │ C++ Object │
43+
* └──────────────────────────────────► └─────────────────────────┘
44+
* v8::TracedReference / ContextEmbedderIndex::kContextifyContext
45+
*
46+
* There are two possibilities for the "wrapper holder":
47+
*
48+
* 1. When vm.constants.DONT_CONTEXTIFY is used, the wrapper holder is the V8
49+
* context's global proxy object
50+
* 2. Otherwise it's the arbitrary "sandbox object" that users pass into
51+
* vm.createContext() or a new empty object created internally if they pass
52+
* undefined.
53+
*
54+
* In 2, the global object of the new V8 context is created using
55+
* global_object_template with interceptors that perform any requested
56+
* operations on the global object in the context first on the sandbox object
57+
* living outside of the new context, then fall back to the global proxy of the
58+
* new context.
59+
*
60+
* It's critical for the user-accessible wrapper holder to keep the
61+
* ContextifyContext wrapper alive via contextify_context_private_symbol
62+
* so that the V8 context is always available to the user while they still
63+
* hold the vm "context" object alive.
64+
*
65+
* It's also critical for the V8 context to keep the wrapper holder
66+
* (specifically, the "sandbox object") as well as the node::ContextifyContext
67+
* C++ object alive, so that when the code runs inside the object and accesses
68+
* the global object, the interceptors can still access the "sandbox object"
69+
* as well as and perform operations
70+
* on them, even if users already relinquish access to the outer
71+
* "sandbox object".
72+
*
73+
* The v8::TracedReference and the ContextEmbedderIndex::kContextifyContext
74+
* slot in the context act as shortcuts from the node::ContextifyContext
75+
* C++ object to the V8 context.
76+
*/
77+
class ContextifyContext final : CPPGC_MIXIN(ContextifyContext) {
2778
public:
79+
SET_CPPGC_NAME(ContextifyContext)
80+
void Trace(cppgc::Visitor* visitor) const final;
81+
void CleanEnvResource(Environment* env) override;
82+
2883
ContextifyContext(Environment* env,
2984
v8::Local<v8::Object> wrapper,
3085
v8::Local<v8::Context> v8_context,
3186
ContextOptions* options);
32-
~ContextifyContext();
33-
34-
void MemoryInfo(MemoryTracker* tracker) const override;
35-
SET_MEMORY_INFO_NAME(ContextifyContext)
36-
SET_SELF_SIZE(ContextifyContext)
87+
~ContextifyContext() override;
3788

3889
static v8::MaybeLocal<v8::Context> CreateV8Context(
3990
v8::Isolate* isolate,
@@ -48,7 +99,7 @@ class ContextifyContext : public BaseObject {
4899
Environment* env, const v8::Local<v8::Object>& wrapper_holder);
49100

50101
inline v8::Local<v8::Context> context() const {
51-
return PersistentToLocal::Default(env()->isolate(), context_);
102+
return context_.Get(env()->isolate());
52103
}
53104

54105
inline v8::Local<v8::Object> global_proxy() const {
@@ -75,14 +126,17 @@ class ContextifyContext : public BaseObject {
75126
static void InitializeGlobalTemplates(IsolateData* isolate_data);
76127

77128
private:
78-
static BaseObjectPtr<ContextifyContext> New(Environment* env,
79-
v8::Local<v8::Object> sandbox_obj,
80-
ContextOptions* options);
129+
// FIXME(joyeecheung): this crashes at PrefinalizerRegistration.
130+
// CPPGC_USING_PRE_FINALIZER(ContextifyContext, Clean);
131+
132+
static ContextifyContext* New(Environment* env,
133+
v8::Local<v8::Object> sandbox_obj,
134+
ContextOptions* options);
81135
// Initialize a context created from CreateV8Context()
82-
static BaseObjectPtr<ContextifyContext> New(v8::Local<v8::Context> ctx,
83-
Environment* env,
84-
v8::Local<v8::Object> sandbox_obj,
85-
ContextOptions* options);
136+
static ContextifyContext* New(v8::Local<v8::Context> ctx,
137+
Environment* env,
138+
v8::Local<v8::Object> sandbox_obj,
139+
ContextOptions* options);
86140

87141
static bool IsStillInitializing(const ContextifyContext* ctx);
88142
static void MakeContext(const v8::FunctionCallbackInfo<v8::Value>& args);
@@ -140,7 +194,7 @@ class ContextifyContext : public BaseObject {
140194
static void IndexedPropertyEnumeratorCallback(
141195
const v8::PropertyCallbackInfo<v8::Array>& args);
142196

143-
v8::Global<v8::Context> context_;
197+
v8::TracedReference<v8::Context> context_;
144198
std::unique_ptr<v8::MicrotaskQueue> microtask_queue_;
145199
};
146200

0 commit comments

Comments
 (0)