Skip to content

Commit f954c91

Browse files
authored
Don't use dependent AbortSignals for Subscriber (#154)
1 parent b8ee44b commit f954c91

File tree

1 file changed

+66
-89
lines changed

1 file changed

+66
-89
lines changed

spec.bs

Lines changed: 66 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ urlPrefix: https://dom.spec.whatwg.org; spec: DOM
3636
text: signal; url: event-listener-signal
3737
for: AbortSignal
3838
text: dependent signals; url: abortsignal-dependent-signals
39-
text: signal abort
39+
text: signal abort; url:abortsignal-signal-abort
4040
</pre>
4141

4242
<style>
@@ -175,15 +175,9 @@ observer/complete steps=].
175175
Each {{Subscriber}} has a <dfn for=Subscriber>teardown callbacks</dfn>, which is a [=list=] of
176176
{{VoidFunction}}s, initially empty.
177177

178-
Each {{Subscriber}} has a <dfn for=Subscriber>complete or error controller</dfn>, which is an
178+
Each {{Subscriber}} has a <dfn for=Subscriber>subscription controller</dfn>, which is an
179179
{{AbortController}}.
180180

181-
Each {{Subscriber}} has a <dfn for=Subscriber>signal</dfn>, which is an {{AbortSignal}}.
182-
183-
Note: This is a [=create a dependent abort signal|dependent signal=], derived from both
184-
[=Subscriber/complete or error controller=]'s [=AbortController/signal=], and
185-
{{SubscribeOptions}}'s {{SubscribeOptions/signal}} (if non-null).
186-
187181
Each {{Subscriber}} has a <dfn for=Subscriber>active</dfn> boolean, initially true.
188182

189183
Note: This is a bookkeeping variable to ensure that a {{Subscriber}} never calls any of the
@@ -193,7 +187,7 @@ The <dfn attribute for=Subscriber><code>active</code></dfn> getter steps are to
193187
[=Subscriber/active=] boolean.
194188

195189
The <dfn attribute for=Subscriber><code>signal</code></dfn> getter steps are to return [=this=]'s
196-
[=Subscriber/signal=].
190+
[=Subscriber/subscription controller=]'s [=AbortController/signal=].
197191

198192
<div algorithm>
199193
The <dfn for=Subscriber method><code>next(|value|)</code></dfn> method steps are:
@@ -228,8 +222,6 @@ The <dfn attribute for=Subscriber><code>signal</code></dfn> getter steps are to
228222

229223
1. [=close a subscription|Close=] [=this=].
230224

231-
1. [=AbortController/Signal abort=] [=this=]'s [=Subscriber/complete or error controller=].
232-
233225
1. Run [=this=]'s [=Subscriber/error algorithm=] given |error|.
234226

235227
[=Assert=]: No <a spec=webidl lt="an exception was thrown">exception was thrown</a>.
@@ -247,8 +239,6 @@ The <dfn attribute for=Subscriber><code>signal</code></dfn> getter steps are to
247239

248240
1. [=close a subscription|Close=] [=this=].
249241

250-
1. [=AbortController/Signal abort=] [=this=]'s [=Subscriber/complete or error controller=].
251-
252242
1. Run [=this=]'s [=Subscriber/complete algorithm=].
253243

254244
[=Assert=]: No <a spec=webidl lt="an exception was thrown">exception was thrown</a>.
@@ -274,43 +264,51 @@ The <dfn attribute for=Subscriber><code>signal</code></dfn> getter steps are to
274264
<div algorithm>
275265
To <dfn>close a subscription</dfn> given a {{Subscriber}} |subscriber|, run these steps:
276266

277-
1. Set |subscriber|'s [=Subscriber/active=] boolean to false.
267+
1. If |subscriber|'s [=Subscriber/active=] is false, then return.
278268

279-
<div class=note>
280-
<p>This algorithm intentionally does not have script-running side-effects; it just updates the
281-
internal state of a {{Subscriber}}. It's important that this algorithm sets
282-
[=Subscriber/active=] to false and clears all of the callback algorithms *before* running any
283-
script, because running script <span class=allow-2119>may</span> reentrantly invoke one of the
284-
methods that closed the subscription in the first place. And closing the subscription <span
285-
class=allow-2119>must</span> ensure that even if a method gets reentrantly invoked, none of the
286-
{{SubscriptionObserver}} callbacks are ever invoked again. Consider this example:</p>
287-
288-
<div class=example id=reentrant-example>
289-
<pre highlight=js>
290-
let innerSubscriber = null;
291-
const producedValues = [];
292-
293-
const controller = new AbortController();
269+
<div class=note>
270+
<p>This guards against re-entrant invocation, which can happen in the "producer-initiated"
271+
unsubscription case. Consider the following example:</p>
272+
<div class=example id=re-entrant-close>
273+
<pre highlight=js>
274+
const outerController = new AbortController();
294275
const observable = new Observable(subscriber =&gt; {
295-
innerSubscriber = subscriber;
276+
subscriber.addTeardown(() =&gt; {
277+
// 2.) This teardown executes inside the "Close" algorithm, while it's
278+
// running. Aborting the downstream signal run its abort algorithms,
279+
// one of which is the currently-running "Close" algorithm.
280+
outerController.abort();
281+
});
282+
283+
// 1.) This immediately invokes the "Close" algorithm, which
284+
// sets subscriber.active to false.
296285
subscriber.complete();
297286
});
298287

299-
observable.subscribe({
300-
next: v =&gt; producedValues.push(v),
301-
complete: () =&gt; innerSubscriber.next('from complete'),
302-
303-
}, {signal: controller.signal}
304-
);
305-
306-
// This invokes the complete() callback, and even though it invokes next() from
307-
// within, the given next() callback will never run, because the subscription
308-
// has already been "closed" before the complete() callback actually executes.
309-
controller.abort();
310-
console.assert(producedValues.length === 0);
311-
</pre>
312-
</div>
313-
</div>
288+
observable.subscribe({}, {signal: outerController.signal});
289+
</pre>
290+
</div>
291+
</div>
292+
293+
1. Set |subscriber|'s [=Subscriber/active=] boolean to false.
294+
295+
1. [=AbortSignal/Signal abort=] |subscriber|'s [=Subscriber/subscription controller=].
296+
297+
Issue: Abort with an appropriate abort reason.
298+
299+
1. [=list/For each=] |teardown| of |subscriber|'s [=Subscriber/teardown callbacks=] sorted in
300+
reverse insertion order:
301+
302+
1. If |subscriber|'s [=relevant global object=] is a {{Window}} object, and its [=associated
303+
Document=] is not [=Document/fully active=], then abort these steps.
304+
305+
Note: This step runs repeatedly because each |teardown| could result in the above
306+
{{Document}} becoming inactive.
307+
308+
1. [=Invoke=] |teardown|.
309+
310+
If <a spec=webidl lt="an exception was thrown">an exception |E| was thrown</a>, call
311+
|subscriber|'s {{Subscriber/error()}} method with |E|.
314312
</div>
315313

316314
<h3 id=observable-api>The {{Observable}} interface</h3>
@@ -526,37 +524,16 @@ An <dfn>internal observer</dfn> is a [=struct=] with the following [=struct/item
526524
: [=Subscriber/complete algorithm=]
527525
:: |internal observer|'s [=internal observer/complete steps=]
528526

529-
: [=Subscriber/signal=]
530-
:: The result of [=creating a dependent abort signal=] from the list «|subscriber|'s
531-
[=Subscriber/complete or error controller=]'s [=AbortController/signal=], |options|'s
532-
{{SubscribeOptions/signal}} if it is non-null», using {{AbortSignal}}, and the [=current
533-
realm=].
534-
535-
1. If |subscriber|'s [=Subscriber/signal=] is [=AbortSignal/aborted=], then [=close a
536-
subscription|close=] |subscriber|.
527+
1. If |options|'s {{SubscribeOptions/signal}} [=map/exists=], then:
537528

538-
Note: This can happen when {{SubscribeOptions}}'s {{SubscribeOptions/signal}} is already
539-
[=AbortSignal/aborted=].
529+
1. If |options|'s {{SubscribeOptions/signal}} is [=AbortSignal/aborted=], then [=close a
530+
subscription|close=] |subscriber|.
540531

541-
1. Otherwise, [=AbortSignal/add|add the following abort algorithm=] to |subscriber|'s
542-
[=Subscriber/signal=]:
532+
1. Otherwise, [=AbortSignal/add|add the following abort algorithm=] to |options|'s
533+
{{SubscribeOptions/signal}}:
543534

544535
1. [=close a subscription|Close=] |subscriber|.
545536

546-
1. [=list/For each=] |teardown| of |subscriber|'s [=Subscriber/teardown callbacks=] sorted in
547-
reverse insertion order:
548-
549-
1. If |subscriber|'s [=relevant global object=] is a {{Window}} object, and its
550-
[=associated Document=] is not [=Document/fully active=], then abort these steps.
551-
552-
Note: This step runs repeatedly because each |teardown| could result in the above
553-
{{Document}} becoming inactive.
554-
555-
1. [=Invoke=] |teardown|.
556-
557-
If <a spec=webidl lt="an exception was thrown">an exception |E| was thrown</a>, call
558-
|subscriber|'s {{Subscriber/error()}} method with |E|.
559-
560537
1. If [=this=]'s [=Observable/subscribe callback=] is a {{SubscribeCallback}}, [=invoke=] it
561538
with |subscriber|.
562539

@@ -618,9 +595,9 @@ For now, see [https://github.com/wicg/observable#operators](https://github.com/w
618595

619596
Note: This will "unsubscribe" from |sourceObservable|, if it has been subscribed to by
620597
this point. This is because |sourceObservable| is subscribed to with the "outer"
621-
|subscriber|'s [=Subscriber/signal=] as an input signal, and that signal will get
622-
[=AbortSignal/signal abort|aborted=] when the "outer" |subscriber|'s
623-
{{Subscriber/complete()}} is called above (and below).
598+
|subscriber|'s [=Subscriber/subscription controller=]'s [=AbortController/signal=] as
599+
an input signal, and that signal will get [=AbortSignal/signal abort|aborted=] when the
600+
"outer" |subscriber|'s {{Subscriber/complete()}} is called above (and below).
624601

625602
: [=internal observer/error steps=]
626603
:: Run |subscriber|'s {{Subscriber/complete()}} method.
@@ -631,7 +608,7 @@ For now, see [https://github.com/wicg/observable#operators](https://github.com/w
631608
mirror |sourceObservable| uninterrupted.
632609

633610
1. Let |options| be a new {{SubscribeOptions}} whose {{SubscribeOptions/signal}} is
634-
|subscriber|'s [=Subscriber/signal=].
611+
|subscriber|'s [=Subscriber/subscription controller=]'s [=AbortController/signal=].
635612

636613
1. <a for=Observable lt="subscribe to an Observable">Subscribe</a> to |notifier| given
637614
|notifierObserver| and |options|.
@@ -705,7 +682,7 @@ For now, see [https://github.com/wicg/observable#operators](https://github.com/w
705682
:: Run |subscriber|'s {{Subscriber/complete()}} method.
706683

707684
1. Let |options| be a new {{SubscribeOptions}} whose {{SubscribeOptions/signal}} is
708-
|subscriber|'s [=Subscriber/signal=].
685+
|subscriber|'s [=Subscriber/subscription controller=]'s [=AbortController/signal=].
709686

710687
1. <a for=Observable lt="subscribe to an Observable">Subscribe</a> to |sourceObservable|
711688
given |sourceObserver| and |options|.
@@ -751,7 +728,7 @@ For now, see [https://github.com/wicg/observable#operators](https://github.com/w
751728
:: Run |subscriber|'s {{Subscriber/complete()}} method.
752729

753730
1. Let |options| be a new {{SubscribeOptions}} whose {{SubscribeOptions/signal}} is
754-
|subscriber|'s [=Subscriber/signal=].
731+
|subscriber|'s [=Subscriber/subscription controller=]'s [=AbortController/signal=].
755732

756733
1. <a for=Observable lt="subscribe to an Observable">Subscribe</a> to |sourceObservable|
757734
given |sourceObserver| and |options|.
@@ -793,7 +770,7 @@ For now, see [https://github.com/wicg/observable#operators](https://github.com/w
793770
:: Run |subscriber|'s {{Subscriber/complete()}} method.
794771

795772
1. Let |options| be a new {{SubscribeOptions}} whose {{SubscribeOptions/signal}} is
796-
|subscriber|'s [=Subscriber/signal=].
773+
|subscriber|'s [=Subscriber/subscription controller=]'s [=AbortController/signal=].
797774

798775
1. <a for=Observable lt="subscribe to an Observable">Subscribe</a> to |sourceObservable|
799776
given |sourceObserver| and |options|.
@@ -832,7 +809,7 @@ For now, see [https://github.com/wicg/observable#operators](https://github.com/w
832809
:: Run |subscriber|'s {{Subscriber/complete()}} method.
833810

834811
1. Let |options| be a new {{SubscribeOptions}} whose {{SubscribeOptions/signal}} is
835-
|subscriber|'s [=Subscriber/signal=].
812+
|subscriber|'s [=Subscriber/subscription controller=]'s [=AbortController/signal=].
836813

837814
1. <a for=Observable lt="subscribe to an Observable">Subscribe</a> to |sourceObservable|
838815
given |sourceObserver| and |options|.
@@ -911,7 +888,7 @@ For now, see [https://github.com/wicg/observable#operators](https://github.com/w
911888
|subscriber|'s {{Subscriber/complete()}} method.
912889

913890
1. Let |options| be a new {{SubscribeOptions}} whose {{SubscribeOptions/signal}} is
914-
|subscriber|'s [=Subscriber/signal=].
891+
|subscriber|'s [=Subscriber/subscription controller=]'s [=AbortController/signal=].
915892

916893
1. <a for=Observable lt="subscribe to an Observable">Subscribe</a> to |sourceObservable|
917894
given |sourceObserver| and |options|.
@@ -976,7 +953,7 @@ For now, see [https://github.com/wicg/observable#operators](https://github.com/w
976953
had not yet completed. Until right now!
977954

978955
1. Let |innerOptions| be a new {{SubscribeOptions}} whose {{SubscribeOptions/signal}} is
979-
|subscriber|'s [=Subscriber/signal=].
956+
|subscriber|'s [=Subscriber/subscription controller=]'s [=AbortController/signal=].
980957

981958
1. <a for=Observable lt="subscribe to an Observable">Subscribe</a> to |innerObservable| given
982959
|innerObserver| and |innerOptions|.
@@ -1044,7 +1021,7 @@ For now, see [https://github.com/wicg/observable#operators](https://github.com/w
10441021
{{Subscriber/complete()}} method.
10451022

10461023
1. Let |options| be a new {{SubscribeOptions}} whose {{SubscribeOptions/signal}} is
1047-
|subscriber|'s [=Subscriber/signal=].
1024+
|subscriber|'s [=Subscriber/subscription controller=]'s [=AbortController/signal=].
10481025

10491026
1. <a for=Observable lt="subscribe to an Observable">Subscribe</a> to |sourceObservable|
10501027
given |sourceObserver| and |options|.
@@ -1098,7 +1075,8 @@ For now, see [https://github.com/wicg/observable#operators](https://github.com/w
10981075
1. Let |innerOptions| be a new {{SubscribeOptions}} whose {{SubscribeOptions/signal}} is the
10991076
result of [=creating a dependent abort signal=] from the list
11001077
«|activeInnerAbortController|'s [=AbortController/signal=], |subscriber|'s
1101-
[=Subscriber/signal=]», using {{AbortSignal}}, and the [=current realm=].
1078+
[=Subscriber/subscription controller=]'s [=AbortController/signal=]», using
1079+
{{AbortSignal}}, and the [=current realm=].
11021080

11031081
1. <a for=Observable lt="subscribe to an Observable">Subscribe</a> to |innerObservable| given
11041082
|innerObserver| and |innerOptions|.
@@ -1134,10 +1112,9 @@ For now, see [https://github.com/wicg/observable#operators](https://github.com/w
11341112
reason=].
11351113

11361114
Note: All we have to do here is [=reject=] |p|. Note that the subscription to [=this=]
1137-
{{Observable}} will also be canceled automatically, since the "inner"
1138-
[=Subscriber/signal=] (created during <a for=Observable lt="subscribe to an
1139-
Observable">subscription</a>) is a [=AbortSignal/dependent signal=] of |options|'s
1140-
{{SubscribeOptions/signal}}.
1115+
{{Observable}} will also be closed automatically, since the "inner" Subscriber gets
1116+
[=close a subscription|closed=] in response to |options|'s {{SubscribeOptions/signal}}
1117+
getting [=AbortSignal/signal abort=].
11411118

11421119
1. Let |values| be a new [=list=].
11431120

@@ -1537,8 +1514,8 @@ partial interface EventTarget {
15371514
Note: This is meant to capture the fact that |event target| can be garbage collected
15381515
by the time this algorithm runs upon subscription.
15391516

1540-
1. If |subscriber|'s [=Subscriber/signal=] is [=AbortSignal/aborted=], abort these
1541-
steps.
1517+
1. If |subscriber|'s [=Subscriber/subscription controller=]'s [=AbortController/signal=]
1518+
is [=AbortSignal/aborted=], abort these steps.
15421519

15431520
1. [=Add an event listener=] with |event target| and an [=event listener=] defined as follows:
15441521

0 commit comments

Comments
 (0)