-
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
Cancel scheduled timeout work, if no longer needed #2135
Conversation
@jayphelps perhaps there's a test we can write for this? I don't see anything here to that regard but should be. |
@mattpodwysocki see my above post 😆 |
@jayphelps * sigh * reading is fundamental... |
src/operator/timeout.ts
Outdated
@@ -45,6 +46,8 @@ class TimeoutOperator<T> implements Operator<T, T> { | |||
class TimeoutSubscriber<T> extends Subscriber<T> { | |||
private index: number = 0; | |||
private _previousIndex: number = 0; | |||
private action: Subscription = null; | |||
|
|||
get previousIndex(): number { |
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.
If we're tracking the scheduled action subscription, do we still need previousIndex
etc.? Same question for hasCompleted
.
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.
not sure, but hasCompleted
indeed seems extraneous. I couldn't figure out why there is an index (aka count) check anyway? Why would it ever not be equal?
src/operator/timeout.ts
Outdated
const currentIndex = this.index; | ||
const timeoutState = { subscriber: this, index: currentIndex }; | ||
|
||
this.cancelTimeout(); |
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.
So here it seems like we can do this:
const { action } = this;
if (!action) {
this.add(this.action = this.scheduler.schedule(...));
} else {
action.schedule(this.waitFor, timeoutState);
}
Instead of scheduling an action, throwing it away, and scheduling again, we can recycle the Action and reschedule it for a new due time. This should be more efficient, and allow us to remove more code from this file.
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 on board with reusing the action.
and allow us to remove more code from this file.
What other code could be removed?
@jayphelps we can use it('should unsubscribe from the scheduled timeout action when timeout is unsubscribed early', () => {
const e1 = hot('--a--b--c---d--e--|');
const e1subs = '^ ! ';
const expected = '--a--b--c-- ';
const unsub = ' ! ';
const result = e1
.lift(function(source) {
const timeoutSubscriber = this;
const { action } = timeoutSubscriber; // get a ref to the action here,
timeoutSubscriber.add(() => { // because it'll be null by the
if (!action.canceled) { // time we get into this function.
throw new Error('TimeoutSubscriber scheduled action wasn\'t canceled');
}
});
return source._subscribe(timeoutSubscriber);
})
.timeout(50, null, rxTestScheduler);
expectObservable(result, unsub).toBe(expected);
expectSubscriptions(e1.subscriptions).toBe(e1subs);
}); |
@jayphelps I submitted a PR to your |
…ed timeout actions. The timeout and timeoutWith operators should dispose their scheduled timeout actions on unsubscription. Also, given the new scheduling architecture, they can recycle their scheduled actions so just one action is allocated per subscription. ReactiveX#2134 ReactiveX#2135
…ed timeout actions. The timeout and timeoutWith operators should dispose their scheduled timeout actions on unsubscription. Also, given the new scheduling architecture, they can recycle their scheduled actions so just one action is allocated per subscription. ReactiveX#2134 ReactiveX#2135
…ed timeout actions. The timeout and timeoutWith operators should dispose their scheduled timeout actions on unsubscription. Also, given the new scheduling architecture, they can recycle their scheduled actions so just one action is allocated per subscription. ReactiveX#2134 ReactiveX#2135
…ed timeout actions. The timeout and timeoutWith operators should dispose their scheduled timeout actions on unsubscription. Also, given the new scheduling architecture, they can recycle their scheduled actions so just one action is allocated per subscription. ReactiveX#2134 ReactiveX#2135
@jayphelps I fixed the lint, rebased, tried and failed to push straight to this PR ref (GH doesn't like that), so I re-PR'd to your timeout branch. |
…heir scheduled work. VirtualActions are immutable so they can be inspected by the TestScheduler. In order to mirror rescheduled stateful Actions, rescheduled VirtualActions shouldn't execute if they've been rescheduled before execution.
…ed timeout actions. The timeout and timeoutWith operators should dispose their scheduled timeout actions on unsubscription. Also, given the new scheduling architecture, they can recycle their scheduled actions so just one action is allocated per subscription.
@@ -89,7 +89,7 @@ | |||
"prepublish": "shx rm -rf ./typings && typings install && npm run build_all", | |||
"publish_docs": "./publish_docs.sh", | |||
"test_mocha": "mocha --opts spec/support/default.opts spec-js", | |||
"debug_mocha": "node-debug _mocha --opts spec/support/debug.opts spec-js", | |||
"debug_mocha": "node --inspect --debug-brk ./node_modules/.bin/_mocha --opts spec/support/debug.opts spec-js", |
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 seems unrelated although I agree with it.
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.
@trxcllnt I believe this is you, cool for me to remove?
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.
as long as it's in a follow up PR that's fine with me. I didn't want to clutter up the PR list with internal config changes that only affect the devs working on the project, but I leave it up to your discretion
spec/operators/timeout-spec.ts
Outdated
const unsub = ' ! '; | ||
|
||
const result = e1 | ||
.lift(function(source) { |
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 not sure this test is very straightforward. Perhaps it could be refactored into using the let
operator, and returning a more basic observable
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.
can we pair on this? I couldn't figure out how to test this at all, so @trxcllnt came up with this solution and it's still not clear to me how we could do it better. 💃
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.
@Blesh let gives you a handle on the source Observable, but lift gives you a handle to each subscriber. In this case we want to know whether the subscriber did something (canceled its internal scheduled action subscription), so we use lift. textbook use-case for lift.
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.
@trxcllnt ugh.. this is a case where we're abusing the fact that Operator
has a call
method, and we're therefor passing a function to lift
here? This is really convoluted, it took me a while to figure that out. If anything this test reminded me why Operator
probably shouldn't have a call
method.
I think we need to find a different way to test this.
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.
@jayphelps I think the way to test this, and other things like it, would be to add functionality to the TestScheduler so that it tracks the Actions it's created and those Actions can be examined to see at what tick they were cancelled. For now, we can leave these tests in here, but they'll need comments explaining what they're doing and why it works.
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.
Basically we need comments explaining why lift is magically accepting a function here and not an Operator instance.
We could simply pass an object with a call method.
.lift({
call(source) { ... }
})
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.
@Blesh this is exactly why Operator has a call method. In an ideal world an operator would just be a function, but because JS sucks, we've had to make them classes. See RxJava, where Operators are functions.
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.
Ben is OK with this for now, but wants some inline comments about why we're doing
spec/operators/timeoutWith-spec.ts
Outdated
const unsub = ' ! '; | ||
|
||
const result = e1 | ||
.lift(function(source) { |
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.
Same as above
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.
Basically we need comments explaining why lift
is magically accepting a function here and not an Operator
instance.
spec/operators/timeout-spec.ts
Outdated
const unsub = ' ! '; | ||
|
||
const result = e1 | ||
.lift(function(source) { |
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.
@jayphelps I think the way to test this, and other things like it, would be to add functionality to the TestScheduler so that it tracks the Actions it's created and those Actions can be examined to see at what tick they were cancelled. For now, we can leave these tests in here, but they'll need comments explaining what they're doing and why it works.
@Blesh alternatively, we should be able to define |
@Blesh what are the blockers to merging this? @jayphelps if possible, could you allow adding commits to this PR from other collaborators? |
@trxcllnt the edit bit is already enabled: We discussed this PR today, minor changes needed: https://github.com/ReactiveX/rxjs-core-notes/blob/master/2016-12/december-20.md#review-prsissues
I won't be able to get to it for at least a couple days, but others with edit bit are welcome to modify. |
Return Observable when merge is called with single lower case observable, so that merge would always return Observable instance.
Add binaryType to config object, so that it is possible to set that parameter on underlying socket before any data emits happen. Closes ReactiveX#2353
Add type signature for using forkJoin with single observable as first parameter and selector function as second parameter, so that typings would not prevent usage which is permitted and properly handled by operator. Closes ReactiveX#2347
…guments Emit undefined insteady of empty array by resulting Observable, when callback function is called without success parameters. Closes ReactiveX#2254
In resulting Observable emit undefined when callback is called without parameters, instead of emitting empty array.
The max line length is set to 150 in 'tslint.json'. In specific regions, it is desirable to allow longer lines, so these regions should be wrapped in comments like the following: ```js // Max line length enforced here. /* tslint:disable:max-line-length */ // Max line length NOT enforced here. /* tslint:enable:max-line-length */ <-- CORRECT // Max line length enforced here. ``` In many cases, the re-enabling comment incorrectly included `disable` instead of `enable` (as shown below), which essentially keeps the `max-line-length` **disabled** for the rest of the file: ```js // Max line length enforced here. /* tslint:disable:max-line-length */ // Max line length NOT enforced here. /* tslint:disable:max-line-length */ <-- INCORRECT // Max line length NOT enforced here. ``` This commit fixes these comments, so the `max-line-length` rule is properly enforced in regions that don't need longer lines.
Adds new parameter in windowTime operator to control how much values given window can emit. Closes ReactiveX#1301
…descriptions Add ObservableInput and SubscribableOrPromise interface descriptions, as well as link these interfaces in type descriptions of operators, so that users always know what kind of parameters they can pass to used methods.
fix commit messages
I'm going to lower commit message checker to warning so ignore failure for now please. |
LOL... why are there so many commits in this PR now? |
@Blesh lint/spec typings fixes and merge commits/conflicts le sigh |
@trxcllnt sorry for inconvenience but rebase to master once again will resolve commit message checker failure (though you'll see warning still). |
Ugh. I'm not sure what to do with this PR. It's older that dirt. It's a solid fix, but there's something crazy going on with the commits. Can we resubmit this one and/or clean it up? |
hmm I'm not really following all of @trxcllnt's additions. If he's not available to fix, we can abandon this PR and I can try to fix the underlying problem again from a fresh start? |
@jayphelps @Blesh they're just merge commits from master to resolve the merge conflicts. the CI was being really restrictive against merge commits, but @kwonoj relaxed that requirement in master, so I've done one more follow-up merge to hopefully make travis pass. |
This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
Fixes #2134
This ties the action subscription returned from a call to
schedule()
to the lifespan of the subscriber, so it handles error/complete. On reschedules it more explicitly cancels, if there's an existing.I think this fixes it, but to be honest, I can't be sure. It's not immediately clear how to test it. At first glance, it doesn't appear we're testing this possible bug in the other operators which could have similar problems. e.g. debounceTime. So other operators could be hiding the fact that they don't clean up properly, because of guards in their work callback, or the subscriptions that protect against values continuing after error/complete.
I tried peeking into the internals of TestScheduler to see if maybe I could grab a reference to the
VirtualAction
and checkaction.pending
, but because it's entirely virtual, the action comes and goes only inside atestScheduler.flush()
.Perhaps @trxcllnt aka Mr. Scheduler can halp?