Skip to content

Commit

Permalink
Implement heap snapshot memory tracking for globals
Browse files Browse the repository at this point in the history
This is a bit more complicated than it otherwise ideally should be
because context globals are handled so specially by jsg. Specifically,
they are not included in traceable handles and we don't generally
keep track of them. However, in order to trace them for the heap
snapshot we need to.
  • Loading branch information
jasnell committed Feb 5, 2024
1 parent 8691915 commit dbe4e03
Show file tree
Hide file tree
Showing 26 changed files with 97 additions and 11 deletions.
1 change: 1 addition & 0 deletions src/workerd/api/actor-state-test.c++
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
2 changes: 2 additions & 0 deletions src/workerd/api/basics-test.c++
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions src/workerd/api/crypto-impl-aes-test.c++
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
2 changes: 2 additions & 0 deletions src/workerd/api/global-scope.h
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,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) {
Expand Down
1 change: 1 addition & 0 deletions src/workerd/api/streams/queue-test.c++
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
1 change: 1 addition & 0 deletions src/workerd/io/promise-wrapper-test.c++
Original file line number Diff line number Diff line change
Expand Up @@ -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<int> test1() {
JSG_FAIL_REQUIRE(TypeError, "boom");
}
Expand Down
1 change: 1 addition & 0 deletions src/workerd/jsg/buffersource-test.c++
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
1 change: 1 addition & 0 deletions src/workerd/jsg/dom-exception-test.c++
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
5 changes: 4 additions & 1 deletion src/workerd/jsg/function-test.c++
Original file line number Diff line number Diff line change
Expand Up @@ -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<kj::String(kj::StringPtr, double)> function) {
Expand Down
1 change: 1 addition & 0 deletions src/workerd/jsg/iterator-test.c++
Original file line number Diff line number Diff line change
Expand Up @@ -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<kj::String> generator) {

Expand Down
5 changes: 4 additions & 1 deletion src/workerd/jsg/jsg-test.c++
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ static_assert(kj::_::isDisallowedInCoroutine<Lock*>());
// ========================================================================================

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) {}
Expand Down
20 changes: 19 additions & 1 deletion src/workerd/jsg/jsg.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <v8.h>
#include <v8-profiler.h>
#include <workerd/jsg/memory.h>
#include <workerd/util/weak-refs.h>
#include "macro-meta.h"
#include "wrappable.h"
#include "util.h"
Expand Down Expand Up @@ -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<WeakRef<ContextGlobal>>(kj::Badge<ContextGlobal> {}, *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<WeakRef<ContextGlobal>> addWeakRef() {
KJ_ASSERT(self.get() != nullptr);
return self->addRef();
}

virtual const Object& getSelfObject() const = 0;

private:
kj::Own<WeakRef<ContextGlobal>> self;
kj::Own<ModuleRegistry> moduleRegistry;

void setModuleRegistry(kj::Own<ModuleRegistry> registry) {
Expand Down Expand Up @@ -2483,6 +2497,10 @@ inline v8::Local<v8::Context> JsContext<T>::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.
Expand Down
5 changes: 4 additions & 1 deletion src/workerd/jsg/jsvalue-test.c++
Original file line number Diff line number Diff line change
Expand Up @@ -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<JsValue> persisted;
Expand Down
5 changes: 4 additions & 1 deletion src/workerd/jsg/memory-test.c++
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
3 changes: 3 additions & 0 deletions src/workerd/jsg/memory.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class MemoryRetainerNode;

template <typename T> class V8Ref;
template <typename T> class Ref;
class ContextGlobal;

enum class MemoryInfoDetachedState {
UNKNOWN,
Expand Down Expand Up @@ -90,6 +91,8 @@ class MemoryTracker final {
kj::StringPtr edgeName, size_t size,
kj::Maybe<kj::StringPtr> nodeName = kj::none) KJ_LIFETIMEBOUND;

inline MemoryTracker& trackField(const ContextGlobal& value) KJ_LIFETIMEBOUND;

template <MemoryRetainer T>
inline MemoryTracker& trackField(
kj::StringPtr edgeName,
Expand Down
1 change: 1 addition & 0 deletions src/workerd/jsg/promise-test.c++
Original file line number Diff line number Diff line change
Expand Up @@ -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<kj::String> makePromise(jsg::Lock& js) {
auto [ p, r ] = js.newPromiseAndResolver<int>();
resolver = kj::mv(r);
Expand Down
5 changes: 4 additions & 1 deletion src/workerd/jsg/resource-test.c++
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
2 changes: 2 additions & 0 deletions src/workerd/jsg/setup-test.c++
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
18 changes: 18 additions & 0 deletions src/workerd/jsg/setup.c++
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
10 changes: 8 additions & 2 deletions src/workerd/jsg/setup.h
Original file line number Diff line number Diff line change
Expand Up @@ -239,13 +239,15 @@ class IsolateBase {

static void jitCodeEvent(const v8::JitCodeEvent* event) noexcept;

friend class IsolateBase;
friend kj::Maybe<kj::StringPtr> getJsStackTrace(void* ucontext, kj::ArrayPtr<char> scratch);

HeapTracer heapTracer;
std::unique_ptr<v8::CppHeap> cppgcHeap;
kj::Own<IsolateObserver> observer;

kj::Vector<kj::Own<WeakRef<ContextGlobal>>> contextGlobals;
void purgeInvalidContextGlobals();

friend class Data;
friend class Wrappable;
friend class HeapTracer;
Expand Down Expand Up @@ -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&>(isolate)) {
jsgIsolate.purgeInvalidContextGlobals();
jsgIsolate.clearDestructionQueue();
}
KJ_DISALLOW_COPY_AND_MOVE(Lock);
Expand Down Expand Up @@ -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>(args)...);
auto ctx = jsgIsolate.wrapper->newContext(*this, jsgIsolate.getObserver(),
(T*)nullptr, kj::fwd<Args>(args)...);
jsgIsolate.contextGlobals.add(ctx->addWeakRef());
return kj::mv(ctx);
}

private:
Expand Down
1 change: 1 addition & 0 deletions src/workerd/jsg/string-test.c++
Original file line number Diff line number Diff line change
Expand Up @@ -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<UsvString> str) {
return kj::mv(str).orDefault(usv("undefined"));
}
Expand Down
1 change: 1 addition & 0 deletions src/workerd/jsg/struct-test.c++
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
1 change: 1 addition & 0 deletions src/workerd/jsg/tracing-test.c++
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ private:
};

struct TraceTestContext: public Object, public ContextGlobal {
const jsg::Object& getSelfObject() const override { return *this; }
kj::Maybe<jsg::Ref<NumberBox>> strongRef;
// A strong reference to a NumberBox which may be get and set.

Expand Down
5 changes: 4 additions & 1 deletion src/workerd/jsg/type-wrapper-test.c++
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
5 changes: 4 additions & 1 deletion src/workerd/jsg/util-test.c++
Original file line number Diff line number Diff line change
Expand Up @@ -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<v8::Value> value, v8::Isolate* isolate) {
Expand Down
5 changes: 4 additions & 1 deletion src/workerd/jsg/value-test.c++
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down

0 comments on commit dbe4e03

Please sign in to comment.