Closed
Bug 721319
Opened 13 years ago
Closed 13 years ago
PlacesUtils.removeLazyBookmarkObserver() doesn't always remove observers, causes browser window to leak
Categories
(Toolkit :: Places, defect)
Tracking
()
VERIFIED
FIXED
mozilla13
People
(Reporter: ecfbugzilla, Assigned: mak)
References
Details
(Keywords: memory-leak, Whiteboard: [MemShrink:P1])
Attachments
(4 files, 3 obsolete files)
I noticed that my restartless extensions leak memory under certain conditions. After reducing the problem it seems to be a platform issue. Steps to reproduce:
1. Install the attached test extension. The relevant code is in main.js, its bootstrap.js file is only boilerplate.
2. Open a new browser window. The extension will attach a dummy event handler to the document of this window.
3. Close the new window again.
4. Disable or remove the test extension.
5. Go to about:memory?verbose and click "Minimize memory usage".
Expected results:
A search for testtest on about:memory doesn't turn up anything - the event handler registered in the browser window was released when this window was closed. Consequently, the extension's compartment could be released properly.
Actual results:
A compartment for bootstrap.js of the test extension is still listed. Something is still holding its event handler even though the browser window where that event handler was registered is already closed.
For some reason, the test extension's compartment gets released after opening and closing another browser window - this doesn't happen with the real-world extensions however. Also, attaching an event listener on the window doesn't cause the issue - it has to be on the document or one of the XUL nodes. Obvious work-around: remove event listeners when the window unloads (ugly).
Reporter | ||
Comment 1•13 years ago
|
||
Forgot to mention: tested in Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0a1) Gecko/20120125 Firefox/12.0a1
Comment 2•13 years ago
|
||
Is this a regression?
Reporter | ||
Comment 3•13 years ago
|
||
At least not a recent one - I tested in Firefox 9.0.1 and it is affected in exactly the same way.
Comment 4•13 years ago
|
||
So, a reason why adding listeners to the window object doesn't leak is that we clear
the event listener manager of window explicitly when the window is closed.
But I don't still understand why something would leak when adding listener to DOM nodes.
Are we missing some cycle collection edge?
Though, I'm not at all familiar with restartless addons. In which context is the main.js
executed.
I'm curious, could it be the root reason for Bug 713216 and Bug 710170?
Reporter | ||
Comment 6•13 years ago
|
||
(In reply to Olli Pettay [:smaug] from comment #4)
> Though, I'm not at all familiar with restartless addons. In which context is
> the main.js executed.
It is loaded into the context of bootstrap.js via subscript loader. And bootstrap.js is running inside a sandbox created by the add-on manager (which is why it is being displayed as a separate compartment). From what I recall the add-on manager creates these sandboxes when the extension is initialized and releases them when it is shut down (disabled or uninstalled). So normally this compartment can be garbage collected once the extension is disabled - unless some external references to it remain.
Comment 7•13 years ago
|
||
Btw, I assume similar problem could happen with userData/userDataHandlers
Comment 8•13 years ago
|
||
Actually, I can't reproduce this. After open/close and disabling the addon
few calls to minimize mem usage and/or cc/gc removes the compartment.
Reporter | ||
Comment 9•13 years ago
|
||
I just tried again - strangely, the issue is indeed not reproducible on the first run with a new profile. However, when I started the browser again with the same profile (extension present but disabled) I got the same zombie compartment again and it doesn't get removed no matter how often I click the buttons.
Comment 10•13 years ago
|
||
What? You get a zombie compartment just when starting with a disabled addon?
That sounds like a bug somewhere in addon handling.
Comment 11•13 years ago
|
||
And can't reproduce zombie compartment after restart, even if I enable the addon and
open/close a window and disable addon.
Reporter | ||
Comment 12•13 years ago
|
||
(In reply to Olli Pettay [:smaug] from comment #10)
> What? You get a zombie compartment just when starting with a disabled addon?
Sorry, not what I meant. I meant that the steps to reproduce work after a restart. There seems to be some strange dependency here: if I first go to the "get add-ons" tab of the add-ons manager (something that always happens on a new profile) then enabling the test extension, opening/closing a new window and disabling it again doesn't produce a zombie compartment. If I get to the "extensions" tab directly then the same steps produce a zombie compartment that won't go away. Trying to figure out what's so special about the "get add-ons" tab (opening it after the first zombie compartment is there won't do anything - repeating the steps still produces new zombie compartments).
Reporter | ||
Comment 13•13 years ago
|
||
(In reply to Wladimir Palant from comment #12)
> Trying to figure out what's so special about
> the "get add-ons" tab (opening it after the first zombie compartment is
> there won't do anything - repeating the steps still produces new zombie
> compartments).
There was apparently some interference with the proxy script I was using - the new profile took it over from the system configuration. After configuring a direct connection the compartment is now always being released the first time I go through the steps, however if I repeat them without restarting the browser I get a zombie compartment.
Comment 14•13 years ago
|
||
This sounds vaguely similar to bug 691537, but the steps to reproduce don't sound like they are the same.
Reporter | ||
Comment 15•13 years ago
|
||
(In reply to Andrew McCreight [:mccr8] from comment #14)
> This sounds vaguely similar to bug 691537, but the steps to reproduce don't
> sound like they are the same.
I don't think so - there is no indication that the add-on manager is doing something wrong here. It is quite definitely tied to event listeners.
Comment 16•13 years ago
|
||
event listeners don't really have anything special. Other code uses the same mechanism
for cycle collection.
I wish I could reproduce this. So far no luck.
Updated•13 years ago
|
Whiteboard: [MemShrink]
Comment 17•13 years ago
|
||
Could you give us a cycle collector log when the zombie compartment is present? Maybe that will give us some insight into what is happening. The directions are here: https://wiki.mozilla.org/Performance:Leak_Tools#Cycle_collector_heap_dump
Reporter | ||
Comment 18•13 years ago
|
||
I tried to make sure that this log is as small as possible, it was captured only with the browser window open and about:blank as the only tab. If I read it correctly, it still lists four ChromeWindow objects which should be two too many. After starting the browser I went through the enable extension / open browser window / close browser window / disable extension cycle twice, then went to about:memory and opened the Scratchpad window to enter the script that would create the log on a timer.
Reporter | ||
Comment 19•13 years ago
|
||
Comment 20•13 years ago
|
||
Kyle, can you triage this and put the right MemShrink priority on it? We recall you saying something about it at last week's meeting.
(In reply to Justin Lebar [:jlebar] from comment #20)
> Kyle, can you triage this and put the right MemShrink priority on it? We
> recall you saying something about it at last week's meeting.
I can look into it, but it'll probably be at least a week.
Assignee: nobody → khuey
Updated•13 years ago
|
Whiteboard: [MemShrink] → [MemShrink:P1]
Reporter | ||
Comment 22•13 years ago
|
||
Attached better test extension, all the functions have names now so that they are easier to find in the CC log.
Attachment #591724 -
Attachment is obsolete: true
Reporter | ||
Comment 23•13 years ago
|
||
Attached new cycle collector log, created with about:ccdump extension running in its own window (opened via openDialog("about:ccdump")).
The log clearly shows that function test_foobarListener (0xfde7f80) leaked. It's being held by a proxy object (0xfde1e70) which is in turn referenced by another proxy (0xfdf6a60) that is referenced by an nsIDOMEventListener wrapper (0x7f913c8) in another compartment. The strange thing: according to this log the entire browser.xul document leaked, about:memory wasn't showing this compartment however.
Attachment #593374 -
Attachment is obsolete: true
Attachment #593375 -
Attachment is obsolete: true
Comment 24•13 years ago
|
||
There is only one compartment for chrome.
Reporter | ||
Comment 25•13 years ago
|
||
I used a simple Python script to reduce the log to the direct or indirect owners of test_foobarListener function (0xfde7f80). Apparently, the root preventing everything from being garbage collected is an nsINavBookmarkObserver implementation.
Reporter | ||
Comment 26•13 years ago
|
||
The nsINavBookmarkObserver in question is this one: http://hg.mozilla.org/mozilla-central/file/5e756e59a794/browser/base/content/browser-places.js#l968
Reporter | ||
Comment 27•13 years ago
|
||
This seems to be a bug in PlacesUtils.jsm, particularly in PlacesUtils.removeLazyBookmarkObserver(). It uses the following check when removing an observer:
> if (this._bookmarksServiceObserversQueue.length == 0) {
> this.bookmarks.removeObserver(aObserver, false);
Supposedly, the length of the queue should always be 0 once the bookmarks service is ready. I checked it in the browser leaking memory - the queue contains one element even though the bookmarks service is ready, that's why the observer isn't being removed and causes the browser window to leak. The observer in the queue is also a PlacesStarButton instance - the one from the first browser window I guess.
I guess this bug needs to be renamed and recategorized.
Component: DOM: Events → Places
Product: Core → Toolkit
QA Contact: events → places
Summary: Event handlers attached to elements of a XUL window leak → PlacesUtils.removeLazyBookmarkObserver() doesn't always remove observers, causes browser window to leak
Reporter | ||
Comment 28•13 years ago
|
||
I am using the default homepage (about:home), this might be important here. Also, if I force the bookmarks service to initialize (e.g. by opening the Bookmarks menu) I can reproduce the issue immediately rather than the second time I try it.
Assignee | ||
Comment 29•13 years ago
|
||
thanks for investigating the issue, will look into it.
Assignee: khuey → mak77
Reporter | ||
Comment 30•13 years ago
|
||
The logic in PlacesUtils is wrong. After looking into it, the "bookmarks-service-ready" notification is being sent out the first time a bookmark changes (via PlacesCategoryStarter.js component), not when the bookmarks service is instantiated. Which means that the bookmarks service can be instantiated (e.g. because the Bookmarks menu has been opened) but the "bookmarks-service-ready" notification hasn't been sent out yet (no bookmarks changed). In this situation addLazyBookmarkObserver() will add observers to the bookmarks service (because it goes by service instantiation) while removeLazyBookmarkObserver() will still attempt to remove them from the queue (because it sees an observer in the queue). The simplest solution is probably to drop checking whether the service has been instantiated - queue the observers until "bookmarks-service-ready" notification is received and only after that start adding observers to the bookmarks service.
Assignee | ||
Comment 31•13 years ago
|
||
Your analysis is pretty good, though it misses the reason it's checking the service instantiation, that is the fact the notification may have happened before PlacesUtils was listening. Though I can fix that and rely on your suggestion; this notification is a specific thing for PlacesUtils so I can just call its observe method and that ensures it is aware of the status.
Assignee | ||
Comment 32•13 years ago
|
||
This should do it, the test exactly hits this case.
Attachment #600153 -
Flags: review?(dietrich)
Attachment #600153 -
Flags: feedback?(trev.moz)
Assignee | ||
Updated•13 years ago
|
Flags: in-testsuite+
Comment 33•13 years ago
|
||
Comment on attachment 600153 [details] [diff] [review]
patch v1.0
Review of attachment 600153 [details] [diff] [review]:
-----------------------------------------------------------------
change looks ok, thanks.
Attachment #600153 -
Flags: review?(dietrich) → review+
Reporter | ||
Comment 34•13 years ago
|
||
Comment on attachment 600153 [details] [diff] [review]
patch v1.0
Looks good to me and removes some unnecessary overhead as well.
Attachment #600153 -
Flags: feedback?(trev.moz) → feedback+
Assignee | ||
Comment 35•13 years ago
|
||
Target Milestone: --- → mozilla13
Assignee | ||
Comment 36•13 years ago
|
||
Status: NEW → RESOLVED
Closed: 13 years ago
Resolution: --- → FIXED
Reporter | ||
Comment 37•13 years ago
|
||
The issue described in comment 0 is no longer present in Mozilla/5.0 (Windows NT 6.1; WOW64; rv:13.0) Gecko/20120227 Firefox/13.0a1.
You need to log in
before you can comment on or make changes to this bug.
Description
•