Bug 79835 - IndexedDB: Resource leak with IDBObjectStore.openCursor
Summary: IndexedDB: Resource leak with IDBObjectStore.openCursor
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: WebKit Misc. (show other bugs)
Version: 528+ (Nightly build)
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Joshua Bell
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2012-02-28 14:35 PST by Joshua Bell
Modified: 2012-02-29 15:03 PST (History)
3 users (show)

See Also:


Attachments
Repro case (971 bytes, text/html)
2012-02-28 14:35 PST, Joshua Bell
no flags Details
Patch (8.76 KB, patch)
2012-02-28 15:58 PST, Joshua Bell
no flags Details | Formatted Diff | Diff
Patch (12.13 KB, patch)
2012-02-28 17:18 PST, Joshua Bell
no flags Details | Formatted Diff | Diff
Patch (13.47 KB, patch)
2012-02-29 11:15 PST, Joshua Bell
no flags Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Joshua Bell 2012-02-28 14:35:35 PST
Created attachment 129333 [details]
Repro case

Run attached page. It will "leak" 123 of each of:

IDBRequest: 123
IDBTransaction: 123
IDBCursorWithValue: 123
IDBTransactionBackendImpl: 123
IDBCursor: 123
IDBKey: 123
IDBObjectStore: 123
IDBCursorBackendImpl: 123

(The backend objects are reclaimed on app shutdown, so it's not a true memory leak.)

After setting up a database with a single store, and (necessarily) a single record, the script runs:

function runtest(db) {
  for (var i = 0; i < 123; ++i) {
    db.transaction('store', webkitIDBTransaction.READ_ONLY).objectStore('store').openCursor();
  }
  done();
}

done() just runs an explicit GC call to clean up temporary objects that aren't being leaked.

The leak goes away if either (1) there are no records in the store, or (2) openCursor() is not called.
Comment 1 Joshua Bell 2012-02-28 15:58:37 PST
Created attachment 129346 [details]
Patch
Comment 2 David Grogan 2012-02-28 16:18:35 PST
Comment on attachment 129346 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=129346&action=review

LGTM

Hopefully this ref-counting spaghetti gets a bit untangled in idb.next.

> Source/WebCore/storage/IDBCursor.cpp:117
> +    if (m_request && m_request->resetReadyState(m_transaction.get())) {

Similar question to below; m_request would only eval to false if continue() is called after close(), which I wouldn't have thought possible.  Are there situations where that happens?

> Source/WebCore/storage/IDBCursor.cpp:143
> +    if (m_request) {

This check seems to indicate that close can be called twice.  When does that happen?
Comment 3 Joshua Bell 2012-02-28 16:27:04 PST
(In reply to comment #2)
> (From update of attachment 129346 [details])
> View in context: https://bugs.webkit.org/attachment.cgi?id=129346&action=review
> 
> LGTM
> 
> Hopefully this ref-counting spaghetti gets a bit untangled in idb.next.
> 
> > Source/WebCore/storage/IDBCursor.cpp:117
> > +    if (m_request && m_request->resetReadyState(m_transaction.get())) {
> 
> Similar question to below; m_request would only eval to false if continue() is called after close(), which I wouldn't have thought possible.  Are there situations where that happens?

This can happen if the user retains a reference to the IDBCursor object but the transaction has finished. Once the transaction finishes, m_request will be cleared out (the new behavior). Previously, it would have called through to IDBCursorBackendImpl::continueFunction which would try and schedule a task against the transaction, which would fail with IDBDatabaseException::TRANSACTION_INACTIVE_ERR

So... I should update this to fail early with that error instead if m_request is null. I'll add a test to verify the error.

> > Source/WebCore/storage/IDBCursor.cpp:143
> > +    if (m_request) {
> 
> This check seems to indicate that close can be called twice.  When does that happen?

Ah, you're right, I can take that out.
Comment 4 Joshua Bell 2012-02-28 17:18:35 PST
Created attachment 129359 [details]
Patch
Comment 5 Tony Chang 2012-02-28 17:30:46 PST
Comment on attachment 129359 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=129359&action=review

> Source/WebCore/storage/IDBCursor.cpp:58
> +    m_transaction->registerOpenCursor(this);

Is it possible to use a C++ class with scoping for this so you don't have to remember to call unregisterOpenCursor manually?

> Source/WebCore/storage/IDBTransaction.cpp:144
> +    for (HashSet<IDBCursor*>::iterator i = cursors.begin(); i != cursors.end(); ++i)
> +        (*i)->close();

Does calling closeOpenCursors cause unregisterOpenCursor to be called?  I guess at that point m_openCursors is an empty HashSet and remove just ignores the call?
Comment 6 Joshua Bell 2012-02-28 17:45:27 PST
(In reply to comment #5)
> (From update of attachment 129359 [details])
> View in context: https://bugs.webkit.org/attachment.cgi?id=129359&action=review
> 
> > Source/WebCore/storage/IDBCursor.cpp:58
> > +    m_transaction->registerOpenCursor(this);
> 
> Is it possible to use a C++ class with scoping for this so you don't have to remember to call unregisterOpenCursor manually?
> 

Yep. I was blindly following the pattern used in IDBTransactionBackendImpl but I agree RAII-style is cleaner. I'll update the patch. 


> > Source/WebCore/storage/IDBTransaction.cpp:144
> > +    for (HashSet<IDBCursor*>::iterator i = cursors.begin(); i != cursors.end(); ++i)
> > +        (*i)->close();
> 
> Does calling closeOpenCursors cause unregisterOpenCursor to be called?  I guess at that point m_openCursors is an empty HashSet and remove just ignores the call?

It depends on whether there are requests in flight or if script holds any references. I used the swap pattern so such calls are safe.
Comment 7 Joshua Bell 2012-02-29 11:15:58 PST
Created attachment 129480 [details]
Patch
Comment 8 WebKit Review Bot 2012-02-29 15:03:27 PST
Comment on attachment 129480 [details]
Patch

Clearing flags on attachment: 129480

Committed r109271: <http://trac.webkit.org/changeset/109271>
Comment 9 WebKit Review Bot 2012-02-29 15:03:31 PST
All reviewed patches have been landed.  Closing bug.