Skip to content

Commit 6766d10

Browse files
committed
src: track cppgc wrappers with CppgcWrapperQueue
1 parent 7c3aa9f commit 6766d10

File tree

4 files changed

+88
-6
lines changed

4 files changed

+88
-6
lines changed

src/README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1041,6 +1041,17 @@ class MyWrap final : CPPGC_MIXIN(MyWrap) {
10411041
}
10421042
```
10431043
1044+
If the wrapper needs to perform cleanups when it's destroyed and that
1045+
cleanup relies on a living Node.js `Environment`, it should implement a
1046+
pattern like this:
1047+
1048+
```c++
1049+
~MyWrap() { this->Clean(); }
1050+
void CleanEnvResource(Environment* env) override {
1051+
// Do cleanup that relies on a living Environemnt.
1052+
}
1053+
```
1054+
10441055
`cppgc::GarbageCollected` types are expected to implement a
10451056
`void Trace(cppgc::Visitor* visitor) const` method. When they are the
10461057
final class in the hierarchy, this method must be marked `final`. For
@@ -1195,6 +1206,8 @@ referrer->Set(
11951206
).ToLocalChecked();
11961207
```
11971208

1209+
#### Lifetime and cleanups of cppgc-managed objects
1210+
11981211
Typically, a newly created cppgc-managed wrapper object should be held alive
11991212
by the JavaScript land (for example, by being returned by a method and
12001213
staying alive in a closure). Long-lived cppgc objects can also
@@ -1206,6 +1219,27 @@ it, this can happen at any time after the garbage collector notices that
12061219
it's no longer reachable and before the V8 isolate is torn down.
12071220
See the [Oilpan documentation in Chromium][] for more details.
12081221

1222+
If the cppgc-managed objects does not need to perform any special cleanup,
1223+
it's fine to use the default destructor. If it needs to perform only trivial
1224+
cleanup that only affects its own members without calling into JS, potentially
1225+
triggering garbage collection or relying on a living `Environment`, then it's
1226+
fine to just implement the trivial cleanup in the destructor directly. If it
1227+
needs to do any substantial cleanup that involves a living `Environment`, because
1228+
the destructor can be called at any time by the garbage collection, even after
1229+
the `Environment` is already gone, it must implement the cleanup with this pattern:
1230+
1231+
```c++
1232+
~MyWrap() { this->Clean(); }
1233+
void CleanEnvResource(Environment* env) override {
1234+
// Do cleanup that relies on a living Environemnt. This would be
1235+
// called by CppgcMixin::Clean() first during Environment shutdown,
1236+
// while the Environment is still alive. If the destructor calls
1237+
// Clean() again later during garbage collection that happens after
1238+
// Environment shutdown, CleanEnvResource() would be skipped, preventing
1239+
// invalid access to the Environment.
1240+
}
1241+
```
1242+
12091243
### Callback scopes
12101244
12111245
The public `CallbackScope` and the internally used `InternalCallbackScope`

src/cppgc_helpers.h

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,30 @@ namespace node {
2525
* with V8's GC scheduling.
2626
*
2727
* 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
28+
* that per cppgc rules, CPPGC_MIXIN(MyWrap) must be at the left-most
2929
* position in the hierarchy (which ensures cppgc::GarbageCollected
3030
* is at the left-most position).
3131
*
32-
* class Klass final : CPPGC_MIXIN(Klass) {
32+
* class MyWrap final : CPPGC_MIXIN(MyWrap) {
3333
* public:
34-
* SET_CPPGC_NAME(Klass) // Sets the heap snapshot name to "Node / Klass"
34+
* SET_CPPGC_NAME(MyWrap) // Sets the heap snapshot name to "Node / MyWrap"
3535
* void Trace(cppgc::Visitor* visitor) const final {
3636
* CppgcMixin::Trace(visitor);
3737
* visitor->Trace(...); // Trace any additional owned traceable data
3838
* }
3939
* }
40+
*
41+
* If the wrapper needs to perform cleanups when it's destroyed and that
42+
* cleanup relies on a living Node.js `Environment`, it should implement a
43+
* pattern like this:
44+
*
45+
* ~MyWrap() { this->Clean(); }
46+
* void CleanEnvResource(Environment* env) override {
47+
* // Do cleanup that relies on a living Environemnt.
48+
* }
4049
*/
41-
class CppgcMixin : public cppgc::GarbageCollectedMixin {
50+
class CppgcMixin : public cppgc::GarbageCollectedMixin,
51+
public CppgcWrapperListNode {
4252
public:
4353
// To help various callbacks access wrapper objects with different memory
4454
// management, cppgc-managed objects share the same layout as BaseObjects.
@@ -58,6 +68,7 @@ class CppgcMixin : public cppgc::GarbageCollectedMixin {
5868
obj->SetAlignedPointerInInternalField(
5969
kEmbedderType, env->isolate_data()->embedder_id_for_cppgc());
6070
obj->SetAlignedPointerInInternalField(kSlot, ptr);
71+
env->cppgc_wrapper_list()->PushFront(ptr);
6172
}
6273

6374
v8::Local<v8::Object> object() const {
@@ -88,8 +99,29 @@ class CppgcMixin : public cppgc::GarbageCollectedMixin {
8899
visitor->Trace(traced_reference_);
89100
}
90101

102+
// This implements CppgcWrapperListNode::Clean and is run for all the
103+
// remaining Cppgc wrappers tracked in the Environment during Environment
104+
// shutdown. The destruction of the wrappers would happen later, when the
105+
// final garbage collection is triggered when CppHeap is torn down as part of
106+
// the Isolate teardown. If subclasses of CppgcMixin wish to perform cleanups
107+
// that depend on the Environment during destruction, they should implment it
108+
// in a CleanEnvResource() override, and then call this->Clean() from their
109+
// destructor. Outside of CleanEnvResource(), subclasses should avoid calling
110+
// into JavaScript or perform any operation that can trigger garbage
111+
// collection during the destruction.
112+
void Clean() override {
113+
if (env_ == nullptr) return;
114+
this->CleanEnvResource(env_);
115+
env_ = nullptr;
116+
}
117+
118+
// The default implementation of CleanEnvResource() is a no-op. Subclasses
119+
// should override it to perform cleanup that require a living Environment,
120+
// instead of doing these cleanups directly in the destructor.
121+
virtual void CleanEnvResource(Environment* env) {}
122+
91123
private:
92-
Environment* env_;
124+
Environment* env_ = nullptr;
93125
v8::TracedReference<v8::Object> traced_reference_;
94126
};
95127

src/env.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1290,6 +1290,8 @@ void Environment::RunCleanup() {
12901290
CleanupHandles();
12911291
}
12921292

1293+
for (CppgcWrapperListNode* handle : cppgc_wrapper_list_) handle->Clean();
1294+
12931295
for (const int fd : unmanaged_fds_) {
12941296
uv_fs_t close_req;
12951297
uv_fs_close(nullptr, &close_req, fd, nullptr);

src/env.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -615,6 +615,12 @@ class Cleanable {
615615
friend class Environment;
616616
};
617617

618+
class CppgcWrapperListNode {
619+
public:
620+
virtual void Clean() = 0;
621+
ListNode<CppgcWrapperListNode> wrapper_list_node;
622+
};
623+
618624
/**
619625
* Environment is a per-isolate data structure that represents an execution
620626
* environment. Each environment has a principal realm. An environment can
@@ -703,6 +709,7 @@ class Environment final : public MemoryRetainer {
703709
Realm* realm,
704710
const ContextInfo& info);
705711
void UnassignFromContext(v8::Local<v8::Context> context);
712+
void UntrackContext(v8::Local<v8::Context> context);
706713
void TrackShadowRealm(shadow_realm::ShadowRealm* realm);
707714
void UntrackShadowRealm(shadow_realm::ShadowRealm* realm);
708715

@@ -910,12 +917,18 @@ class Environment final : public MemoryRetainer {
910917
typedef ListHead<HandleWrap, &HandleWrap::handle_wrap_queue_> HandleWrapQueue;
911918
typedef ListHead<ReqWrapBase, &ReqWrapBase::req_wrap_queue_> ReqWrapQueue;
912919
typedef ListHead<Cleanable, &Cleanable::cleanable_queue_> CleanableQueue;
920+
typedef ListHead<CppgcWrapperListNode,
921+
&CppgcWrapperListNode::wrapper_list_node>
922+
CppgcWrapperList;
913923

914924
inline HandleWrapQueue* handle_wrap_queue() { return &handle_wrap_queue_; }
915925
inline CleanableQueue* cleanable_queue() {
916926
return &cleanable_queue_;
917927
}
918928
inline ReqWrapQueue* req_wrap_queue() { return &req_wrap_queue_; }
929+
inline CppgcWrapperList* cppgc_wrapper_list() {
930+
return &cppgc_wrapper_list_;
931+
}
919932

920933
// https://w3c.github.io/hr-time/#dfn-time-origin
921934
inline uint64_t time_origin() {
@@ -1084,7 +1097,6 @@ class Environment final : public MemoryRetainer {
10841097
v8::Local<v8::Value>),
10851098
const char* errmsg);
10861099
void TrackContext(v8::Local<v8::Context> context);
1087-
void UntrackContext(v8::Local<v8::Context> context);
10881100

10891101
std::list<binding::DLib> loaded_addons_;
10901102
v8::Isolate* const isolate_;
@@ -1203,6 +1215,8 @@ class Environment final : public MemoryRetainer {
12031215
CleanableQueue cleanable_queue_;
12041216
HandleWrapQueue handle_wrap_queue_;
12051217
ReqWrapQueue req_wrap_queue_;
1218+
CppgcWrapperList cppgc_wrapper_list_;
1219+
12061220
int handle_cleanup_waiting_ = 0;
12071221
int request_waiting_ = 0;
12081222

0 commit comments

Comments
 (0)