| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 6762768 commit 90f70ed
5 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -223,7 +223,12 @@ void AsyncHooks::InstallPromiseHooks(Local<Context> ctx) { | |||
| 223 | 223 | : PersistentToLocal::Strong(js_promise_hooks_[3])); | |
| 224 | 224 | } | |
| 225 | 225 | ||
| 226 | + void Environment::PurgeTrackedEmptyContexts() { | ||
| 227 | + std::erase_if(contexts_, [&](auto&& el) { return el.IsEmpty(); }); | ||
| 228 | + } | ||
| 229 | + | ||
| 226 | 230 | void Environment::TrackContext(Local<Context> context) { | |
| 231 | + PurgeTrackedEmptyContexts(); | ||
| 227 | 232 | size_t id = contexts_.size(); | |
| 228 | 233 | contexts_.resize(id + 1); | |
| 229 | 234 | contexts_[id].Reset(isolate_, context); | |
@@ -232,7 +237,7 @@ void Environment::TrackContext(Local<Context> context) { | |||
| 232 | 237 | ||
| 233 | 238 | void Environment::UntrackContext(Local<Context> context) { | |
| 234 | 239 | HandleScope handle_scope(isolate_); | |
| 235 | - std::erase_if(contexts_, [&](auto&& el) { return el.IsEmpty(); }); | ||
| 240 | + PurgeTrackedEmptyContexts(); | ||
| 236 | 241 | for (auto it = contexts_.begin(); it != contexts_.end(); it++) { | |
| 237 | 242 | if (Local<Context> saved_context = PersistentToLocal::Weak(isolate_, *it); | |
| 238 | 243 | saved_context == context) { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1093,6 +1093,7 @@ class Environment final : public MemoryRetainer { | |||
| 1093 | 1093 | const char* errmsg); | |
| 1094 | 1094 | void TrackContext(v8::Local<v8::Context> context); | |
| 1095 | 1095 | void UntrackContext(v8::Local<v8::Context> context); | |
| 1096 | + void PurgeTrackedEmptyContexts(); | ||
| 1096 | 1097 | ||
| 1097 | 1098 | std::list<binding::DLib> loaded_addons_; | |
| 1098 | 1099 | v8::Isolate* const isolate_; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -118,8 +118,9 @@ Local<Name> Uint32ToName(Local<Context> context, uint32_t index) { | |||
| 118 | 118 | ||
| 119 | 119 | } // anonymous namespace | |
| 120 | 120 | ||
| 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) { | ||
| 123 | 124 | Local<ObjectTemplate> object_template; | |
| 124 | 125 | HandleScope scope(env->isolate()); | |
| 125 | 126 | CHECK_IMPLIES(sandbox_obj.IsEmpty(), options->vanilla); | |
@@ -140,41 +141,32 @@ BaseObjectPtr<ContextifyContext> ContextifyContext::New( | |||
| 140 | 141 | if (!(CreateV8Context(env->isolate(), object_template, snapshot_data, queue) | |
| 141 | 142 | .ToLocal(&v8_context))) { | |
| 142 | 143 | // Allocation failure, maximum call stack size reached, termination, etc. | |
| 143 | - return BaseObjectPtr<ContextifyContext>(); | ||
| 144 | + return {}; | ||
| 144 | 145 | } | |
| 145 | 146 | return New(v8_context, env, sandbox_obj, options); | |
| 146 | 147 | } | |
| 147 | 148 | ||
| 148 | - void ContextifyContext::MemoryInfo(MemoryTracker* tracker) const {} | ||
| 149 | + void ContextifyContext::Trace(cppgc::Visitor* visitor) const { | ||
| 150 | + CppgcMixin::Trace(visitor); | ||
| 151 | + visitor->Trace(context_); | ||
| 152 | + } | ||
| 149 | 153 | ||
| 150 | 154 | ContextifyContext::ContextifyContext(Environment* env, | |
| 151 | 155 | Local<Object> wrapper, | |
| 152 | 156 | Local<Context> v8_context, | |
| 153 | 157 | ContextOptions* options) | |
| 154 | - : BaseObject(env, wrapper), | ||
| 155 | - microtask_queue_(options->own_microtask_queue | ||
| 158 | + : microtask_queue_(options->own_microtask_queue | ||
| 156 | 159 | ? options->own_microtask_queue.release() | |
| 157 | 160 | : nullptr) { | |
| 161 | + CppgcMixin::Wrap(this, env, wrapper); | ||
| 162 | + | ||
| 158 | 163 | context_.Reset(env->isolate(), v8_context); | |
| 159 | 164 | // This should only be done after the initial initializations of the context | |
| 160 | 165 | // global object is finished. | |
| 161 | 166 | DCHECK_NULL(v8_context->GetAlignedPointerFromEmbedderData( | |
| 162 | 167 | ContextEmbedderIndex::kContextifyContext)); | |
| 163 | 168 | v8_context->SetAlignedPointerInEmbedderData( | |
| 164 | 169 | 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(); | ||
| 170 | - } | ||
| 171 | - | ||
| 172 | - ContextifyContext::~ContextifyContext() { | ||
| 173 | - Isolate* isolate = env()->isolate(); | ||
| 174 | - HandleScope scope(isolate); | ||
| 175 | - | ||
| 176 | - env()->UnassignFromContext(PersistentToLocal::Weak(isolate, context_)); | ||
| 177 | - context_.Reset(); | ||
| 178 | 170 | } | |
| 179 | 171 | ||
| 180 | 172 | void ContextifyContext::InitializeGlobalTemplates(IsolateData* isolate_data) { | |
@@ -251,19 +243,18 @@ MaybeLocal<Context> ContextifyContext::CreateV8Context( | |||
| 251 | 243 | return scope.Escape(ctx); | |
| 252 | 244 | } | |
| 253 | 245 | ||
| 254 | - BaseObjectPtr<ContextifyContext> ContextifyContext::New( | ||
| 255 | - Local<Context> v8_context, | ||
| 256 | - Environment* env, | ||
| 257 | - Local<Object> sandbox_obj, | ||
| 258 | - ContextOptions* options) { | ||
| 246 | + ContextifyContext* ContextifyContext::New(Local<Context> v8_context, | ||
| 247 | + Environment* env, | ||
| 248 | + Local<Object> sandbox_obj, | ||
| 249 | + ContextOptions* options) { | ||
| 259 | 250 | HandleScope scope(env->isolate()); | |
| 260 | 251 | CHECK_IMPLIES(sandbox_obj.IsEmpty(), options->vanilla); | |
| 261 | 252 | // This only initializes part of the context. The primordials are | |
| 262 | 253 | // only initialized when needed because even deserializing them slows | |
| 263 | 254 | // things down significantly and they are only needed in rare occasions | |
| 264 | 255 | // in the vm contexts. | |
| 265 | 256 | if (InitializeContextRuntime(v8_context).IsNothing()) { | |
| 266 | - return BaseObjectPtr<ContextifyContext>(); | ||
| 257 | + return {}; | ||
| 267 | 258 | } | |
| 268 | 259 | ||
| 269 | 260 | Local<Context> main_context = env->context(); | |
@@ -300,7 +291,7 @@ BaseObjectPtr<ContextifyContext> ContextifyContext::New( | |||
| 300 | 291 | info.origin = *origin_val; | |
| 301 | 292 | } | |
| 302 | 293 | ||
| 303 | - BaseObjectPtr<ContextifyContext> result; | ||
| 294 | + ContextifyContext* result; | ||
| 304 | 295 | Local<Object> wrapper; | |
| 305 | 296 | { | |
| 306 | 297 | Context::Scope context_scope(v8_context); | |
@@ -315,7 +306,7 @@ BaseObjectPtr<ContextifyContext> ContextifyContext::New( | |||
| 315 | 306 | ctor_name, | |
| 316 | 307 | static_cast<v8::PropertyAttribute>(v8::DontEnum)) | |
| 317 | 308 | .IsNothing()) { | |
| 318 | - return BaseObjectPtr<ContextifyContext>(); | ||
| 309 | + return {}; | ||
| 319 | 310 | } | |
| 320 | 311 | } | |
| 321 | 312 | ||
@@ -328,21 +319,23 @@ BaseObjectPtr<ContextifyContext> ContextifyContext::New( | |||
| 328 | 319 | env->host_defined_option_symbol(), | |
| 329 | 320 | options->host_defined_options_id) | |
| 330 | 321 | .IsNothing()) { | |
| 331 | - return BaseObjectPtr<ContextifyContext>(); | ||
| 322 | + return {}; | ||
| 332 | 323 | } | |
| 333 | 324 | ||
| 334 | 325 | env->AssignToContext(v8_context, nullptr, info); | |
| 335 | 326 | ||
| 336 | 327 | if (!env->contextify_wrapper_template() | |
| 337 | 328 | ->NewInstance(v8_context) | |
| 338 | 329 | .ToLocal(&wrapper)) { | |
| 339 | - return BaseObjectPtr<ContextifyContext>(); | ||
| 330 | + return {}; | ||
| 340 | 331 | } | |
| 341 | 332 | ||
| 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(); | ||
| 333 | + result = cppgc::MakeGarbageCollected<ContextifyContext>( | ||
| 334 | + env->isolate()->GetCppHeap()->GetAllocationHandle(), | ||
| 335 | + env, | ||
| 336 | + wrapper, | ||
| 337 | + v8_context, | ||
| 338 | + options); | ||
| 346 | 339 | } | |
| 347 | 340 | ||
| 348 | 341 | Local<Object> wrapper_holder = | |
@@ -352,7 +345,7 @@ BaseObjectPtr<ContextifyContext> ContextifyContext::New( | |||
| 352 | 345 | ->SetPrivate( | |
| 353 | 346 | v8_context, env->contextify_context_private_symbol(), wrapper) | |
| 354 | 347 | .IsNothing()) { | |
| 355 | - return BaseObjectPtr<ContextifyContext>(); | ||
| 348 | + return {}; | ||
| 356 | 349 | } | |
| 357 | 350 | ||
| 358 | 351 | // Assign host_defined_options_id to the sandbox object or the global object | |
@@ -364,7 +357,7 @@ BaseObjectPtr<ContextifyContext> ContextifyContext::New( | |||
| 364 | 357 | env->host_defined_option_symbol(), | |
| 365 | 358 | options->host_defined_options_id) | |
| 366 | 359 | .IsNothing()) { | |
| 367 | - return BaseObjectPtr<ContextifyContext>(); | ||
| 360 | + return {}; | ||
| 368 | 361 | } | |
| 369 | 362 | return result; | |
| 370 | 363 | } | |
@@ -438,7 +431,7 @@ void ContextifyContext::MakeContext(const FunctionCallbackInfo<Value>& args) { | |||
| 438 | 431 | options.host_defined_options_id = args[6].As<Symbol>(); | |
| 439 | 432 | ||
| 440 | 433 | TryCatchScope try_catch(env); | |
| 441 | - BaseObjectPtr<ContextifyContext> context_ptr = | ||
| 434 | + ContextifyContext* context_ptr = | ||
| 442 | 435 | ContextifyContext::New(env, sandbox, &options); | |
| 443 | 436 | ||
| 444 | 437 | if (try_catch.HasCaught()) { | |
@@ -469,6 +462,10 @@ ContextifyContext* ContextifyContext::ContextFromContextifiedSandbox( | |||
| 469 | 462 | ||
| 470 | 463 | template <typename T> | |
| 471 | 464 | ContextifyContext* ContextifyContext::Get(const PropertyCallbackInfo<T>& args) { | |
| 465 | + // TODO(joyeecheung): it should be fine to simply use | ||
| 466 | + // args.GetIsolate()->GetCurrentContext() and take the pointer at | ||
| 467 | + // ContextEmbedderIndex::kContextifyContext, as V8 is supposed to | ||
| 468 | + // push the creation context before invoking these callbacks. | ||
| 472 | 469 | return Get(args.This()); | |
| 473 | 470 | } | |
| 474 | 471 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -23,17 +23,73 @@ struct ContextOptions { | |||
| 23 | 23 | bool vanilla = false; | |
| 24 | 24 | }; | |
| 25 | 25 | ||
| 26 | - class ContextifyContext : public BaseObject { | ||
| 26 | + /** | ||
| 27 | + * The memory management of a vm context is as follows: | ||
| 28 | + * | ||
| 29 | + * user code | ||
| 30 | + * │ | ||
| 31 | + * As global proxy or ▼ | ||
| 32 | + * ┌──────────────┐ kSandboxObject embedder data ┌────────────────┐ | ||
| 33 | + * ┌─► │ V8 Context │────────────────────────────────►│ Wrapper holder │ | ||
| 34 | + * │ └──────────────┘ └───────┬────────┘ | ||
| 35 | + * │ ▲ Object constructor/creation context │ | ||
| 36 | + * │ │ │ | ||
| 37 | + * │ ┌──────┴────────────┐ contextify_context_private_symbol │ | ||
| 38 | + * │ │ ContextifyContext │◄────────────────────────────────────┘ | ||
| 39 | + * │ │ JS Wrapper │◄──────────► ┌─────────────────────────┐ | ||
| 40 | + * │ └───────────────────┘ cppgc │ node::ContextifyContext │ | ||
| 41 | + * │ │ C++ Object │ | ||
| 42 | + * └──────────────────────────────────► └─────────────────────────┘ | ||
| 43 | + * v8::TracedReference / ContextEmbedderIndex::kContextifyContext | ||
| 44 | + * | ||
| 45 | + * There are two possibilities for the "wrapper holder": | ||
| 46 | + * | ||
| 47 | + * 1. When vm.constants.DONT_CONTEXTIFY is used, the wrapper holder is the V8 | ||
| 48 | + * context's global proxy object | ||
| 49 | + * 2. Otherwise it's the arbitrary "sandbox object" that users pass into | ||
| 50 | + * vm.createContext() or a new empty object created internally if they pass | ||
| 51 | + * undefined. | ||
| 52 | + * | ||
| 53 | + * In 2, the global object of the new V8 context is created using | ||
| 54 | + * global_object_template with interceptors that perform any requested | ||
| 55 | + * operations on the global object in the context first on the sandbox object | ||
| 56 | + * living outside of the new context, then fall back to the global proxy of the | ||
| 57 | + * new context. | ||
| 58 | + * | ||
| 59 | + * It's critical for the user-accessible wrapper holder to keep the | ||
| 60 | + * ContextifyContext wrapper alive via contextify_context_private_symbol | ||
| 61 | + * so that the V8 context is always available to the user while they still | ||
| 62 | + * hold the vm "context" object alive. | ||
| 63 | + * | ||
| 64 | + * It's also critical for the V8 context to keep the wrapper holder | ||
| 65 | + * (specifically, the "sandbox object" if users pass one) as well as the | ||
| 66 | + * node::ContextifyContext C++ object alive, so that when the code | ||
| 67 | + * runs inside the object and accesses the global object, the interceptors | ||
| 68 | + * can still access the "sandbox object" and perform operations | ||
| 69 | + * on them, even if users already relinquish access to the outer | ||
| 70 | + * "sandbox object". | ||
| 71 | + * | ||
| 72 | + * The v8::TracedReference and the ContextEmbedderIndex::kContextifyContext | ||
| 73 | + * slot in the context only act as shortcuts between | ||
| 74 | + * the node::ContextifyContext C++ object and the V8 context. | ||
| 75 | + */ | ||
| 76 | + class ContextifyContext final : CPPGC_MIXIN(ContextifyContext) { | ||
| 27 | 77 | public: | |
| 78 | + SET_CPPGC_NAME(ContextifyContext) | ||
| 79 | + void Trace(cppgc::Visitor* visitor) const final; | ||
| 80 | + | ||
| 28 | 81 | ContextifyContext(Environment* env, | |
| 29 | 82 | v8::Local<v8::Object> wrapper, | |
| 30 | 83 | v8::Local<v8::Context> v8_context, | |
| 31 | 84 | ContextOptions* options); | |
| 32 | - ~ContextifyContext(); | ||
| 33 | 85 | ||
| 34 | - void MemoryInfo(MemoryTracker* tracker) const override; | ||
| 35 | - SET_MEMORY_INFO_NAME(ContextifyContext) | ||
| 36 | - SET_SELF_SIZE(ContextifyContext) | ||
| 86 | + // The destructors don't need to do anything because when the wrapper is | ||
| 87 | + // going away, the context is already going away or otherwise it would've | ||
| 88 | + // been holding the wrapper alive, so there's no need to reset the pointers | ||
| 89 | + // in the context. Also, any global handles to the context would've been | ||
| 90 | + // empty at this point, and the per-Environment context tracking code is | ||
| 91 | + // capable of dealing with empty handles from contexts purged elsewhere. | ||
| 92 | + ~ContextifyContext() = default; | ||
| 37 | 93 | ||
| 38 | 94 | static v8::MaybeLocal<v8::Context> CreateV8Context( | |
| 39 | 95 | v8::Isolate* isolate, | |
@@ -48,7 +104,7 @@ class ContextifyContext : public BaseObject { | |||
| 48 | 104 | Environment* env, const v8::Local<v8::Object>& wrapper_holder); | |
| 49 | 105 | ||
| 50 | 106 | inline v8::Local<v8::Context> context() const { | |
| 51 | - return PersistentToLocal::Default(env()->isolate(), context_); | ||
| 107 | + return context_.Get(env()->isolate()); | ||
| 52 | 108 | } | |
| 53 | 109 | ||
| 54 | 110 | inline v8::Local<v8::Object> global_proxy() const { | |
@@ -75,14 +131,14 @@ class ContextifyContext : public BaseObject { | |||
| 75 | 131 | static void InitializeGlobalTemplates(IsolateData* isolate_data); | |
| 76 | 132 | ||
| 77 | 133 | private: | |
| 78 | - static BaseObjectPtr<ContextifyContext> New(Environment* env, | ||
| 79 | - v8::Local<v8::Object> sandbox_obj, | ||
| 80 | - ContextOptions* options); | ||
| 134 | + static ContextifyContext* New(Environment* env, | ||
| 135 | + v8::Local<v8::Object> sandbox_obj, | ||
| 136 | + ContextOptions* options); | ||
| 81 | 137 | // 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); | ||
| 138 | + static ContextifyContext* New(v8::Local<v8::Context> ctx, | ||
| 139 | + Environment* env, | ||
| 140 | + v8::Local<v8::Object> sandbox_obj, | ||
| 141 | + ContextOptions* options); | ||
| 86 | 142 | ||
| 87 | 143 | static bool IsStillInitializing(const ContextifyContext* ctx); | |
| 88 | 144 | static void MakeContext(const v8::FunctionCallbackInfo<v8::Value>& args); | |
@@ -140,7 +196,7 @@ class ContextifyContext : public BaseObject { | |||
| 140 | 196 | static void IndexedPropertyEnumeratorCallback( | |
| 141 | 197 | const v8::PropertyCallbackInfo<v8::Array>& args); | |
| 142 | 198 | ||
| 143 | - v8::Global<v8::Context> context_; | ||
| 199 | + v8::TracedReference<v8::Context> context_; | ||
| 144 | 200 | std::unique_ptr<v8::MicrotaskQueue> microtask_queue_; | |
| 145 | 201 | }; | |
| 146 | 202 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -8,7 +8,7 @@ common.skipIfInspectorDisabled(); | |||
| 8 | 8 | const assert = require('assert'); | |
| 9 | 9 | const vm = require('vm'); | |
| 10 | 10 | const { Session } = require('inspector'); | |
| 11 | - | ||
| 11 | + const { gcUntil } = require('../common/gc'); | ||
| 12 | 12 | const session = new Session(); | |
| 13 | 13 | session.connect(); | |
| 14 | 14 | ||
@@ -66,8 +66,7 @@ async function testContextCreatedAndDestroyed() { | |||
| 66 | 66 | ||
| 67 | 67 | // GC is unpredictable... | |
| 68 | 68 | console.log('Checking/waiting for GC.'); | |
| 69 | - while (!contextDestroyed) | ||
| 70 | - global.gc(); | ||
| 69 | + await gcUntil('context destruction', () => contextDestroyed, Infinity, { type: 'major', execution: 'async' }); | ||
| 71 | 70 | console.log('Context destroyed.'); | |
| 72 | 71 | ||
| 73 | 72 | assert.strictEqual(contextDestroyed.params.executionContextId, id, | |
@@ -98,8 +97,7 @@ async function testContextCreatedAndDestroyed() { | |||
| 98 | 97 | ||
| 99 | 98 | // GC is unpredictable... | |
| 100 | 99 | console.log('Checking/waiting for GC again.'); | |
| 101 | - while (!contextDestroyed) | ||
| 102 | - global.gc(); | ||
| 100 | + await gcUntil('context destruction', () => contextDestroyed, Infinity, { type: 'major', execution: 'async' }); | ||
| 103 | 101 | console.log('Other context destroyed.'); | |
| 104 | 102 | } | |
| 105 | 103 | ||
@@ -124,8 +122,7 @@ async function testContextCreatedAndDestroyed() { | |||
| 124 | 122 | ||
| 125 | 123 | // GC is unpredictable... | |
| 126 | 124 | console.log('Checking/waiting for GC a third time.'); | |
| 127 | - while (!contextDestroyed) | ||
| 128 | - global.gc(); | ||
| 125 | + await gcUntil('context destruction', () => contextDestroyed, Infinity, { type: 'major', execution: 'async' }); | ||
| 129 | 126 | console.log('Context destroyed once again.'); | |
| 130 | 127 | } | |
| 131 | 128 | ||
@@ -148,8 +145,7 @@ async function testContextCreatedAndDestroyed() { | |||
| 148 | 145 | ||
| 149 | 146 | // GC is unpredictable... | |
| 150 | 147 | console.log('Checking/waiting for GC a fourth time.'); | |
| 151 | - while (!contextDestroyed) | ||
| 152 | - global.gc(); | ||
| 148 | + await gcUntil('context destruction', () => contextDestroyed, Infinity, { type: 'major', execution: 'async' }); | ||
| 153 | 149 | console.log('Context destroyed a fourth time.'); | |
| 154 | 150 | } | |
| 155 | 151 | } | |
| Back | FazBrowse Home | New Git URL |
0 commit comments