-
Notifications
You must be signed in to change notification settings - Fork 3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Teardown and error/complete messages are out of order #7443
Comments
This CL "fixes" Observable unsubscription/teardown timing. As a matter of accidental historical precedent, Observables in JavaScript (but not in other languages) had implemented the "rule" that upon Subscriber#error() or Subscriber#complete(), the subscriber would: 1. First, invoke the appropriate Observer callback, if provided (i.e., complete() or error() callback). 2. Signal abort Subscriber#signal, which invokes any teardowns and also fires the `abort` event at the signal. However, after dom@chromium.org discussed this more with ben@benlesh.com, we came to the conclusion that the principle of "as soon as you know you will teardown, you MUST close the subscription and any upstream subscriptions" should be adhered. This means the above steps must be inverted. This is a small-in-size but medium-in-impact design change for the Observable concept, and led to a blog post [1] and an announcement [2] that the RxJS library intends to change its historical ordering of these events. This CL: 1. Inverts the order of the aforementioned steps in the Blink implementation. 2. Improves some tests that assert this new ordering. 3. Simplifies the takeUntil() operator in general. The Observable spec will be updated alongside this commit. [1]: https://benlesh.com/posts/observables-are-broken-and-so-is-javascript/ [2]: ReactiveX/rxjs#7443 R=masonf@chromium.org Bug: 1485981 Change-Id: I376e66eef490808d264dc999862a801d591aa278
Related: I think our fix to The fix mentioned here will solve for reentrancy with We can resolve this issue in Subscriber, then implement take more "naturally" and it should work as expected. |
This CL "fixes" Observable unsubscription/teardown timing. As a matter of accidental historical precedent, Observables in JavaScript (but not in other languages) had implemented the "rule" that upon Subscriber#error() or Subscriber#complete(), the subscriber would: 1. First, invoke the appropriate Observer callback, if provided (i.e., complete() or error() callback). 2. Signal abort Subscriber#signal, which invokes any teardowns and also fires the `abort` event at the signal. However, after dom@chromium.org discussed this more with ben@benlesh.com, we came to the conclusion that the principle of "as soon as you know you will teardown, you MUST close the subscription and any upstream subscriptions" should be adhered. This means the above steps must be inverted. This is a small-in-size but medium-in-impact design change for the Observable concept, and led to a blog post [1] and an announcement [2] that the RxJS library intends to change its historical ordering of these events. This CL: 1. Inverts the order of the aforementioned steps in the Blink implementation. 2. Improves some tests that assert this new ordering. 3. Simplifies the takeUntil() operator in general. The Observable spec will be updated alongside this commit. [1]: https://benlesh.com/posts/observables-are-broken-and-so-is-javascript/ [2]: ReactiveX/rxjs#7443 R=masonf@chromium.org Bug: 1485981 Change-Id: I376e66eef490808d264dc999862a801d591aa278
This CL "fixes" Observable unsubscription/teardown timing. As a matter of accidental historical precedent, Observables in JavaScript (but not in other languages) had implemented the "rule" that upon Subscriber#error() or Subscriber#complete(), the subscriber would: 1. First, invoke the appropriate Observer callback, if provided (i.e., complete() or error() callback). 2. Signal abort Subscriber#signal, which invokes any teardowns and also fires the `abort` event at the signal. However, after dom@chromium.org discussed this more with ben@benlesh.com, we came to the conclusion that the principle of "as soon as you know you will teardown, you MUST close the subscription and any upstream subscriptions" should be adhered. This means the above steps must be inverted. This is a small-in-size but medium-in-impact design change for the Observable concept, and led to a blog post [1] and an announcement [2] that the RxJS library intends to change its historical ordering of these events. This CL: 1. Inverts the order of the aforementioned steps in the Blink implementation. 2. Improves some tests that assert this new ordering. 3. Simplifies the takeUntil() operator in general. The Observable spec will be updated alongside this commit. [1]: https://benlesh.com/posts/observables-are-broken-and-so-is-javascript/ [2]: ReactiveX/rxjs#7443 R=masonf@chromium.org Bug: 1485981 Change-Id: I376e66eef490808d264dc999862a801d591aa278
This CL "fixes" Observable unsubscription/teardown timing. As a matter of accidental historical precedent, Observables in JavaScript (but not in other languages) had implemented the "rule" that upon Subscriber#error() or Subscriber#complete(), the subscriber would: 1. First, invoke the appropriate Observer callback, if provided (i.e., complete() or error() callback). 2. Signal abort Subscriber#signal, which invokes any teardowns and also fires the `abort` event at the signal. However, after dom@chromium.org discussed this more with ben@benlesh.com, we came to the conclusion that the principle of "as soon as you know you will teardown, you MUST close the subscription and any upstream subscriptions" should be adhered. This means the above steps must be inverted. This is a small-in-size but medium-in-impact design change for the Observable concept, and led to a blog post [1] and an announcement [2] that the RxJS library intends to change its historical ordering of these events. This CL: 1. Inverts the order of the aforementioned steps in the Blink implementation. 2. Improves some tests that assert this new ordering. 3. Simplifies the takeUntil() operator in general. The Observable spec will be updated alongside this commit: WICG/observable#120. [1]: https://benlesh.com/posts/observables-are-broken-and-so-is-javascript/ [2]: ReactiveX/rxjs#7443 R=masonf@chromium.org Bug: 1485981 Change-Id: I376e66eef490808d264dc999862a801d591aa278
This CL "fixes" Observable unsubscription/teardown timing. As a matter of accidental historical precedent, Observables in JavaScript (but not in other languages) had implemented the "rule" that upon Subscriber#error() or Subscriber#complete(), the subscriber would: 1. First, invoke the appropriate Observer callback, if provided (i.e., complete() or error() callback). 2. Signal abort Subscriber#signal, which invokes any teardowns and also fires the `abort` event at the signal. However, after dom@chromium.org discussed this more with ben@benlesh.com, we came to the conclusion that the principle of "as soon as you know you will teardown, you MUST close the subscription and any upstream subscriptions" should be adhered. This means the above steps must be inverted. This is a small-in-size but medium-in-impact design change for the Observable concept, and led to a blog post [1] and an announcement [2] that the RxJS library intends to change its historical ordering of these events. This CL: 1. Inverts the order of the aforementioned steps in the Blink implementation. 2. Improves some tests that assert this new ordering. 3. Simplifies the takeUntil() operator in general. The Observable spec will be updated alongside this commit: WICG/observable#120. [1]: https://benlesh.com/posts/observables-are-broken-and-so-is-javascript/ [2]: ReactiveX/rxjs#7443 R=masonf@chromium.org Bug: 1485981 Change-Id: I376e66eef490808d264dc999862a801d591aa278
This CL "fixes" Observable unsubscription/teardown timing. As a matter of accidental historical precedent, Observables in JavaScript (but not in other languages) had implemented the "rule" that upon Subscriber#error() or Subscriber#complete(), the subscriber would: 1. First, invoke the appropriate Observer callback, if provided (i.e., complete() or error() callback). 2. Signal abort Subscriber#signal, which invokes any teardowns and also fires the `abort` event at the signal. However, after dom@chromium.org discussed this more with ben@benlesh.com, we came to the conclusion that the principle of "as soon as you know you will teardown, you MUST close the subscription and any upstream subscriptions" should be adhered. This means the above steps must be inverted. This is a small-in-size but medium-in-impact design change for the Observable concept, and led to a blog post [1] and an announcement [2] that the RxJS library intends to change its historical ordering of these events. This CL: 1. Inverts the order of the aforementioned steps in the Blink implementation. 2. Improves some tests that assert this new ordering. 3. Simplifies the takeUntil() operator in general. The Observable spec will be updated alongside this commit: WICG/observable#120. [1]: https://benlesh.com/posts/observables-are-broken-and-so-is-javascript/ [2]: ReactiveX/rxjs#7443 R=masonf@chromium.org Bug: 1485981 Change-Id: I376e66eef490808d264dc999862a801d591aa278 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5311097 Commit-Queue: Dominic Farolino <dom@chromium.org> Reviewed-by: Mason Freed <masonf@chromium.org> Cr-Commit-Position: refs/heads/main@{#1263562}
This CL "fixes" Observable unsubscription/teardown timing. As a matter of accidental historical precedent, Observables in JavaScript (but not in other languages) had implemented the "rule" that upon Subscriber#error() or Subscriber#complete(), the subscriber would: 1. First, invoke the appropriate Observer callback, if provided (i.e., complete() or error() callback). 2. Signal abort Subscriber#signal, which invokes any teardowns and also fires the `abort` event at the signal. However, after dom@chromium.org discussed this more with ben@benlesh.com, we came to the conclusion that the principle of "as soon as you know you will teardown, you MUST close the subscription and any upstream subscriptions" should be adhered. This means the above steps must be inverted. This is a small-in-size but medium-in-impact design change for the Observable concept, and led to a blog post [1] and an announcement [2] that the RxJS library intends to change its historical ordering of these events. This CL: 1. Inverts the order of the aforementioned steps in the Blink implementation. 2. Improves some tests that assert this new ordering. 3. Simplifies the takeUntil() operator in general. The Observable spec will be updated alongside this commit: WICG/observable#120. [1]: https://benlesh.com/posts/observables-are-broken-and-so-is-javascript/ [2]: ReactiveX/rxjs#7443 R=masonf@chromium.org Bug: 1485981 Change-Id: I376e66eef490808d264dc999862a801d591aa278 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5311097 Commit-Queue: Dominic Farolino <dom@chromium.org> Reviewed-by: Mason Freed <masonf@chromium.org> Cr-Commit-Position: refs/heads/main@{#1263562}
This CL "fixes" Observable unsubscription/teardown timing. As a matter of accidental historical precedent, Observables in JavaScript (but not in other languages) had implemented the "rule" that upon Subscriber#error() or Subscriber#complete(), the subscriber would: 1. First, invoke the appropriate Observer callback, if provided (i.e., complete() or error() callback). 2. Signal abort Subscriber#signal, which invokes any teardowns and also fires the `abort` event at the signal. However, after dom@chromium.org discussed this more with ben@benlesh.com, we came to the conclusion that the principle of "as soon as you know you will teardown, you MUST close the subscription and any upstream subscriptions" should be adhered. This means the above steps must be inverted. This is a small-in-size but medium-in-impact design change for the Observable concept, and led to a blog post [1] and an announcement [2] that the RxJS library intends to change its historical ordering of these events. This CL: 1. Inverts the order of the aforementioned steps in the Blink implementation. 2. Improves some tests that assert this new ordering. 3. Simplifies the takeUntil() operator in general. The Observable spec will be updated alongside this commit: WICG/observable#120. [1]: https://benlesh.com/posts/observables-are-broken-and-so-is-javascript/ [2]: ReactiveX/rxjs#7443 R=masonf@chromium.org Bug: 1485981 Change-Id: I376e66eef490808d264dc999862a801d591aa278 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5311097 Commit-Queue: Dominic Farolino <dom@chromium.org> Reviewed-by: Mason Freed <masonf@chromium.org> Cr-Commit-Position: refs/heads/main@{#1263562}
This CL "fixes" Observable unsubscription/teardown timing. As a matter of accidental historical precedent, Observables in JavaScript (but not in other languages) had implemented the "rule" that upon Subscriber#error() or Subscriber#complete(), the subscriber would: 1. First, invoke the appropriate Observer callback, if provided (i.e., complete() or error() callback). 2. Signal abort Subscriber#signal, which invokes any teardowns and also fires the `abort` event at the signal. However, after dom@chromium.org discussed this more with ben@benlesh.com, we came to the conclusion that the principle of "as soon as you know you will teardown, you MUST close the subscription and any upstream subscriptions" should be adhered. This means the above steps must be inverted. This is a small-in-size but medium-in-impact design change for the Observable concept, and led to a blog post [1] and an announcement [2] that the RxJS library intends to change its historical ordering of these events. This CL: 1. Inverts the order of the aforementioned steps in the Blink implementation. 2. Improves some tests that assert this new ordering. 3. Simplifies the takeUntil() operator in general. The Observable spec will be updated alongside this commit: WICG/observable#120. [1]: https://benlesh.com/posts/observables-are-broken-and-so-is-javascript/ [2]: ReactiveX/rxjs#7443 R=masonf@chromium.org Bug: 1485981 Change-Id: I376e66eef490808d264dc999862a801d591aa278 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5311097 Commit-Queue: Dominic Farolino <dom@chromium.org> Reviewed-by: Mason Freed <masonf@chromium.org> Cr-Commit-Position: refs/heads/main@{#1263562}
…s, a=testonly Automatic update from web-platform-tests DOM: Reorder Observable completion events This CL "fixes" Observable unsubscription/teardown timing. As a matter of accidental historical precedent, Observables in JavaScript (but not in other languages) had implemented the "rule" that upon Subscriber#error() or Subscriber#complete(), the subscriber would: 1. First, invoke the appropriate Observer callback, if provided (i.e., complete() or error() callback). 2. Signal abort Subscriber#signal, which invokes any teardowns and also fires the `abort` event at the signal. However, after dom@chromium.org discussed this more with ben@benlesh.com, we came to the conclusion that the principle of "as soon as you know you will teardown, you MUST close the subscription and any upstream subscriptions" should be adhered. This means the above steps must be inverted. This is a small-in-size but medium-in-impact design change for the Observable concept, and led to a blog post [1] and an announcement [2] that the RxJS library intends to change its historical ordering of these events. This CL: 1. Inverts the order of the aforementioned steps in the Blink implementation. 2. Improves some tests that assert this new ordering. 3. Simplifies the takeUntil() operator in general. The Observable spec will be updated alongside this commit: WICG/observable#120. [1]: https://benlesh.com/posts/observables-are-broken-and-so-is-javascript/ [2]: ReactiveX/rxjs#7443 R=masonf@chromium.org Bug: 1485981 Change-Id: I376e66eef490808d264dc999862a801d591aa278 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5311097 Commit-Queue: Dominic Farolino <dom@chromium.org> Reviewed-by: Mason Freed <masonf@chromium.org> Cr-Commit-Position: refs/heads/main@{#1263562} -- wpt-commits: a1a5ea2b3dc460ce75cbfcf237538d959469b1ed wpt-pr: 44682
…s, a=testonly Automatic update from web-platform-tests DOM: Reorder Observable completion events This CL "fixes" Observable unsubscription/teardown timing. As a matter of accidental historical precedent, Observables in JavaScript (but not in other languages) had implemented the "rule" that upon Subscriber#error() or Subscriber#complete(), the subscriber would: 1. First, invoke the appropriate Observer callback, if provided (i.e., complete() or error() callback). 2. Signal abort Subscriber#signal, which invokes any teardowns and also fires the `abort` event at the signal. However, after dom@chromium.org discussed this more with ben@benlesh.com, we came to the conclusion that the principle of "as soon as you know you will teardown, you MUST close the subscription and any upstream subscriptions" should be adhered. This means the above steps must be inverted. This is a small-in-size but medium-in-impact design change for the Observable concept, and led to a blog post [1] and an announcement [2] that the RxJS library intends to change its historical ordering of these events. This CL: 1. Inverts the order of the aforementioned steps in the Blink implementation. 2. Improves some tests that assert this new ordering. 3. Simplifies the takeUntil() operator in general. The Observable spec will be updated alongside this commit: WICG/observable#120. [1]: https://benlesh.com/posts/observables-are-broken-and-so-is-javascript/ [2]: ReactiveX/rxjs#7443 R=masonf@chromium.org Bug: 1485981 Change-Id: I376e66eef490808d264dc999862a801d591aa278 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5311097 Commit-Queue: Dominic Farolino <dom@chromium.org> Reviewed-by: Mason Freed <masonf@chromium.org> Cr-Commit-Position: refs/heads/main@{#1263562} -- wpt-commits: a1a5ea2b3dc460ce75cbfcf237538d959469b1ed wpt-pr: 44682
It turns out that fixing this barely broke anything in our entire test suite. It did cause me to discover a weird behavior with |
The problem
This is actually a big deal. Since the dawn of RxJS, as best I can tell, the ordering of teardown (unsubscription from source) and complete or error notifications has always been "notify complete/error first, then unsubscribe from source". TMK, I was never give a reason for this, it's just how it always was.
Fast forward 9-10 years, and we've run into a few issues, generally involving reentrancy. The issue presents itself in situations where a source synchronously emits back into itself before clean up of the source subscription can occur. Here are a few examples:
We solved the above by unsubscribing before we emit from our operators. And we came up with the principle (I believe @cartant agreed with me on this), at least for operator development that "As soon as you know you can unsubscribe from a source, you should unsubscribe before doing anything else".
Unfortunately the problem is endemic. As we never patched this scenario:
And fixing that is going to get gnarly fast. It basically means that anyone that develops an operator that takes a user-land function would have to create a subscriber first, so it can capture that subscriber and use it to unsubscribe from the source before notifying of an error.
That's a real smell, and it got me thinking "What if I applied this principle everywhere? Including the Subscriber itself?"
Proposed solution:
Always unsubscribe before completing or erroring, but from within Subscriber.
Looking into it, it solves our problems everywhere when it comes to reentrancy with regards to complete and error notifications.
But it's a big deal, so I had to go back to the original principal from @headinthebox that Observable is the "dual" of iterable. I needed to see if a consumer could act on the information that an iterable was "done" before the producer finalized.
Digging into this in both JavaScript and DotNet showed that finalization of the producer within an Iterable (or Enumerator in DotNet's case) always occurred before the consumer had a chance to act on the information that iteration was complete (or errored):
Even in the error case:
Even if you manually iterate:
Even if we check out Promise, a contemporary of observable, it adheres to this behavior:
Observable, Not so much:
In every case for iterable, the dual of observable, the consumer can't possibly act on the information that the producer is complete/errored before the producer finalizes!
This means that, by principle, we should always be unsubscribing from the source prior to notification of complete or error. This would be a breaking change for RxJS.
Other thoughts:
This still doesn't solve for situations like
take
,takeWhile
, andfirst
where we still want to unsubscribe from source prior to emitting a value. The only way to solve that would be to allow some sort of "next and complete" emission from the Observer, like an optional boolean on the next call...subscriber.next('somevalue', true)
or the like. But I'm unwilling to push for that change at this time, it's complicated, and I don't think that the API is user friendly. It's interesting, as a mental exercise, that there are actually 4 ways that observables can terminate in practice: completion, error, consumer unsubscribe, and "final value". It also points to a design flaw in JavaScript's single-step iteration design, in that you can't differentiate between{ done: true, value: undefined }
being a "final value" that should be included in iteration, or just "complete", because JS has implicit "undefined" return values. Languages with classic designed iteration that take two calls to perform iteration cannot have this issue.The text was updated successfully, but these errors were encountered: