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

Release zlib callbacks and buffer after processing chunks #6955

Closed

Conversation

mdlavin
Copy link
Contributor

@mdlavin mdlavin commented May 24, 2016

Checklist
  • tests and code linting passes
  • a test and/or benchmark is included
  • the commit message follows commit guidelines
Affected core subsystem(s)

zlib

Description of change

When a long lived zlib compressor is created, using zlib.createDeflateRaw() for example, the compressor will hold onto some functions and buffers longer than it needs to. If there are lots of zlib compressors created, the total allocated space can add up to consume a non-trivial amount of memory.

One example of a real project that can experience this issue is the WS library with per-message deflating: https://github.com/websockets/ws/blob/master/lib/PerMessageDeflate.js . A common user of that is socket.io, which will create possibly create many web sockets. If a socket.io server has lots of web sockets connected with zlib compression, the leaked objects can take memory that could be freed. When New Relic is being used, the amount of memory is even more obvious, because the held closures also keep some New Relic transaction data alive as well.

I know that test cases are desired for changes, but I wasn’t sure about the best way to automate the test for this bug. There are some simple tests that could be added, like checking for certain attributes being null after certain code execution, but I wasn’t sure if that was useful enough. I’m open to suggestions. In place of a unit test, I created a standalone example that can demonstrate the problem here: https://gist.github.com/mdlavin/334338eb801bd3243679db89c219c7ac

Before the fix, the output looks like this:

Closure changes [ { what: 'Closure',
    size_bytes: 648,
    size: '648 bytes',
    '+': 11,
    '-': 2 } ]
Uint8Array changes [ { what: 'Uint8Array',
    size_bytes: 240,
    size: '240 bytes',
    '+': 3,
    '-': 0 } ]

After the fix, the number of Closures and Uint8Arrays are reduced by 1 each because the memory can be reclaimed by the GC.

Closure changes [ { what: 'Closure',
    size_bytes: 576,
    size: '576 bytes',
    '+': 10,
    '-': 2 } ]
Uint8Array changes [ { what: 'Uint8Array',
    size_bytes: 160,
    size: '160 bytes',
    '+': 2,
    '-': 0 } ]

@nodejs-github-bot nodejs-github-bot added the zlib Issues and PRs related to the zlib subsystem. label May 24, 2016
@mdlavin mdlavin force-pushed the release-zlib-callbacks-and-buffers branch from abca123 to fecc1ca Compare May 24, 2016 17:19
// Don't hold onto the buffer and the callback
// after they have been used.
delete this.buffer;
delete this.callback;
Copy link
Member

Choose a reason for hiding this comment

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

For efficiency reasons, it's better to set the values to null or undefined. The delete operator changes the shape (a.k.a. the hidden class) of the object.

@mdlavin
Copy link
Contributor Author

mdlavin commented May 24, 2016

Thanks for the feedback. I updated the PR with your suggestions. I was't sure if you preferred to commits to be squash or not, so I pushed a new commit. I'm happy to squash if that's desired.

// is === this._handle, and that's why it's important to null
// out the values after they are done being used. `this._handle`
// can stay in memory longer than the callback and buffer are needed.
if (this) {
Copy link
Contributor

@ronkorving ronkorving May 25, 2016

Choose a reason for hiding this comment

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

Will this ever be falsy or not req?

Copy link
Member

Choose a reason for hiding this comment

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

no and no, I think… I guess just asking to change the variable instead of asking for the above comment woud have been easier, sorry

Copy link
Contributor Author

@mdlavin mdlavin May 25, 2016

Choose a reason for hiding this comment

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

this will be falsy when callback is called from line 547. And, this will equal newReq (not req) when callback is called from the async write on line 623.

Well, I guess req and newReq are technically the same value with the current implementation. But this can definitely be falsy

Copy link
Member

Choose a reason for hiding this comment

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

@mdlavin newReq === req because _handle.write() just returns _handle (I know it’s confusing)… but yes you can leave it like this then.

@addaleax
Copy link
Member

@mdlavin This will be squashed anyway when it’s landed, until then you can do it in whatever way you think helps best with reviewing.

CI: https://ci.nodejs.org/job/node-test-commit/3494/

@mdlavin
Copy link
Contributor Author

mdlavin commented May 26, 2016

@addaleax it looks like that integration build failed for reasons other than my change. Does that match what you see?

@addaleax
Copy link
Member

Yes, the CI failures are unrelated, no need to worry about that.

LGTM

@jhamhader
Copy link
Contributor

LGTM

addaleax pushed a commit that referenced this pull request May 28, 2016
PR-URL: #6955
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: Yuval Brik <[email protected]>
@addaleax
Copy link
Member

Landed in 313ef54, thanks! I believe this is your first commit to core, if so, welcome on board and we hope you find other ways to contribute!

Fishrock123 pushed a commit to Fishrock123/node that referenced this pull request May 30, 2016
PR-URL: nodejs#6955
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: Yuval Brik <[email protected]>
@mdlavin
Copy link
Contributor Author

mdlavin commented May 31, 2016

@addaleax this is my first commit and I'm happy to finally contribute. I'd like to backport this fix to the 4.x series if you think it's a good candidate. The changes are very simple, and I think the change applies cleanly in the 4.x version of zlib too.

@addaleax
Copy link
Member

@mdarse I think you can do a backport PR, yes, and I wouldn’t expect much trouble doing that. Make sure to take the commit that landed in master and create a PR with it against v4.x-staging; and btw, changes need to live a while in a released version in order to be eligible for inclusion into LTS, so don’t expect it to get merged right away. :)

rvagg pushed a commit that referenced this pull request Jun 2, 2016
PR-URL: #6955
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: Yuval Brik <[email protected]>
@mdlavin
Copy link
Contributor Author

mdlavin commented Jun 9, 2016

Thanks for the pointers. I opened #7251 as the backport request

mdlavin added a commit to mdlavin/node that referenced this pull request Jun 30, 2016
PR-URL: nodejs#6955
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: Yuval Brik <[email protected]>
MylesBorins pushed a commit that referenced this pull request Jun 30, 2016
PR-URL: #6955
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: Yuval Brik <[email protected]>
MylesBorins pushed a commit that referenced this pull request Jul 12, 2016
PR-URL: #6955
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: Yuval Brik <[email protected]>
@MylesBorins MylesBorins mentioned this pull request Jul 12, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
zlib Issues and PRs related to the zlib subsystem.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants