diff --git a/src/workerd/api/actor-state-test.c++ b/src/workerd/api/actor-state-test.c++ index 3db232d3c457..71b031d4fc53 100644 --- a/src/workerd/api/actor-state-test.c++ +++ b/src/workerd/api/actor-state-test.c++ @@ -27,6 +27,7 @@ jsg::V8System v8System; struct ActorStateContext: public jsg::Object, public jsg::ContextGlobal { JSG_RESOURCE_TYPE(ActorStateContext) { } + const jsg::Object& getSelfObject() const override { return *this; } }; JSG_DECLARE_ISOLATE_TYPE(ActorStateIsolate, ActorStateContext); diff --git a/src/workerd/api/basics-test.c++ b/src/workerd/api/basics-test.c++ index 8fd389d73844..7a22e31a8a13 100644 --- a/src/workerd/api/basics-test.c++ +++ b/src/workerd/api/basics-test.c++ @@ -53,6 +53,8 @@ struct BasicsContext: public jsg::Object, public jsg::ContextGlobal { JSG_RESOURCE_TYPE(BasicsContext) { JSG_METHOD(test); } + + const jsg::Object& getSelfObject() const override { return *this; } }; JSG_DECLARE_ISOLATE_TYPE( BasicsIsolate, diff --git a/src/workerd/api/crypto-impl-aes-test.c++ b/src/workerd/api/crypto-impl-aes-test.c++ index 14b4b7243752..dfdad4359a22 100644 --- a/src/workerd/api/crypto-impl-aes-test.c++ +++ b/src/workerd/api/crypto-impl-aes-test.c++ @@ -20,6 +20,7 @@ jsg::V8System v8System; struct CryptoContext: public jsg::Object, public jsg::ContextGlobal { JSG_RESOURCE_TYPE(CryptoContext) { } + const jsg::Object& getSelfObject() const override { return *this; } }; JSG_DECLARE_ISOLATE_TYPE(CryptoIsolate, CryptoContext); diff --git a/src/workerd/api/global-scope.h b/src/workerd/api/global-scope.h index 863d4a43f02a..09c8808c0928 100644 --- a/src/workerd/api/global-scope.h +++ b/src/workerd/api/global-scope.h @@ -137,6 +137,8 @@ class PromiseRejectionEvent: public Event { class WorkerGlobalScope: public EventTarget, public jsg::ContextGlobal { public: + const jsg::Object& getSelfObject() const override { return *this; } + jsg::Unimplemented importScripts(kj::String s) { return {}; }; JSG_RESOURCE_TYPE(WorkerGlobalScope, CompatibilityFlags::Reader flags) { diff --git a/src/workerd/api/streams/queue-test.c++ b/src/workerd/api/streams/queue-test.c++ index cecce4f0d29e..219c58c1500f 100644 --- a/src/workerd/api/streams/queue-test.c++ +++ b/src/workerd/api/streams/queue-test.c++ @@ -13,6 +13,7 @@ jsg::V8System v8System; struct QueueContext: public jsg::Object, public jsg::ContextGlobal { JSG_RESOURCE_TYPE(QueueContext) {} + const jsg::Object& getSelfObject() const override { return *this; } }; JSG_DECLARE_ISOLATE_TYPE(QueueIsolate, QueueContext); diff --git a/src/workerd/io/promise-wrapper-test.c++ b/src/workerd/io/promise-wrapper-test.c++ index adafffb944ee..0b69581fd009 100644 --- a/src/workerd/io/promise-wrapper-test.c++ +++ b/src/workerd/io/promise-wrapper-test.c++ @@ -15,6 +15,7 @@ namespace { jsg::V8System v8System; struct CaptureThrowContext: public jsg::Object, public ContextGlobal { + const jsg::Object& getSelfObject() const override { return *this; } kj::Promise test1() { JSG_FAIL_REQUIRE(TypeError, "boom"); } diff --git a/src/workerd/jsg/buffersource-test.c++ b/src/workerd/jsg/buffersource-test.c++ index 82cb27c7408a..a500e3b9f4a0 100644 --- a/src/workerd/jsg/buffersource-test.c++ +++ b/src/workerd/jsg/buffersource-test.c++ @@ -12,6 +12,7 @@ namespace { V8System v8System; struct BufferSourceContext: public jsg::Object, public jsg::ContextGlobal { + const jsg::Object& getSelfObject() const override { return *this; } BufferSource takeBufferSource(BufferSource buf) { auto ptr = buf.asArrayPtr(); KJ_ASSERT(!buf.isDetached()); diff --git a/src/workerd/jsg/dom-exception-test.c++ b/src/workerd/jsg/dom-exception-test.c++ index f5ba92f036b9..cd2942e1e5e0 100644 --- a/src/workerd/jsg/dom-exception-test.c++ +++ b/src/workerd/jsg/dom-exception-test.c++ @@ -12,6 +12,7 @@ namespace { V8System v8System; struct DOMExceptionContext: public Object, public ContextGlobal { + const jsg::Object& getSelfObject() const override { return *this; } JSG_RESOURCE_TYPE(DOMExceptionContext) { JSG_NESTED_TYPE(DOMException); } diff --git a/src/workerd/jsg/function-test.c++ b/src/workerd/jsg/function-test.c++ index bbee6d850f15..e2e3f557753c 100644 --- a/src/workerd/jsg/function-test.c++ +++ b/src/workerd/jsg/function-test.c++ @@ -8,7 +8,10 @@ namespace workerd::jsg::test { namespace { V8System v8System; -class ContextGlobalObject: public Object, public ContextGlobal {}; +class ContextGlobalObject: public Object, public ContextGlobal { +public: + const jsg::Object& getSelfObject() const override { return *this; } +}; struct CallbackContext: public ContextGlobalObject { kj::String callCallback(Lock& js, jsg::Function function) { diff --git a/src/workerd/jsg/iterator-test.c++ b/src/workerd/jsg/iterator-test.c++ index 726c6506e551..17e92ed67156 100644 --- a/src/workerd/jsg/iterator-test.c++ +++ b/src/workerd/jsg/iterator-test.c++ @@ -10,6 +10,7 @@ namespace { V8System v8System; struct GeneratorContext: public Object, public ContextGlobal { + const jsg::Object& getSelfObject() const override { return *this; } uint generatorTest(Lock& js, Generator generator) { diff --git a/src/workerd/jsg/jsg-test.c++ b/src/workerd/jsg/jsg-test.c++ index 0bcf509d0cf7..118878f8e4a5 100644 --- a/src/workerd/jsg/jsg-test.c++ +++ b/src/workerd/jsg/jsg-test.c++ @@ -35,7 +35,10 @@ static_assert(kj::_::isDisallowedInCoroutine()); // ======================================================================================== V8System v8System; -class ContextGlobalObject: public Object, public ContextGlobal {}; +class ContextGlobalObject: public Object, public ContextGlobal { +public: + const jsg::Object& getSelfObject() const override { return *this; } +}; struct TestContext: public ContextGlobalObject { JSG_RESOURCE_TYPE(TestContext) {} diff --git a/src/workerd/jsg/jsg.h b/src/workerd/jsg/jsg.h index 2b0287f981af..7b7e8d102aa9 100644 --- a/src/workerd/jsg/jsg.h +++ b/src/workerd/jsg/jsg.h @@ -16,6 +16,7 @@ #include #include #include +#include #include "macro-meta.h" #include "wrappable.h" #include "util.h" @@ -1529,13 +1530,26 @@ class ModuleRegistry; // The lifetime of the global object matches the lifetime of the JavaScript context. class ContextGlobal { public: - ContextGlobal() {} + inline ContextGlobal() + : self(kj::refcounted>(kj::Badge {}, *this)) {} + inline virtual ~ContextGlobal() noexcept(false) { + KJ_DASSERT(self.get() != nullptr); + self->invalidate(); + } KJ_DISALLOW_COPY_AND_MOVE(ContextGlobal); ModuleRegistry& getModuleRegistry() { return *moduleRegistry; } + inline kj::Own> addWeakRef() { + KJ_ASSERT(self.get() != nullptr); + return self->addRef(); + } + + virtual const Object& getSelfObject() const = 0; + private: + kj::Own> self; kj::Own moduleRegistry; void setModuleRegistry(kj::Own registry) { @@ -2483,6 +2497,10 @@ inline v8::Local JsContext::getHandle(Lock& js) { return handle.Get(js.v8Isolate); } +MemoryTracker& MemoryTracker::trackField(const ContextGlobal& value) { + return trackField(nullptr, value.getSelfObject()); +} + } // namespace workerd::jsg // These two includes are needed for the JSG type glue macros to work. diff --git a/src/workerd/jsg/jsvalue-test.c++ b/src/workerd/jsg/jsvalue-test.c++ index 5d7900ffa9c8..7bcbec653db4 100644 --- a/src/workerd/jsg/jsvalue-test.c++ +++ b/src/workerd/jsg/jsvalue-test.c++ @@ -8,7 +8,10 @@ namespace workerd::jsg::test { namespace { V8System v8System; -class ContextGlobalObject: public Object, public ContextGlobal {}; +class ContextGlobalObject: public Object, public ContextGlobal { +public: + const jsg::Object& getSelfObject() const override { return *this; } +}; struct JsValueContext: public ContextGlobalObject { JsRef persisted; diff --git a/src/workerd/jsg/memory-test.c++ b/src/workerd/jsg/memory-test.c++ index ba94e2340702..9f7340f1ee2d 100644 --- a/src/workerd/jsg/memory-test.c++ +++ b/src/workerd/jsg/memory-test.c++ @@ -11,7 +11,10 @@ namespace workerd::jsg::test { namespace { V8System v8System; -class ContextGlobalObject: public Object, public ContextGlobal {}; +class ContextGlobalObject: public Object, public ContextGlobal { +public: + const jsg::Object& getSelfObject() const override { return *this; } +}; struct Foo: public Object { kj::String bar = kj::str("test"); diff --git a/src/workerd/jsg/memory.h b/src/workerd/jsg/memory.h index 09dc810d7731..459a72a78e2f 100644 --- a/src/workerd/jsg/memory.h +++ b/src/workerd/jsg/memory.h @@ -27,6 +27,7 @@ class MemoryRetainerNode; template class V8Ref; template class Ref; +class ContextGlobal; enum class MemoryInfoDetachedState { UNKNOWN, @@ -90,6 +91,8 @@ class MemoryTracker final { kj::StringPtr edgeName, size_t size, kj::Maybe nodeName = kj::none) KJ_LIFETIMEBOUND; + inline MemoryTracker& trackField(const ContextGlobal& value) KJ_LIFETIMEBOUND; + template inline MemoryTracker& trackField( kj::StringPtr edgeName, diff --git a/src/workerd/jsg/promise-test.c++ b/src/workerd/jsg/promise-test.c++ index c6dd36f8e2aa..bd180f7aadd8 100644 --- a/src/workerd/jsg/promise-test.c++ +++ b/src/workerd/jsg/promise-test.c++ @@ -14,6 +14,7 @@ int promiseTestResult = 0; kj::String catchTestResult; struct PromiseContext: public jsg::Object, public jsg::ContextGlobal { + const jsg::Object& getSelfObject() const override { return *this; } Promise makePromise(jsg::Lock& js) { auto [ p, r ] = js.newPromiseAndResolver(); resolver = kj::mv(r); diff --git a/src/workerd/jsg/resource-test.c++ b/src/workerd/jsg/resource-test.c++ index 408d73c050de..173962f66134 100644 --- a/src/workerd/jsg/resource-test.c++ +++ b/src/workerd/jsg/resource-test.c++ @@ -9,7 +9,10 @@ namespace workerd::jsg::test { namespace { V8System v8System; -class ContextGlobalObject: public Object, public ContextGlobal {}; +class ContextGlobalObject: public Object, public ContextGlobal { +public: + const jsg::Object& getSelfObject() const override { return *this; } +}; struct BoxContext: public ContextGlobalObject { JSG_RESOURCE_TYPE(BoxContext) { diff --git a/src/workerd/jsg/setup-test.c++ b/src/workerd/jsg/setup-test.c++ index 9d412a624175..fdfe42ec461b 100644 --- a/src/workerd/jsg/setup-test.c++ +++ b/src/workerd/jsg/setup-test.c++ @@ -10,6 +10,7 @@ namespace { V8System v8System; struct EvalContext: public Object, public ContextGlobal { + const jsg::Object& getSelfObject() const override { return *this; } JSG_RESOURCE_TYPE(EvalContext) {} }; JSG_DECLARE_ISOLATE_TYPE(EvalIsolate, EvalContext); @@ -46,6 +47,7 @@ KJ_TEST("eval() is blocked") { // ======================================================================================== struct ConfigContext: public Object, public ContextGlobal { + const jsg::Object& getSelfObject() const override { return *this; } struct Nested: public Object { JSG_RESOURCE_TYPE(Nested, int configuration) { KJ_EXPECT(configuration == 123, configuration); diff --git a/src/workerd/jsg/setup.c++ b/src/workerd/jsg/setup.c++ index bad3fd09cec6..ec79265fe1e3 100644 --- a/src/workerd/jsg/setup.c++ +++ b/src/workerd/jsg/setup.c++ @@ -170,6 +170,12 @@ void IsolateBase::buildEmbedderGraph(v8::Isolate* isolate, void IsolateBase::jsgGetMemoryInfo(MemoryTracker& tracker) const { tracker.trackField("uuid", uuid); tracker.trackField("heapTracer", heapTracer); + for (auto& ctx : contextGlobals) { + KJ_ASSERT(ctx.get() != nullptr); + ctx->runIfAlive([&](ContextGlobal& context) { + tracker.trackField(context); + }); + } } void IsolateBase::deferDestruction(Item item) { @@ -680,4 +686,16 @@ kj::StringPtr IsolateBase::getUuid() { return uuid.emplace(randomUUID(kj::none)); } +void IsolateBase::purgeInvalidContextGlobals() { + auto released = contextGlobals.releaseAsArray(); + contextGlobals.clear(); + KJ_ASSERT(contextGlobals.empty()); + for (auto& ctx : released) { + KJ_ASSERT(ctx.get() != nullptr); + if (ctx->isValid()) { + contextGlobals.add(kj::mv(ctx)); + } + } +} + } // namespace workerd::jsg diff --git a/src/workerd/jsg/setup.h b/src/workerd/jsg/setup.h index f78833e0021e..e0f615653092 100644 --- a/src/workerd/jsg/setup.h +++ b/src/workerd/jsg/setup.h @@ -239,13 +239,15 @@ class IsolateBase { static void jitCodeEvent(const v8::JitCodeEvent* event) noexcept; - friend class IsolateBase; friend kj::Maybe getJsStackTrace(void* ucontext, kj::ArrayPtr scratch); HeapTracer heapTracer; std::unique_ptr cppgcHeap; kj::Own observer; + kj::Vector>> contextGlobals; + void purgeInvalidContextGlobals(); + friend class Data; friend class Wrappable; friend class HeapTracer; @@ -355,6 +357,7 @@ class Isolate: public IsolateBase { // order for V8's stack-scanning GC to find them. Lock(const Isolate& isolate, V8StackScope&) : jsg::Lock(isolate.ptr), jsgIsolate(const_cast(isolate)) { + jsgIsolate.purgeInvalidContextGlobals(); jsgIsolate.clearDestructionQueue(); } KJ_DISALLOW_COPY_AND_MOVE(Lock); @@ -436,7 +439,10 @@ class Isolate: public IsolateBase { // allocate the object (forwarding arguments to the constructor) and return something like // a Ref. - return jsgIsolate.wrapper->newContext(*this, jsgIsolate.getObserver(), (T*)nullptr, kj::fwd(args)...); + auto ctx = jsgIsolate.wrapper->newContext(*this, jsgIsolate.getObserver(), + (T*)nullptr, kj::fwd(args)...); + jsgIsolate.contextGlobals.add(ctx->addWeakRef()); + return kj::mv(ctx); } private: diff --git a/src/workerd/jsg/string-test.c++ b/src/workerd/jsg/string-test.c++ index 258871284293..c8957931e0ce 100644 --- a/src/workerd/jsg/string-test.c++ +++ b/src/workerd/jsg/string-test.c++ @@ -351,6 +351,7 @@ KJ_TEST("UsvString from kj::String") { V8System v8System; struct UsvStringContext: public jsg::Object, public jsg::ContextGlobal { + const jsg::Object& getSelfObject() const override { return *this; } UsvString testUsv(jsg::Optional str) { return kj::mv(str).orDefault(usv("undefined")); } diff --git a/src/workerd/jsg/struct-test.c++ b/src/workerd/jsg/struct-test.c++ index d422acb47f7f..0ba501cc4298 100644 --- a/src/workerd/jsg/struct-test.c++ +++ b/src/workerd/jsg/struct-test.c++ @@ -17,6 +17,7 @@ struct SelfStruct { }; struct StructContext: public Object, public ContextGlobal { + const jsg::Object& getSelfObject() const override { return *this; } kj::String readTestStruct(TestStruct s) { return kj::str(s.str, ", ", s.num, ", ", s.box->value); } diff --git a/src/workerd/jsg/tracing-test.c++ b/src/workerd/jsg/tracing-test.c++ index ece7a566ae10..ab030406b50a 100644 --- a/src/workerd/jsg/tracing-test.c++ +++ b/src/workerd/jsg/tracing-test.c++ @@ -102,6 +102,7 @@ private: }; struct TraceTestContext: public Object, public ContextGlobal { + const jsg::Object& getSelfObject() const override { return *this; } kj::Maybe> strongRef; // A strong reference to a NumberBox which may be get and set. diff --git a/src/workerd/jsg/type-wrapper-test.c++ b/src/workerd/jsg/type-wrapper-test.c++ index bc95a8b6e8eb..7be5f95179b2 100644 --- a/src/workerd/jsg/type-wrapper-test.c++ +++ b/src/workerd/jsg/type-wrapper-test.c++ @@ -8,7 +8,10 @@ namespace workerd::jsg::test { namespace { V8System v8System; -class ContextGlobalObject: public Object, public ContextGlobal {}; +class ContextGlobalObject: public Object, public ContextGlobal { +public: + const jsg::Object& getSelfObject() const override { return *this; } +}; struct InfoContext: public ContextGlobalObject { struct WantInfo: public Object { diff --git a/src/workerd/jsg/util-test.c++ b/src/workerd/jsg/util-test.c++ index aa2acacd93c2..527274a20543 100644 --- a/src/workerd/jsg/util-test.c++ +++ b/src/workerd/jsg/util-test.c++ @@ -11,7 +11,10 @@ namespace workerd::jsg::test { namespace { V8System v8System; -class ContextGlobalObject: public Object, public ContextGlobal {}; +class ContextGlobalObject: public Object, public ContextGlobal { +public: + const jsg::Object& getSelfObject() const override { return *this; } +}; struct FreezeContext: public ContextGlobalObject { void recursivelyFreeze(v8::Local value, v8::Isolate* isolate) { diff --git a/src/workerd/jsg/value-test.c++ b/src/workerd/jsg/value-test.c++ index ba09a7e3f7c5..074a70ecee39 100644 --- a/src/workerd/jsg/value-test.c++ +++ b/src/workerd/jsg/value-test.c++ @@ -8,7 +8,10 @@ namespace workerd::jsg::test { namespace { V8System v8System; -class ContextGlobalObject: public Object, public ContextGlobal {}; +class ContextGlobalObject: public Object, public ContextGlobal { +public: + const jsg::Object& getSelfObject() const override { return *this; } +}; struct BoolContext: public ContextGlobalObject { kj::String takeBool(bool b) {