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: fix memory leak for v8.serialize #42695

Merged
merged 3 commits into from
May 2, 2022

Conversation

liuxingbaoyu
Copy link
Contributor

Fixes: #40828
Refs: #38300

@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 Apr 11, 2022
When Buffer::New passes in existing data,
it cannot be garbage collected in js synchronous execution.

Fixes: nodejs#40828
Refs: nodejs#38300
Copy link
Member

@addaleax addaleax left a comment

Choose a reason for hiding this comment

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

#40828 (comment) is the right comment here here: This is currently not working because the garbage collector does not get a chance to run, including in the test case here.

This change here is not a good solution. It adds an unnecessary buffer copy without real benefit.

If you do want to fix this in a better way, you’ll need to modify Buffer::New(Environment*,char*,size_t) to not call into Buffer::New(Environment*,char*,size_t,FreeCallback,void); instead.

The best way to fix this is to keep reminding users that Buffer GC may need to perform some cleanup asynchronously, and Buffer instances may be kept alive until synchronous code stops running.

@liuxingbaoyu
Copy link
Contributor Author

liuxingbaoyu commented Apr 13, 2022

@addaleax

Thanks for your review!

This change here is not a good solution. It adds an unnecessary buffer copy without real benefit.

Although an extra memory copy is added, the test becomes faster, perhaps due to the reduced use of CleanupHook.

And it takes less memory and is more stable.

In some cases, such as babel, it can reduce memory by 300~500mb.

If you do want to fix this in a better way, you’ll need to modify Buffer::New(Environment*,char*,size_t) to not call into Buffer::New(Environment*,char*,size_t,FreeCallback,void); instead.

Can you give me more help? I'd love to use this better method!

I have tried modifying the CallbackInfo to release the buffer synchronously, but that requires a lock on the CleanupHook.

And using CleanupHook also seems to have an impact on performance.

The best way to fix this is to keep reminding users that Buffer GC may need to perform some cleanup asynchronously, and Buffer instances may be kept alive until synchronous code stops running.

But this problem leads to bad performance and even crashes, and there is no good way to avoid it other than changing everything to async.

@addaleax
Copy link
Member

Can you give me more help? I'd love to use this better method!

The goal here would be to avoid CallbackInfo/CleanupHook entirely, and instead let V8 handle the allocation release. Passing a free callback to V8 is more efficient than passing one to the 5-argument Buffer::New() version, because the V8 callback makes fewer API guarantees (for example, it can run on any thread and can be called as part of GC; on the other hand, the Buffer::New() callback guarantees that JS can be executed from inside the callback, for legacy compatibility reasons).

So – the idea here is, instead of using Buffer::New(Environment*, char*, size_t, FreeCallback, void*) inside of Buffer::New(Environment*, char*, size_t):

  • Creating a v8::BackingStore with a release callback using v8::ArrayBuffer::NewBackingStore(char*, size_t, DeleterCallback, void*)
  • Creating an v8::ArrayBuffer from it using v8::ArrayBuffer::New(Isolate*, std::shared_ptr<BackingStore>)
  • Create a Node.js buffer from that using Buffer::New(Environment*, Local<ArrayBuffer>, size_t, size_t)

The really nice thing about this approach would be that it takes care of this problem in all situations in which Buffer::New(Environment*, char*, size_t) is used, including addons which use it.

Although an extra memory copy is added, the test becomes faster, perhaps due to the reduced use of CleanupHook.

I assume that this would depend a bit on how much memory is allocated – longer buffers take longer to copy, but (typically) a constant amount of time to release.

In some cases, such as babel, it can reduce memory by 300~500mb.

Nice! To be clear, I would absolutely love to see this :)

@liuxingbaoyu
Copy link
Contributor Author

Thank you very much for your detailed help, I will try it!

Copy link
Member

@addaleax addaleax left a comment

Choose a reason for hiding this comment

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

Awesome, thank you!

@addaleax addaleax added request-ci Add this label to start a Jenkins CI on a PR. and removed needs-ci PRs that need a full CI run. labels Apr 13, 2022
@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Apr 13, 2022
@nodejs-github-bot
Copy link
Collaborator

@liuxingbaoyu
Copy link
Contributor Author

It seems that the test is so strict that it fails on jenkins, I changed it from less than 10mb increments to less than double.

@addaleax addaleax added author ready PRs that have at least one approval, no pending requests for changes, and a CI started. request-ci Add this label to start a Jenkins CI on a PR. labels Apr 15, 2022
@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Apr 15, 2022
@nodejs-github-bot
Copy link
Collaborator

@aduh95 aduh95 added commit-queue Add this label to land a pull request using GitHub Actions. commit-queue-squash Add this label to instruct the Commit Queue to squash all the PR commits into the first one. and removed commit-queue-failed An error occurred while landing this pull request using GitHub Actions. labels May 2, 2022
@nodejs-github-bot nodejs-github-bot removed the commit-queue Add this label to land a pull request using GitHub Actions. label May 2, 2022
@nodejs-github-bot nodejs-github-bot merged commit ce29d28 into nodejs:master May 2, 2022
@nodejs-github-bot
Copy link
Collaborator

Landed in ce29d28

RafaelGSS pushed a commit that referenced this pull request May 10, 2022
When Buffer::New passes in existing data,
it cannot be garbage collected in js synchronous execution.

Fixes: #40828
Refs: #38300

PR-URL: #42695
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Minwoo Jung <[email protected]>
@RafaelGSS RafaelGSS mentioned this pull request May 10, 2022
juanarbol pushed a commit that referenced this pull request May 31, 2022
When Buffer::New passes in existing data,
it cannot be garbage collected in js synchronous execution.

Fixes: #40828
Refs: #38300

PR-URL: #42695
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Minwoo Jung <[email protected]>
danielleadams pushed a commit that referenced this pull request Jun 27, 2022
When Buffer::New passes in existing data,
it cannot be garbage collected in js synchronous execution.

Fixes: #40828
Refs: #38300

PR-URL: #42695
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Minwoo Jung <[email protected]>
targos pushed a commit that referenced this pull request Jul 12, 2022
When Buffer::New passes in existing data,
it cannot be garbage collected in js synchronous execution.

Fixes: #40828
Refs: #38300

PR-URL: #42695
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Minwoo Jung <[email protected]>
targos pushed a commit that referenced this pull request Jul 31, 2022
When Buffer::New passes in existing data,
it cannot be garbage collected in js synchronous execution.

Fixes: #40828
Refs: #38300

PR-URL: #42695
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Minwoo Jung <[email protected]>
codebytere added a commit to electron/electron that referenced this pull request Aug 24, 2022
nodejs/node#42695

This test does not work for Electron as they do not use V8's
ArrayBufferAllocator.
codebytere added a commit to electron/electron that referenced this pull request Aug 24, 2022
nodejs/node#42695

This test does not work for Electron as they do not use V8's
ArrayBufferAllocator.
codebytere added a commit to electron/electron that referenced this pull request Aug 25, 2022
nodejs/node#42695

This test does not work for Electron as they do not use V8's
ArrayBufferAllocator.
jkleinsc pushed a commit to electron/electron that referenced this pull request Aug 29, 2022
* chore: bump node in DEPS to v16.17.0

* chore: fixup asar patch

* lib: use null-prototype objects for property descriptors

nodejs/node#43270

* src: make SecureContext fields private

nodejs/node#43173

* crypto: remove Node.js-specific webcrypto extensions

nodejs/node#43310

* test: refactor to top-level await

nodejs/node#43500

* deps: cherry-pick two libuv fixes

nodejs/node#43950

* src: slim down env-inl.h

nodejs/node#43745

* util: add AggregateError.prototype.errors to inspect output

nodejs/node#43646

* esm: improve performance & tidy tests

nodejs/node#43784

* src: NodeArrayBufferAllocator delegates to v8's allocator

nodejs/node#43594

* chore: update patch indices

* chore: update filenames

* src: refactor IsSupportedAuthenticatedMode

nodejs/node#42368

* src: add --openssl-legacy-provider option

nodejs/node#40478

* lib,src: add source map support for global eval

nodejs/node#43428

* trace_events: trace net connect event

nodejs/node#43903

* deps: update ICU to 71.1

nodejs/node#42655

This fails the test because it's missing https://chromium-review.googlesource.com/c/chromium/deps/icu/+/3841093

* lib: give names to promisified exists() and question()

nodejs/node#43218

* crypto: add CFRG curves to Web Crypto API

nodejs/node#42507

* src: fix memory leak for v8.serialize

nodejs/node#42695

This test does not work for Electron as they do not use V8's
ArrayBufferAllocator.

* buffer: fix atob input validation

nodejs/node#42539

* src: fix ssize_t error from nghttp2.h

nodejs/node#44393

Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <[email protected]>
guangwong pushed a commit to noslate-project/node that referenced this pull request Oct 10, 2022
When Buffer::New passes in existing data,
it cannot be garbage collected in js synchronous execution.

Fixes: nodejs/node#40828
Refs: nodejs/node#38300

PR-URL: nodejs/node#42695
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Minwoo Jung <[email protected]>
khalwa pushed a commit to solarwindscloud/electron that referenced this pull request Feb 22, 2023
* chore: bump node in DEPS to v16.17.0

* chore: fixup asar patch

* lib: use null-prototype objects for property descriptors

nodejs/node#43270

* src: make SecureContext fields private

nodejs/node#43173

* crypto: remove Node.js-specific webcrypto extensions

nodejs/node#43310

* test: refactor to top-level await

nodejs/node#43500

* deps: cherry-pick two libuv fixes

nodejs/node#43950

* src: slim down env-inl.h

nodejs/node#43745

* util: add AggregateError.prototype.errors to inspect output

nodejs/node#43646

* esm: improve performance & tidy tests

nodejs/node#43784

* src: NodeArrayBufferAllocator delegates to v8's allocator

nodejs/node#43594

* chore: update patch indices

* chore: update filenames

* src: refactor IsSupportedAuthenticatedMode

nodejs/node#42368

* src: add --openssl-legacy-provider option

nodejs/node#40478

* lib,src: add source map support for global eval

nodejs/node#43428

* trace_events: trace net connect event

nodejs/node#43903

* deps: update ICU to 71.1

nodejs/node#42655

This fails the test because it's missing https://chromium-review.googlesource.com/c/chromium/deps/icu/+/3841093

* lib: give names to promisified exists() and question()

nodejs/node#43218

* crypto: add CFRG curves to Web Crypto API

nodejs/node#42507

* src: fix memory leak for v8.serialize

nodejs/node#42695

This test does not work for Electron as they do not use V8's
ArrayBufferAllocator.

* buffer: fix atob input validation

nodejs/node#42539

* src: fix ssize_t error from nghttp2.h

nodejs/node#44393

Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
author ready PRs that have at least one approval, no pending requests for changes, and a CI started. c++ Issues and PRs that require attention from people who are familiar with C++. commit-queue-squash Add this label to instruct the Commit Queue to squash all the PR commits into the first one.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

v8.serialize leaks memory
6 participants