src: track cppgc wrappers with a list in Realm · nodejs/node@a35cc21
@@ -6,14 +6,19 @@
66#include <type_traits> // std::remove_reference
77#include "cppgc/garbage-collected.h"
88#include "cppgc/name-provider.h"
9-#include "env.h"
9+#include "cppgc/persistent.h"
1010#include "memory_tracker.h"
11+#include "util.h"
1112#include "v8-cppgc.h"
1213#include "v8-sandbox.h"
1314#include "v8.h"
14151516namespace node {
161718+class Environment;
19+class Realm;
20+class CppgcWrapperListNode;
21+1722/**
1823 * This is a helper mixin with a BaseObject-like interface to help
1924 * implementing wrapper objects managed by V8's cppgc (Oilpan) library.
@@ -25,20 +30,29 @@ namespace node {
2530 * with V8's GC scheduling.
2631 *
2732 * A cppgc-managed native wrapper should look something like this, note
28- * that per cppgc rules, CPPGC_MIXIN(Klass) must be at the left-most
33+ * that per cppgc rules, CPPGC_MIXIN(MyWrap) must be at the left-most
2934 * position in the hierarchy (which ensures cppgc::GarbageCollected
3035 * is at the left-most position).
3136 *
32- * class Klass final : CPPGC_MIXIN(Klass) {
37+ * class MyWrap final : CPPGC_MIXIN(MyWrap) {
3338 * public:
34- * SET_CPPGC_NAME(Klass) // Sets the heap snapshot name to "Node / Klass"
39+ * SET_CPPGC_NAME(MyWrap) // Sets the heap snapshot name to "Node / MyWrap"
3540 * void Trace(cppgc::Visitor* visitor) const final {
3641 * CppgcMixin::Trace(visitor);
3742 * visitor->Trace(...); // Trace any additional owned traceable data
3843 * }
3944 * }
45+ *
46+ * If the wrapper needs to perform cleanups when it's destroyed and that
47+ * cleanup relies on a living Node.js `Realm`, it should implement a
48+ * pattern like this:
49+ *
50+ * ~MyWrap() { this->Destroy(); }
51+ * void Clean(Realm* env) override {
52+ * // Do cleanup that relies on a living Environemnt.
53+ * }
4054 */
41-class CppgcMixin : public cppgc::GarbageCollectedMixin {
55+class CppgcMixin : public cppgc::GarbageCollectedMixin, public MemoryRetainer {
4256public:
4357// To help various callbacks access wrapper objects with different memory
4458// management, cppgc-managed objects share the same layout as BaseObjects.
@@ -48,48 +62,58 @@ class CppgcMixin : public cppgc::GarbageCollectedMixin {
4862// invoked from the child class constructor, per cppgc::GarbageCollectedMixin
4963// rules.
5064template <typename T>
51-static void Wrap(T* ptr, Environment* env, v8::Local<v8::Object> obj) {
52-CHECK_GE(obj->InternalFieldCount(), T::kInternalFieldCount);
53- ptr->env_ = env;
54- v8::Isolate* isolate = env->isolate();
55- ptr->traced_reference_ = v8::TracedReference<v8::Object>(isolate, obj);
56- v8::Object::Wrap<v8::CppHeapPointerTag::kDefaultTag>(isolate, obj, ptr);
57-// Keep the layout consistent with BaseObjects.
58- obj->SetAlignedPointerInInternalField(
59-kEmbedderType, env->isolate_data()->embedder_id_for_cppgc());
60- obj->SetAlignedPointerInInternalField(kSlot, ptr);
61- }
65+static inline void Wrap(T* ptr, Realm* realm, v8::Local<v8::Object> obj);
66+template <typename T>
67+static inline void Wrap(T* ptr, Environment* env, v8::Local<v8::Object> obj);
626863- v8::Local<v8::Object> object() const {
64-return traced_reference_.Get(env_->isolate());
69+inline v8::Local<v8::Object> object() const;
70+inline Environment* env() const;
71+inline Realm* realm() const { return realm_; }
72+inline v8::Local<v8::Object> object(v8::Isolate* isolate) const {
73+return traced_reference_.Get(isolate);
6574 }
667567- Environment* env() const { return env_; }
68-6976template <typename T>
70-static T* Unwrap(v8::Local<v8::Object> obj) {
71-// We are not using v8::Object::Unwrap currently because that requires
72-// access to isolate which the ASSIGN_OR_RETURN_UNWRAP macro that we'll shim
73-// with ASSIGN_OR_RETURN_UNWRAP_GC doesn't take, and we also want a
74-// signature consistent with BaseObject::Unwrap() to avoid churn. Since
75-// cppgc-managed objects share the same layout as BaseObjects, just unwrap
76-// from the pointer in the internal field, which should be valid as long as
77-// the object is still alive.
78-if (obj->InternalFieldCount() != T::kInternalFieldCount) {
79-return nullptr;
80- }
81- T* ptr = static_cast<T*>(obj->GetAlignedPointerFromInternalField(T::kSlot));
82-return ptr;
83- }
77+static inline T* Unwrap(v8::Local<v8::Object> obj);
84788579// Subclasses are expected to invoke CppgcMixin::Trace() in their own Trace()
8680// methods.
8781void Trace(cppgc::Visitor* visitor) const override {
8882 visitor->Trace(traced_reference_);
8983 }
908485+// TODO(joyeecheung): use ObjectSizeTrait;
86+inline size_t SelfSize() const override { return sizeof(*this); }
87+inline bool IsCppgcWrapper() const override { return true; }
88+89+// This is run for all the remaining Cppgc wrappers tracked in the Realm
90+// during Realm shutdown. The destruction of the wrappers would happen later,
91+// when the final garbage collection is triggered when CppHeap is torn down as
92+// part of the Isolate teardown. If subclasses of CppgcMixin wish to perform
93+// cleanups that depend on the Realm during destruction, they should implment
94+// it in a Clean() override, and then call this->Finalize() from their
95+// destructor. Outside of Finalize(), subclasses should avoid calling
96+// into JavaScript or perform any operation that can trigger garbage
97+// collection during the destruction.
98+void Finalize() {
99+if (realm_ == nullptr) return;
100+this->Clean(realm_);
101+ realm_ = nullptr;
102+ }
103+104+// The default implementation of Clean() is a no-op. If subclasses wish
105+// to perform cleanup that require a living Realm, they should
106+// should put the cleanups in a Clean() override, and call this->Finalize()
107+// in the destructor, instead of doing those cleanups directly in the
108+// destructor.
109+virtual void Clean(Realm* realm) {}
110+111+inline ~CppgcMixin();
112+113+friend class CppgcWrapperListNode;
114+91115private:
92-Environment* env_;
116+Realm* realm_ = nullptr;
93117 v8::TracedReference<v8::Object> traced_reference_;
94118};
95119@@ -105,7 +129,8 @@ class CppgcMixin : public cppgc::GarbageCollectedMixin {
105129#define SET_CPPGC_NAME(Klass) \
106130inline const char* GetHumanReadableName() const final { \
107131return "Node / " #Klass; \
108- }
132+ } \
133+inline const char* MemoryInfoName() const override { return #Klass; }
109134110135/**
111136 * Similar to ASSIGN_OR_RETURN_UNWRAP() but works on cppgc-managed types