MessagePorts are not GCed after becoming disentangled
Categories
(Core :: DOM: postMessage, defect, P2)
Tracking
()
People
(Reporter: fergald, Unassigned)
Details
User Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36
Steps to reproduce:
https://fergald.github.io/random/html-snippets/WeakRefMessagePort/post.html
Post a MessageChannel's port to another window.
Close that window.
Force garbage collection.
Actual results:
The WeakRef to the array is garbage collected.
The WeakRef to the MessagePort is never garbage collected.
Expected results:
Both should be garbage collected. port2
was send to a window that has now been closed, so there are no references to it. The spec says that port1
should then be disentangled from port2
and now there are no references to it either.
https://html.spec.whatwg.org/multipage/web-messaging.html#ports-and-garbage-collection
Comment 1•9 months ago
|
||
I would like to attempt confirmation of your report. Would you mind re-writing the steps to reproduce more clearly?
Maybe you could correct my understanding of your steps:
- Load the test page in the browser: https://fergald.github.io/random/html-snippets/WeakRefMessagePort/post.html
- Click the "Open window and send port" button
- Close the newly opened tab
- Click the "Force GC by allocating and freeing lots of memory." button
Actual: How exactly do I verify that:
"The WeakRef to the array is garbage collected.
The WeakRef to the MessagePort is never garbage collected."?
Expected: How exactly do I verify that :
"Both should be garbage collected. port2 was sent to a window that has now been closed, so there are no references to it. The spec says that port1 should then be disentangled from port2 and now there are no references to it either."?
Thank you for your contribution and for your understanding!
Comment 2•9 months ago
|
||
This doesn't need to go through normal triage; moving this to our MessagePort component since this is expected as part of spec refinements around https://github.com/whatwg/html/issues/1766 and https://github.com/fergald/explainer-messageport-close
Updated•9 months ago
|
Reporter | ||
Comment 3•9 months ago
|
||
Andrew, I don't think this is expected. I'm reporting a bug that I just happened to find while exploring stuff for "close" event.
Daniel,
Before clicking "Force GC", the text in the page reads
port1_weak.deref() == [object MessagePort]
control_weak.deref() == a string
After closing the window (and maybe clicking "Force GC") the text in page updates to
port1_weak.deref() == [object MessagePort]
control_weak.deref() == undefined
indicating that the string has been GCed but the MessagePort has not.
If the MessagePort was GCed, it would read
port1_weak.deref() == undefined
control_weak.deref() == undefined
Both Chrome and Safari are easy to get into that state. FF never seems to reach that state.
It may be that something else needs to happen to make it GC harder but I don't know what that is.
Comment 4•9 months ago
|
||
(In reply to Fergal Daly from comment #3)
Andrew, I don't think this is expected. I'm reporting a bug that I just happened to find while exploring stuff for "close" event.
Bad phrasing on my part, apologies. Expected in the sense of we ~intentionally didn't want to react to the other end closing in the past. With the changes to the spec and related discussion where we have clarity that we're fine with revealing the information, you are of course correct that the correct, expected behavior is that the port will be GCed.
Comment 5•6 months ago
|
||
:annevk had asked why we don't GC and I wrote up in https://github.com/whatwg/html/issues/10201#issuecomment-2121870369 the following which I include here because it's hard to re-find these things:
In Firefox, all shipped MessagePorts go through our IPC. When our IPC layer is processing the implicit close for the collected port:
- We decide to close the entangled port
- Which then results in us calling close on the parent IPC actor.
- But our Close method just dissociates the parent actor from our service but does not tear down the IPC parent actor, so the child actor never knows that it's not talking to anyone. We do have a method that would do that but we only call that in the nightmarish case where the port was further re-shipped but there are messages in-flight and state machines are catching up with that.
Honestly, it's not clear this behavior was entirely intentional (we will certainly spew NS_WARNINGs in the parent process when this case happens if the closed port sends us messages); the code has a bunch of technical debt and is due for a cleanup. But I think I might have avoided changing the behavior in a few bugs because it seemed like it might have been equally been intentional to avoid leaking GC, but it's hard to tell because our infrastructure makes it hard to find old discussion of things.
Description
•