FYI I have opened 1 bug and merged 2 PRs to help track this:

https://bugs.launchpad.net/juju-core/+bug/1611427
https://github.com/juju/juju/pull/5958
https://github.com/juju/utils/pull/229

Katherine Cox-Buday <katherine.cox-bu...@canonical.com> writes:

> Hey All,
>
> We currently have 3 ways we're performing retries in Juju:
>
> 1. Archaic, custom, intra-package retry code.
> 2. github.com/juju/utils::{Countdown,BackoffTimer}
> 3. github.com/juju/retry (current best practice)
>
> I have just touched some code which fits #1, and when I was attempting to 
> retrofit it to #3, I found it a little obtuse. Here's why:
>
> In my case (and I assume most), I wanted something like this:
>
> for range retries { // Loop should break if our parent is cancelled
>       // Do something
>     // Success, retry, or fatal error
> }
>
> To implement this, I do something like this:
>
> args := retry.CallArgs{
>     Func: func() error {
>       // Body of my loop
>     },
>     BackoffFunc: func(delay time.Duration, attempt int) time.Duration {
>         if attempt == 1 {
>             return delay
>         }
>         return delay * factor
>     },
>     Stop: dying,
>     // etc...
> }
>
> Call(args)
>
> We attempt to encapsulate every scenario for retries in 
> github.com/juju/retry/::CallArgs. We have variables to determine if errors 
> are fatal, to notify things, how to back off, etc. This feels very 
> heavy-weight to me, and a bit like the monolith model rather than the unix 
> model. I would prefer the logic be left to the caller (i.e. do I break out of 
> this loop? do I notify something?), and the interface into a retry strategy 
> be much simpler, say a channel like in time.Tick(time.Duration) <-chan 
> time.Time.
>
> How would folks feel about something like this?
>
> func BackoffTick(done <-chan struct{}, clock clock.Cock, delay time.Duration, 
> factor int64) <-chan time.Time {
>       signalStream := make(chan time.Time)
>       go func() {
>               defer close(signalStream)
>               for {
>                       select {
>                       case <-done:
>                               return
>                       case signalStream <- time.After(delay):
>                               delay = time.Duration(delay.Nanoseconds() * 
> factor)
>                       }
>               }
>       }()
>       return signalStream
> }
>
> With this, the above becomes:
>
> for range BackoffTick(dying, myClock, 1*time.Second, 2) {
>     // Determination of fatality and notifications happen here
> }
>
> If you want a max retry concept, you do this:
>
> for range Take(done, 3, BackoffTick(dying, myClock, 1*time.Second, 2)) {
>     // Determination of fatality and notifications happen here
> }
>
> If you want a max duration you do this:
>
> done = Timeout(done, 5*time.Minute)
> for range Take(done, 3, BackoffTick(dying, myClock, 1*time.Second, 2)) {
>     // Determination of fatality and notifications happen here
> }
>
> Functionally, what's in juju/retry is fine; and I can stuff anything I want 
> into the function. It just feels a little odd to use in that I must put the 
> body of my loop in a function, and I dislike that the CallArgs struct 
> attempts to encapsulate a bunch of different scenarios.
>
> Thoughts?
>
> Also, is it on the roadmap to address the inconsitant retries throughout 
> Juju? I almost used utils.BackoffTimer until I started looking deeper. I'm 
> going to submit a PR to flag it as deprecated.

-- 
Katherine

-- 
Juju-dev mailing list
Juju-dev@lists.ubuntu.com
Modify settings or unsubscribe at: 
https://lists.ubuntu.com/mailman/listinfo/juju-dev

Reply via email to