Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

src: track cppgc wrappers with a list in Realm #56534

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

joyeecheung
Copy link
Member

@joyeecheung joyeecheung commented Jan 9, 2025

src: track cppgc wrappers with a list in Realm

This allows us to perform cleanups of cppgc wrappers that rely
on a living Realm during Realm shutdown. Otherwise
the cleanup may happen during object destruction, which can
be triggered by GC after Realm shutdown, leading to invalid
access to Realm.

The general pattern for this type of non-trivial destruction is
designed to be:

class MyWrap final : CPPGC_MIXIN(MyWrap) {
 public:
  ~MyWrap() { this->Finalize(); }
  void Clean(Realm* realm) override {
     // Do cleanup that relies on a living Realm. This would be
     // called by CppgcMixin::Finalize() first during Realm shutdown,
     // while the Realm is still alive. If the destructor calls
     // Finalize() again later during garbage collection that happens after
     // Realm shutdown, Clean() would be skipped, preventing
     // invalid access to the Realm.
  }
}

In addition, this allows us to trace external memory held by the wrappers
in the heap snapshots if we add synthethic edges between the wrappers
and other nodes in the embdder graph callback, or to perform snapshot
serialization for them.

@nodejs-github-bot nodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run. labels Jan 9, 2025
@joyeecheung joyeecheung added the request-ci Add this label to start a Jenkins CI on a PR. label Jan 9, 2025
@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Jan 9, 2025
@nodejs-github-bot
Copy link
Collaborator

Copy link

codecov bot commented Jan 9, 2025

Codecov Report

Attention: Patch coverage is 49.10714% with 57 lines in your changes missing coverage. Please review.

Project coverage is 90.19%. Comparing base (4cc69f9) to head (4bb01f9).
Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
src/memory_tracker-inl.h 22.41% 42 Missing and 3 partials ⚠️
src/cppgc_helpers-inl.h 83.33% 1 Missing and 3 partials ⚠️
src/cppgc_helpers.h 66.66% 4 Missing ⚠️
src/cppgc_helpers.cc 75.00% 1 Missing and 1 partial ⚠️
src/node_contextify.h 0.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #56534      +/-   ##
==========================================
- Coverage   90.20%   90.19%   -0.02%     
==========================================
  Files         630      632       +2     
  Lines      185307   185394      +87     
  Branches    36271    36288      +17     
==========================================
+ Hits       167160   167208      +48     
- Misses      11085    11140      +55     
+ Partials     7062     7046      -16     
Files with missing lines Coverage Δ
src/memory_tracker.h 100.00% <100.00%> (ø)
src/node_realm-inl.h 89.65% <100.00%> (+0.56%) ⬆️
src/node_realm.cc 73.75% <100.00%> (+0.33%) ⬆️
src/node_realm.h 100.00% <100.00%> (ø)
src/cppgc_helpers.cc 75.00% <75.00%> (ø)
src/node_contextify.h 70.58% <0.00%> (-9.42%) ⬇️
src/cppgc_helpers-inl.h 83.33% <83.33%> (ø)
src/cppgc_helpers.h 73.33% <66.66%> (-13.04%) ⬇️
src/memory_tracker-inl.h 57.43% <22.41%> (-11.51%) ⬇️

... and 24 files with indirect coverage changes

🚀 New features to boost your workflow:
  • Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@legendecas legendecas added the request-ci Add this label to start a Jenkins CI on a PR. label Jan 23, 2025
@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Jan 23, 2025
@nodejs-github-bot
Copy link
Collaborator

@joyeecheung
Copy link
Member Author

joyeecheung commented Feb 14, 2025

Trying to rebase #51017 on top of it to verify the "use list for heap snapshot integration" idea. It seems we'll need to update the MemoryTracker code to stop creating duplicate nodes for cppgc wrappers (either that, or use something different for the cppgc wrapper list). Still looking into it..

@joyeecheung joyeecheung force-pushed the cppgc-list branch 3 times, most recently from ed3648a to 036116a Compare March 7, 2025 15:54
```cpp
private:
CPPGC_USING_PRE_FINALIZER(MyWrap, Clean);
```
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of these rules may be non-obvious to many developers. For example, let's say someone has a wrapped object that is holding a BaseObjectPtr<Foo> as a member... Just based on reading this documentation, as a newcomer I would not know which of these conditions may apply. Do I need to clear the BaseObjectPtr in the CleanEnvResource callback? What about if it has a v8::Global<...> member field? Is it ok to just let that be cleared by the default destructor? etc. I think calling out a few more detailed examples (and not partial examples like you have currently) of each case would be most helpful.

Copy link
Member Author

@joyeecheung joyeecheung Mar 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's say someone has a wrapped object that is holding a BaseObjectPtr<Foo> as a member

I don't think this would be supported in the nearest future, or will ever be supported - a cppgc-object should not hold on to a BaseObjectPtr<Foo> without converting that BaseObjectPtr<Foo> to cppgc-managed first - and then in that case they should use cppgc::Member<Foo> to hold on to that instead. The migration needs to be done bottom-to-top. In the case of using cppgc::Member<Foo>, nothing special needs to be done in the destructor, V8 will handle that properly.

What about if it has a v8::Global<...> member field?

I think in most cases, this should be v8::TracedReference instead. In that case nothing special needs to be done in the destructor. This has been documented in the Creating C++ to JavaScript references in cppgc-managed objects section.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a note about not supporting cross BaseObject and cppgc-managed wrapeprs. I don't think it's impossible but someone needs to implement some helpers for that first (I am not 100% sure what needs to be done, but using cppgc::Persistent would be a start).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we can somehow have c++ linting detect it for us if someone includes a BaseObjectPtr member in one of these? .... in any case, this all sounds fine, I'd just like to make sure this gets included somehow in the documentation.

@joyeecheung joyeecheung changed the title src: track cppgc wrappers with CppgcWrapperList in Environment src: track cppgc wrappers with a list in Realm Mar 7, 2025
@joyeecheung joyeecheung force-pushed the cppgc-list branch 2 times, most recently from 909a80a to 6fd34b0 Compare March 7, 2025 16:44
@joyeecheung
Copy link
Member Author

joyeecheung commented Mar 7, 2025

I did a few updates to the design:

  • The cleanup pattern is now Finalize() calling Clean() which takes a Realm. I think Finalize() sounds better and is less confusing.
  • The wrapper list is now per-Realm instead of per-Environment, similar to how BaseObjects are managed.
  • This makes the CppgcMixin a MemoryRetainer and includes de-duplication code of the MemoryRetainerNode, so that subclasses can implement MemoryInfo to track additional data just like before. For an example of what it looks like, see wip: crypto: use cppgc to manage Hash #51017 (comment)

This allows us to perform cleanups of cppgc wrappers that rely on a
living Realm during Realm shutdown. Otherwise the cleanup may happen
during object destruction, which can be triggered by GC after Realm
shutdown, leading to invalid access to Realm.

The general pattern for this type of non-trivial destruction is
designed to be:

```
class MyWrap final : CPPGC_MIXIN(MyWrap) {
    public:
    ~MyWrap() { this->Finalize(); }
    void Clean(Realm* realm) override {
        // Do cleanup that relies on a living Realm. This would be
        // called by CppgcMixin::Finalize() first during Realm
        // shutdown, while the Realm is still alive. If the destructor
        // calls Finalize() again later during garbage collection that
        // happens after Realm shutdown, Clean() would be skipped,
        // preventing invalid access to the Realm.
    }
}
```

In addition, this allows us to trace external memory held by the
wrappers in the heap snapshots if we add synthethic edges between the
wrappers and other nodes in the embdder graph callback, or to perform
snapshot serialization for them.
@joyeecheung joyeecheung added the request-ci Add this label to start a Jenkins CI on a PR. label Mar 7, 2025
@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Mar 7, 2025
@nodejs-github-bot
Copy link
Collaborator

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants