Skip to content
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

Add option to reset interval on success #18

Merged
merged 8 commits into from May 17, 2020

Conversation

vially
Copy link
Contributor

@vially vially commented Mar 17, 2020

This pull-request adds an option to reset the state of the retryBackoff operator on successful emissions on the source observable.

When this option is enabled (it's off by default to maintain backwards compatibility) both the delay interval and the attempt counter gets reset when the source observable emits a value.

Use cases

Some of the arguments and motivation for this option were nicely laid out in ReactiveX/rxjs#1413. A similar option was recently added to the retry operator (ReactiveX/rxjs#5280).

Quoting @benlesh from the linked issue:

Often with retryWhen there is a desire to reset some "state" of your retries when the stream your retrying starts receiving values successfully again. For example, in a network retry scenario where you want to exponentially step back from the first failure. Once you get a value over that network observable, you want to reset your exponential step back to the first level again, because things are going well now.

Code review notes

The first four commits are not related to this pull-request and I can submit them separately if needed. They are related to some small nuisances I encountered when running the tests locally.

@alex-okrushko
Copy link
Owner

Ah, sorry. Very nice!
I saw the initial issue, but got distracted.

Ben was just talking at ng-conf about reset for retry and I remembered the issue that you opened.
Screen Shot 2020-04-03 at 2 58 00 PM

@vially
Copy link
Contributor Author

vially commented Apr 3, 2020

No worries! Let me know if you'd prefer to submit the unrelated commits separately to keep this pull-request cleaner.

package.json Outdated Show resolved Hide resolved
yarn.lock Outdated
@@ -2926,6 +2926,13 @@ run-queue@^1.0.0, run-queue@^1.0.3:
dependencies:
aproba "^1.1.1"

rxjs@^6.4.0:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this would be reverted as well

return <T>(source: Observable<T>) =>
source.pipe(
return <T>(source: Observable<T>) => {
let index = 0;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would create a problem with referential transparency.
Nicholas Jamieson describes it very well here: https://stackoverflow.com/a/52022545/1167879

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯 Awesome catch and thanks for the link. Very interesting explanation of the perils of keeping state in operators. I also found this article, by the same author, which goes into a bit more details about the same problem.

These are the steps I took to fix this issue:

  • add two tests related to this internal state problems (296637b): ✔️ Tests passing
    This was to confirm everything worked as expected before my changes to the operator.
  • add my initial naive implementation (d1356ab): ❌ Tests failing
    One of those two tests was failing, which confirms the initial implementation was buggy. Although it wasn't the referential transparency one that was failing, it was the other one.
  • apply the fix suggested in stackoverflow/linked article (df20de5): ✔️ Tests passing

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice! Thanks for getting it done.

Let's adjust the tap and one more quick one - do you have prettier (extension) installed for formatting? I should've added it to dev deps as well 😕

Also, what are your thought about making resetOnSuccess = true by default?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've reformatted the retryBackoff.ts file using this prettier config (because it seemed close enough to the original formatting):

{
    "arrowParens": "avoid",
    "singleQuote": true
}

Also, what are your thought about making resetOnSuccess = true by default?

My impression is that having it true by default is a reasonable default (I think more often than not that's what you want). My main concern is that it's a breaking change but that shouldn't be a problem if it will be introduced with a major version bump and documented accordingly.

)
),
tap((_: T) => {
if (resetOnSuccess) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you are not using arguments, then there's no need to put them at all :) () => {...}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing the argument completely makes TypeScript throw an error when compiling the code. Keeping the argument in there, even if it's not used, seemed to be a good trade-off in order to make the code compile.

I would still like to get rid of it as you've suggested but I haven't found a way to do so in a clean way. Let me know if you have any suggestions.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm... I don't see any complains from TS 🙂(did a git pull for you PR).

Screen Shot 2020-05-13 at 11 42 19 PM

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, it seems to compile just fine now (8fa59d7). Not sure what has changed since I saw that error but it's good that it works now.

Copy link
Contributor Author

@vially vially left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the late reply. Took me a while to find the time to get back to this to address your feedback.

)
),
tap((_: T) => {
if (resetOnSuccess) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing the argument completely makes TypeScript throw an error when compiling the code. Keeping the argument in there, even if it's not used, seemed to be a good trade-off in order to make the code compile.

I would still like to get rid of it as you've suggested but I haven't found a way to do so in a clean way. Let me know if you have any suggestions.

return <T>(source: Observable<T>) =>
source.pipe(
return <T>(source: Observable<T>) => {
let index = 0;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯 Awesome catch and thanks for the link. Very interesting explanation of the perils of keeping state in operators. I also found this article, by the same author, which goes into a bit more details about the same problem.

These are the steps I took to fix this issue:

  • add two tests related to this internal state problems (296637b): ✔️ Tests passing
    This was to confirm everything worked as expected before my changes to the operator.
  • add my initial naive implementation (d1356ab): ❌ Tests failing
    One of those two tests was failing, which confirms the initial implementation was buggy. Although it wasn't the referential transparency one that was failing, it was the other one.
  • apply the fix suggested in stackoverflow/linked article (df20de5): ✔️ Tests passing

@vially
Copy link
Contributor Author

vially commented May 16, 2020

I've added two more commits to address your latest feedback. I can squash and clean-up the commits before merging but I didn't want to do it yet because it makes it harder to review just the new changes.

@alex-okrushko alex-okrushko merged commit 7d38283 into alex-okrushko:master May 17, 2020
@alex-okrushko
Copy link
Owner

alex-okrushko commented May 17, 2020

Awesome @vially !
Thanks a lot for your contribution and for following through all the review changes.

Announced it as well: https://twitter.com/AlexOkrushko/status/1262202129772621824

@vially vially deleted the reset-on-success branch May 18, 2020 06:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants