Skip to content
This repository was archived by the owner on Mar 23, 2023. It is now read-only.

fix: leveldb iterator memory leak - #26

Merged
achingbrain merged 2 commits into
masterfrom
fix/iterator-memory-leak
Jan 14, 2020
Merged

fix: leveldb iterator memory leak#26
achingbrain merged 2 commits into
masterfrom
fix/iterator-memory-leak

Conversation

@alanshaw

@alanshaw alanshaw commented Jan 3, 2020

Copy link
Copy Markdown
Member

The .end() method MUST be called on LevelDB iterators or they remain open, leaking memory.

This PR calls .end() on the leveldb iterator when it is done.

The added test exposes the problem by causing an error to be thrown on process exit when an iterator is open AND leveldb is not closed.

Normally when leveldb is closed it'll automatically clean up open iterators but if you don't close the store this error will occur:

Assertion failed: (ended_), function ~Iterator, file ../binding.cc, line 546.

This is thrown by a destructor function for iterator objects that asserts the iterator has ended before cleanup.

https://github.com/Level/leveldown/blob/d3453fbde4d2a8aa04d9091101c25c999649069b/binding.cc#L545

FYI:

Destructors are usually used to deallocate memory and do other cleanup for a class object and its class members when the object is destroyed. A destructor is called for a class object when that object passes out of scope or is explicitly deleted.
https://www.ibm.com/support/knowledgecenter/en/ssw_ibm_i_74/rzarg/cplr380.htm

The `.end()` method MUST be called on LevelDB iterators or they remain open,
leaking memory.

This PR calls `.end()` on the leveldb iterator when it is done.

The added test exposes the problem by causing an error to be thrown on process
exit when an iterator is open AND leveldb is not closed.

Normally when leveldb is closed it'll automatically clean up open iterators but
if you don't close the store this error will occur:

> Assertion failed: (ended_), function ~Iterator, file ../binding.cc, line 546.

This is thrown by a destructor function for iterator objects that asserts the
iterator has ended before cleanup.

https://github.com/Level/leveldown/blob/d3453fbde4d2a8aa04d9091101c25c999649069b/binding.cc#L545

FYI:

> Destructors are usually used to deallocate memory and do other cleanup for a class object and its class members when the object is destroyed. A destructor is called for a class object when that object passes out of scope or is explicitly deleted.
> https://www.ibm.com/support/knowledgecenter/en/ssw_ibm_i_74/rzarg/cplr380.htm
@alanshaw
alanshaw requested a review from achingbrain January 3, 2020 22:27
@achingbrain

Copy link
Copy Markdown
Member

This looks good but I can't get the test to fail against master - see the build here - it has the new test added but no change made to the src folder.

@alanshaw

alanshaw commented Jan 4, 2020

Copy link
Copy Markdown
Member Author

hmm, maybe the error is only printed in nodejs 12 (this is what I was using locally). I’ll take a look.

Comment thread src/index.js
li.next((err, key, value) => {
if (err) return reject(err)
if (key == null) return resolve({ done: true })
if (key == null) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

For completeness can you also implement the Generator.prototype.throw() method with similar logic?

Comment thread test/node.js

cp.on('exit', code => {
expect(code).to.equal(0)
expect(out).to.not.include('Assertion failed: (ended_)')

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is there a way to assert on the presence of something rather than the absence of something? Even something we add to the fixture to demonstrate that the iterator has been torn down correctly?

Otherwise this test is coupled to the internal implementation of leveldown which may change without us knowing and this test may not expose the problem it is designed to.

@alanshaw alanshaw Jan 4, 2020

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Not really, the leveldb iterator is not exposed an any way and the js iterator always ends fine. Given that this doesn’t even work on Node.js 10 I think this is not a good test.

That said I’m all out of ideas for how to test it right now!

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

(Still internal but at least it'd assert presence of something) you could check it._ended.

@vweevers

vweevers commented Jan 4, 2020

Copy link
Copy Markdown

hmm, maybe the error is only printed in nodejs 12 (this is what I was using locally). I’ll take a look.

Yes, calling finalizers on exit is a recent node 12 behavior. For background, see Level/leveldown#667 and nodejs/node#28428

@achingbrain
achingbrain merged commit e503c1a into master Jan 14, 2020
@achingbrain
achingbrain deleted the fix/iterator-memory-leak branch January 14, 2020 15:23
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants