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

Scheduled and repeat API + retry refactoring #172

Merged
merged 40 commits into from
Jul 10, 2024
Merged

Scheduled and repeat API + retry refactoring #172

merged 40 commits into from
Jul 10, 2024

Conversation

micossow
Copy link
Contributor

@micossow micossow commented Jul 3, 2024

closes #57

Summary:

  • retry* API has become more generic and renamed to scheduled*
  • existing retry* API uses scheduled* internally and retry-specific DSL has remained unchanged
  • new repeat* API also uses scheduled*` internally with repeat-specific DSL
  • scheduled API, Schedule trait and its implementations should be decoupled from retry API, specifically it calculates "duration" instead of "delay", which can later be interpreted as a delay or interval.
  • scheduled implementation is covered by tests for retry and repeat
  • docs updated where applicable

@micossow micossow requested review from adamw and kciesielski July 5, 2024 07:19
policy.onRetry,
shouldContinueOnError = policy.resultPolicy.isWorthRetrying,
shouldContinueOnResult = t => !policy.resultPolicy.isSuccess(t),
delayPolicy = DelayPolicy.SinceTheEndOfTheLastInvocation
Copy link
Member

Choose a reason for hiding this comment

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

shouldn't the delay policy be part of RetryPolicy, so that it's also user-configurable if needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My idea was to create a public API runScheduled* with the all configurable options and make retry* and repeat* as "thin" as possible - basically an opinionated variants of runScheduled*, specifically:

  • retry - that behaves similarly to resillience4j retries, where sleep time is counted since the end of previous operation, has an optional onRetry callback and initial delay (sleep before first operation) is not relevant
  • repeat - that behaves similarly to Akka's Source.tick, where sleep time is counted since the start of previous operation (interval), doesn't expose onRepeat callback (not relevant) and initial delay is allowed
    WDYT?

Copy link
Member

Choose a reason for hiding this comment

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

Ok, sounds good. Let's just document these defaults quite clearly, plus all the flexibility that runScheduled (or maybe simply scheduled?) gives you

import scala.concurrent.duration.FiniteDuration
import scala.concurrent.duration.DurationInt

case class RepeatConfig[E, T](
Copy link
Member

Choose a reason for hiding this comment

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

we have RepeatConfig, but RetryPolicy - why?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

RetryPolicy has been refactored to RetryConfig

private[scheduling] sealed trait Finite extends Schedule:
def maxRepeats: Int
def initialDelay: FiniteDuration = Duration.Zero
def fallbackTo(fallback: Finite): Finite = FallingBack(this, fallback)
Copy link
Member

Choose a reason for hiding this comment

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

maybe .andThen would be a better name?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

// TODO: shouldn't this trait be unsealed so that users can create their own schedules?
sealed trait Schedule:
// TODO: consider better name that `attempt`
def nextDelay(attempt: Int, lastDelay: Option[FiniteDuration]): FiniteDuration
Copy link
Member

Choose a reason for hiding this comment

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

attempt - that's the attempt number, yes?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For retries it is an attempt number, for repeats it's an repetition number. I'm still looking for some common name :)

import scala.util.Random

// TODO: shouldn't this trait be unsealed so that users can create their own schedules?
sealed trait Schedule:
Copy link
Member

Choose a reason for hiding this comment

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

let's keep it sealed for now, and wait for a use-case to unseal it ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

todo removed

enum DelayPolicy:
case SinceTheStartOfTheLastInvocation, SinceTheEndOfTheLastInvocation

case class RunScheduledConfig[E, T](
Copy link
Member

Choose a reason for hiding this comment

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

these classes/methods can all be private[ox] I suppose? they're not meant to be used by the end-user?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it has been decided that the scheduled and ScheduledConfig will be public API

@adamw
Copy link
Member

adamw commented Jul 5, 2024

Looks good but it would be great to see some examples of the API in action (in docs, or stand-alone) to see how the API can be used

@micossow micossow requested a review from adamw July 9, 2024 12:17
@micossow micossow changed the title [WIP] Refactor retry* functions to scheduled* Scheduled and repeat API + retry refactoring Jul 9, 2024
@micossow micossow marked this pull request as ready for review July 9, 2024 12:18
import scala.util.Random

sealed trait Schedule:
// TODO: consider better name that `attempt`
Copy link
Member

Choose a reason for hiding this comment

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

iteration?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I think it would be the best in that case

/** The mode that specifies how to interpret the duration provided by the schedule. */
enum SleepMode:
/** Interval (since the start of the last operation), i.e. guarantees that the next operation will start no sooner than the specified
* duration after the previous operations has finished. If the previous operation takes longer than the interval, the next operation will
Copy link
Member

Choose a reason for hiding this comment

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

started, not finished?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

@@ -0,0 +1,18 @@
# 8. Retries
Copy link
Member

Choose a reason for hiding this comment

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

Good idea to add an ADR :)

doc/repeat.md Outdated
The `repeat` config requires a `Schedule`, which indicates how many times and with what interval should the `operation`
be repeated.

In addition, it is possible to define strategies for handling the results and errors returned by the `operation`:
Copy link
Member

Choose a reason for hiding this comment

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

question: Shouldn't we have a stronger division between retry, which is related to "trying and possibly failing" and repeat, which has no notion of errors? I think it would be simpler from the user perspective, otherwise they may be confused when to use retry and when to use repeat + shouldContinueOnError.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right, I don't think that shouldContinueOnError handler would be used often.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

shouldContinueOnError removed and mentioned that repeat can be used with retry + example

doc/scheduled.md Outdated Show resolved Hide resolved
@micossow micossow requested review from adamw and kciesielski July 10, 2024 07:22
import scala.util.Try

/** Retries an operation returning a direct result until it succeeds or the policy decides to stop.
/** Retries an operation returning a direct result until it succeeds or the config decides to stop.
Copy link
Member

Choose a reason for hiding this comment

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

I think I'd also mention here that retry is a special-case of schedule, with a given set of defaults (and enumarte these defaults). Maybe this should be in the @see section?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

import scala.util.Random

sealed trait Schedule:
def nextDuration(iteration: Int, lastDuration: Option[FiniteDuration]): FiniteDuration
Copy link
Member

Choose a reason for hiding this comment

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

or ... we're using invocation in the docs quite often, so maybe that would be a good name as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good idea!

@micossow micossow requested a review from adamw July 10, 2024 08:52
private[scheduling] object FiniteAndFiniteSchedules:
/** A schedule that repeats indefinitely, using [[first]] first [[first.maxRepeats]] number of times, and then always using [[second]].
*/
def forever(first: Finite, second: Infinite): Infinite = FiniteAndInfiniteSchedules(first, second)
Copy link
Member

Choose a reason for hiding this comment

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

is this correct that "finite and finite" schedule contains a method which takes a Finite and Infinite schedule?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, it doesn't make sense after refactoring. Fixed!

- `RepeatConfig.fixedRate[E, T](maxInvocations: Int, interval: FiniteDuration, initialDelay: Option[FiniteDuration] = None)`
- `RepeatConfig.fixedRateForever[E, T](interval: FiniteDuration, initialDelay: Option[FiniteDuration] = None)`
- `RepeatConfig.backoff[E, T](maxInvocations: Int, firstInterval: FiniteDuration, maxInterval: FiniteDuration = 1.minute, jitter: Jitter = Jitter.None, initialDelay: Option[FiniteDuration] = None)`
- `RepeatConfig.backoffForever[E, T](firstInterval: FiniteDuration, maxInterval: FiniteDuration = 1.minute, jitter: Jitter = Jitter.None, initialDelay: Option[FiniteDuration] = None)`
Copy link
Member

Choose a reason for hiding this comment

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

it would be good to give an example of combinining schedules using .andThen (here and in the code snippet)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added some examples to scheduled.md and See [scheduled](scheduled.md) for details on how to create custom schedules. to repeat.md and retries.md

Copy link
Member

@adamw adamw left a comment

Choose a reason for hiding this comment

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

Just some minor adjustments, but looks great otherwise!

@adamw adamw merged commit c61beae into master Jul 10, 2024
5 checks passed
@adamw adamw deleted the 57-repeats branch July 10, 2024 10:36
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.

Add a schedule-aware repeat
3 participants