-
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
fix(Observable): Fix Observable.subscribe to add operator TeardownLogic to returned Subscription #4434
fix(Observable): Fix Observable.subscribe to add operator TeardownLogic to returned Subscription #4434
Conversation
What's the use case that requires this change? |
Every E.g. the code coverage report of the |
I'm curious as to whether your opening this PR had something to do with your looking at That operator's current implementation has a bug and when this PR is merged the bug will be fixed and the implementation will no longer return a teardown function. I'm also wondering whether there are any other operator implementations that return teardown functions - I suspect there aren't. |
@cartant yes, I remembered from that discussion that this issue exists, but fixing this issue does not change the behavior of I still think that the existence of this issue is at least a (publicly visible) code smell which should be fixed, either by respecting the contract of the
I suspect or hope this too, but we don't know since the |
I was wondering whether you were looking at this change going some way to fixing the behaviour of Although it won't be used anywhere in RxJS's internals - once the above-referenced PR is merged - I don't think the return type can be removed. It would be a breaking change as far as the TypeScript declarations are concerned. I'll have a closer look at what you've proposed, in a short while. |
src/internal/Subscription.ts
Outdated
@@ -166,6 +166,8 @@ export class Subscription implements SubscriptionLike { | |||
const tmp = subscription; | |||
subscription = new Subscription(); | |||
subscription._subscriptions = [tmp]; | |||
} else if (this._subscriptions && this._subscriptions.some(s => s === subscription)) { | |||
return subscription; | |||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see why this is necessary. How does this relate to the change you made in Observable.ts
? If it's not directly related to that change - and it solves another problem - it should be in a separate PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change prevents three unit tests from failing. Otherwise the change in Observable.ts
would add Subscribers
a second time to the _subscriptions
array, because most built-in operators have their own Subscriber
implementation which already add themself to the array:
rxjs/src/internal/Subscriber.ts
Line 76 in ef557bb
destinationOrNext.add(this); |
Here are the failing unit tests:
3 failing
1) observeOn operator
should clean up subscriptions created by async scheduling (prevent memory leaks #2244):
Uncaught AssertionError: expected 3 to equal 2
+ expected - actual
-3
+2
at SafeSubscriber._next (spec/operators/observeOn-spec.ts:108:57)
at SafeSubscriber.__tryOrUnsub (src/internal/Subscriber.ts:267:10)
at SafeSubscriber.next (src/internal/Subscriber.ts:209:14)
at Subscriber._next (src/internal/Subscriber.ts:139:22)
at Subscriber.next (src/internal/Subscriber.ts:99:12)
at Notification.observe (src/internal/Notification.ts:36:42)
at AsapAction.ObserveOnSubscriber.dispatch (src/internal/operators/observeOn.ts:81:18)
at AsapAction.AsyncAction._execute (src/internal/scheduler/AsyncAction.ts:121:12)
at AsapAction.AsyncAction.execute (src/internal/scheduler/AsyncAction.ts:96:24)
at AsapScheduler.flush (src/internal/scheduler/AsapScheduler.ts:17:26)
at runIfPresent (src/internal/util/Immediate.ts:8:5)
at src/internal/util/Immediate.ts:16:34
at <anonymous>
2) switchAll
should not leak when child completes before each switch (prevent memory leaks #2355):
AssertionError: expected 2 to equal 1
+ expected - actual
-2
+1
at spec/operators/switch-spec.ts:239:10
at Context.modified (spec/helpers/testScheduler-ui.ts:206:13)
3) switchAll
should not leak if we switch before child completes (prevent memory leaks #2355):
AssertionError: expected 2 to equal 1
+ expected - actual
-2
+1
at spec/operators/switch-spec.ts:258:10
at Context.modified (spec/helpers/testScheduler-ui.ts:206:13)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm familiar with some of those tests. They are very low-level and fragile. It's likely that the change is unnecessary and that the low-level tests should be changed, instead.
I will have a close look at this later.
Thanks for the work that you've out into this PR.
@PSanetra Sorry for the delay in reviewing this. I'm working on this ATM. I'm going to revert the change made to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've tried to make the changes mentioned in this review by pushing to your branch's PR, but I'm unable to do so. Could you please make the changes?
src/internal/Subscription.ts
Outdated
@@ -166,6 +166,8 @@ export class Subscription implements SubscriptionLike { | |||
const tmp = subscription; | |||
subscription = new Subscription(); | |||
subscription._subscriptions = [tmp]; | |||
} else if (this._subscriptions && this._subscriptions.some(s => s === subscription)) { | |||
return subscription; | |||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've reviewed the failing tests and you are correct: your changes to Subscription
are needed. However, I'd like them to be done a little differently.
I'd like the change you've made to the add
method in Subscription.ts
to be reverted and, instead, I'd like the check to be moved to _addParent
- so that all of the parent checks are co-located. Like this:
/** @internal */
private _addParent(parent: Subscription): boolean {
let { _parent, _parents } = this;
if (_parent === parent) {
// If the new parent is the same as the current parent, then do nothing.
return false;
} else if (!_parent) {
// If we don't have a parent, then set this._parent to the new parent.
this._parent = parent;
return true;
} else if (!_parents) {
// If there's already one parent, but not multiple, allocate an Array to
// store the rest of the parent Subscriptions.
this._parents = [parent];
return true;
} else if (_parents.indexOf(parent) === -1) {
// Only add the new parent to the _parents list if it's not already there.
_parents.push(parent);
return true;
}
return false;
}
And, in the add
method, I'd like the pushing of the subscription onto the array to be wrapped in a conditional that uses the return value of the above call:
if (subscription._addParent(this)) {
subscriptions.push(subscription);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have implemented your requested changes and also added you as collaborator to the repository fork.
3ef67c0
to
a6df26d
Compare
…ic to returned Subscription.
a6df26d
to
7c61e57
Compare
Pull Request Test Coverage Report for Build 7956
💛 - Coveralls |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Thank you, @PSanetra! |
The
Observable.subscribe
method does not addTeardownLogic
of a liftedOperator
to the returned Subscription. Therefore theTeardownLogic
of such anOperator
is never called on unsubscriptions. This PR fixes that issue.