Skip to content

Commit

Permalink
Add a close event to MessagePort
Browse files Browse the repository at this point in the history
As discussed in #1766, there is currently no timely and reliable way to detect when a MessagePort becomes disentangled. This makes it difficult to release resources associated with ports.

This adds a close event to MessagePort objects. For example, if there are two entangled ports, portA and portB, and portA is closed, the close event is fired on portB. See https://github.com/fergald/explainer-messageport-close for an explainer.

Closes #1766.
  • Loading branch information
nononokam authored Jan 12, 2024
1 parent c814fe3 commit cc2634f
Showing 1 changed file with 64 additions and 31 deletions.
95 changes: 64 additions & 31 deletions source
Original file line number Diff line number Diff line change
Expand Up @@ -103057,6 +103057,13 @@ new PaymentRequest(…); // Allowed to use
<li><p>Set <var>document</var>'s <i data-x="concept-document-salvageable">salvageable</i> state
to false.</p></li>

<li><p>Let <var>ports</var> be the list of <code>MessagePort</code>s whose <span>relevant global
object</span>'s <span data-x="concept-document-window">associated <code>Document</code></span> is
<var>document</var>.</p></li>

<li><p>For each <var>port</var> in <var>ports</var>, <span>disentangle</span>
<var>port</var>.</p></li>

<li><p>Run any <span>unloading document cleanup steps</span> for <var>document</var> that
are defined by this specification and <span>other applicable specifications</span>.</p></li>

Expand Down Expand Up @@ -115351,6 +115358,7 @@ interface <dfn interface>MessagePort</dfn> : <span>EventTarget</span> {
// event handlers
attribute <span>EventHandler</span> <span data-x="handler-MessagePort-onmessage">onmessage</span>;
attribute <span>EventHandler</span> <span data-x="handler-MessagePort-onmessageerror">onmessageerror</span>;
attribute <span>EventHandler</span> <span data-x="handler-MessagePort-onclose">onclose</span>;
};

dictionary <dfn dictionary>StructuredSerializeOptions</dfn> {
Expand Down Expand Up @@ -115445,6 +115453,40 @@ dictionary <dfn dictionary>StructuredSerializeOptions</dfn> {
</li>
</ol>

<p>The <dfn>disentangle</dfn> steps, given a <code>MessagePort</code> <var>initiatorPort</var>
which initiates disentangling, are as follows:</p>

<ol>
<li><p>Let <var>otherPort</var> be the <code>MessagePort</code> which <var>initiatorPort</var>
was entangled with.</p></li>

<li><p><span>Assert</span>: <var>otherPort</var> exists.</p></li>

<li><p>Disentangle <var>initiatorPort</var> and <var>otherPort</var>, so that they are no longer
entangled or associated with each other.</p></li>

<li><p><span data-x="concept-event-fire">Fire an event</span> named <code
data-x="event-close">close</code> at <var>otherPort</var>.</p></li>
</ol>

<div class="note">
<p>The <code data-x="event-close">close</code> event will be fired even if the port is not
explicitly closed. The cases where this event is dispatched are:</p>

<ul>
<li>the <code data-x="dom-MessagePort-close">close()</code> method was called;</li>

<li>the <code>Document</code> was <span data-x="destroy a document">destroyed</span>; or</li>

<li>the <code>MessagePort</code> was <a href="#ports-and-garbage-collection">garbage
collected</a>.</li>
</ul>

<p>We only dispatch the event on <var>otherPort</var> because <var>initiatorPort</var> explicitly
triggered the close, its <code>Document</code> no longer exists, or it was already garbage
collected, as described above.</p>
</div>

<hr>

<p id="transferMessagePort"><code>MessagePort</code> objects are <span>transferable
Expand Down Expand Up @@ -115605,7 +115647,7 @@ dictionary <dfn dictionary>StructuredSerializeOptions</dfn> {
<ol>
<li><p>Set <span>this</span>'s <span>[[Detached]]</span> internal slot value to true.</p></li>

<li><p>If <span>this</span> is entangled, disentangle it.</p></li>
<li><p>If <span>this</span> is entangled, <span>disentangle</span> it.</p></li>
</ol>

<hr>
Expand All @@ -115621,6 +115663,7 @@ dictionary <dfn dictionary>StructuredSerializeOptions</dfn> {
<tbody>
<tr><td><dfn attribute for="MessagePort"><code data-x="handler-MessagePort-onmessage">onmessage</code></dfn> <td> <code data-x="event-message">message</code>
<tr><td><dfn attribute for="MessagePort"><code data-x="handler-MessagePort-onmessageerror">onmessageerror</code></dfn> <td> <code data-x="event-messageerror">messageerror</code>
<tr><td><dfn attribute for="MessagePort"><code data-x="handler-MessagePort-onclose">onclose</code></dfn> <td> <code data-x="event-close">close</code>
</table>

<p>The first time a <code>MessagePort</code> object's <code
Expand All @@ -115631,27 +115674,27 @@ dictionary <dfn dictionary>StructuredSerializeOptions</dfn> {
</div>


<h4>Broadcasting to many ports</h4>

<!-- NON-NORMATIVE SECTION -->

<p>Broadcasting to many ports is in principle relatively simple: keep an array of
<code>MessagePort</code> objects to send messages to, and iterate through the array to send a
message. However, this has one rather unfortunate effect: it prevents the ports from being garbage
collected, even if the other side has gone away. To avoid this problem, implement a simple
protocol whereby the other side acknowledges it still exists. If it doesn't do so after a certain
amount of time, assume it's gone, close the <code>MessagePort</code> object, and let it be garbage
collected.</p>


<h4 id="ports-and-garbage-collection">Ports and garbage collection</h4>

<div w-nodev>
<p>When a <code>MessagePort</code> object <var>o</var> is garbage collected, if <var>o</var> is
entangled, then the user agent must <span data-x="disentangle">disentangle</span>
<var>o</var>.</p>

<p>When a <code>MessagePort</code> object <var>o</var> is entangled and <code
data-x="event-message">message</code> or <code data-x="event-messageerror">messageerror</code>
event listener is registered, user agents must act as if <var>o</var>'s entangled
<code>MessagePort</code> object has a strong reference to <var>o</var>.</p>

<p>When a <code>MessagePort</code> object <var>o</var> is entangled, user agents must either act
as if <var>o</var>'s entangled <code>MessagePort</code> object has a strong reference to
<var>o</var>, or as if <var>o</var>'s <span>relevant global object</span> has a strong reference
to <var>o</var>.</p>
<p>Furthermore, a <code>MessagePort</code> object must not be garbage collected while there
exists an event referenced by a <span data-x="concept-task">task</span> in a <span>task
queue</span> that is to be dispatched on that <code>MessagePort</code> object, or while the
<code>MessagePort</code> object's <span>port message queue</span> is enabled and not empty.</p>
<!-- we might not need to explicitly say the first part if UI Events is fixed to say that events
on a task queue prevent GC -->

<!-- ports in the ports attribute of a MessageEvent that isn't dispatched yet are safe because
the MessageEvent is safe -->

<div class="note">

Expand All @@ -115660,20 +115703,10 @@ dictionary <dfn dictionary>StructuredSerializeOptions</dfn> {

<p>Of course, if this was to occur on both sides of the channel, then both ports could be garbage
collected, since they would not be reachable from live code, despite having a strong reference to
each other.</p>
each other. However, if a message port has a pending message, it is not garbage collected.</p>

</div>

<p>Furthermore, a <code>MessagePort</code> object must not be garbage collected while there exists
an event referenced by a <span data-x="concept-task">task</span> in a <span>task queue</span> that is to be dispatched on that <code>MessagePort</code>
object, or while the <code>MessagePort</code> object's <span>port message queue</span> is enabled
and not empty.</p> <!-- we
might not need to explicitly say the first part if UI Events is fixed to say that events on a
task queue prevent GC -->

<!-- ports in the ports attribute of a MessageEvent that isn't dispatched yet are safe because the
MessageEvent is safe -->

</div>

<p class="note">Authors are strongly encouraged to explicitly close <code>MessagePort</code>
Expand Down Expand Up @@ -140177,8 +140210,8 @@ INSERT INTERFACES HERE
<tr> <!-- close -->
<td> <dfn event for="HTMLElement"><code data-x="event-close">close</code></dfn>
<td> <code>Event</code>
<td> <code>CloseWatcher</code>, <code>dialog</code> elements
<td> Fired at <code>CloseWatcher</code> objects or <code>dialog</code> elements when they are closed via a <span>close request</span> or via web developer code
<td> <code>CloseWatcher</code>, <code>dialog</code> elements, <code>MessagePort</code>
<td> Fired at <code>CloseWatcher</code> objects or <code>dialog</code> elements when they are closed via a <span>close request</span> or via web developer code, or at <code>MessagePort</code> objects when <span data-x="disentangle">disentangled</span>

<tr> <!-- connect -->
<td> <dfn event for="SharedWorkerGlobalScope"><code data-x="event-WorkerGlobalScope-connect">connect</code></dfn>
Expand Down

0 comments on commit cc2634f

Please sign in to comment.