RFC: time to remove the "smart" output formatter?
Hi folks, While perusing the codebase, I was reminded of our "smart" output formatter. It is the yaml but not quite yaml one. I propose we remove it, and default commands that were defaulting to that to use yaml instead. Thoughts? Tim -- Juju-dev mailing list Juju-dev@lists.ubuntu.com Modify settings or unsubscribe at: https://lists.ubuntu.com/mailman/listinfo/juju-dev
Re: Let's talk retries
roger peppewrites: > There's a fourth way that you haven't mentioned, which fits somewhere > in between 1 and 2 I think (it was the first explicit general retry > code in Juju AFAIK), which is utils.Attempt. > > I'd suggest that the way it's used matches the pattern you'd like to > write quite well: > > for attempt := strategy.State(); attempt.Next(); { Do something } Thanks, Roger, I had missed that one. utils.Attempt would be my next preference, but it would have to be taught how to be interruptable via channel. I also feel that because so much of Go is oriented towards channels, exposing the wait via a channel rather than a function makes sense, but I don't immediately have a use-case in mind. > > AFAIR this pattern was arrived at after some discussion between myself > and Gustavo. At the time I was thinking of some kind of callback-based > scheme like the others schemes you mention, but I think that leaving > the caller in control and the loop explicit is actually nicer - the > logic is clear to the reader and more composable with other primitives > (it is also a good match for the pattern you propose). I strongly agree with this. In my estimation, favoring composability and inline code is much clearer and maintainable than piling one abstractions on another. > How about making AttemptStrategy a little more flexible? It looks like we've standardized on juju/retry. I don't have the historical context in my cache to know why we've rewritten retries 3 times now o.0 -- Katherine -- Juju-dev mailing list Juju-dev@lists.ubuntu.com Modify settings or unsubscribe at: https://lists.ubuntu.com/mailman/listinfo/juju-dev
Change in provisioning functionality
Hey All, I have just landed a fix for lp:1597170 (juju2 beta10 fails to deploy all services in aws if storage is not ready), and it may come with some unexpected (desirable?) consequences. In my fix, I found that -- though there is code in place to do so -- we never actually retried to provision machines if an error is encountered. This has been corrected. Please keep an eye out for any unintended consequences to either the UX, or base functionality, and raise bugs if necessary. Thanks very much! -- Katherine -- Juju mailing list Juju@lists.ubuntu.com Modify settings or unsubscribe at: https://lists.ubuntu.com/mailman/listinfo/juju
Re: Juju as a snap
On Mon, Aug 8, 2016 at 12:44 AM, Stuart Bishopwrote: > > > On 3 August 2016 at 04:00, Nicholas Skaggs > wrote: > >> The Juju client has been snappified and pushed to the juju store. Note >> for now it's just an amd64 build. >> >> snap install juju --beta --devmode >> >> The beta channel contains the latest beta, 2.0-beta13. This is a sneak >> preview of further builds, including the latest crack available in the edge >> channel as development happens. >> > > You might want to get the snap added to the normal release process... > beta14 is the new hotness. > > (Launchpad can do automatic builds of multiple architectures from a git or > bzr branch, and push them to the charm store for you) > > > -- > Stuart Bishop > I've pushed beta14 now, again only an amd64 build however. As far as launchpad goes, I do plan on having it build the snaps (with all the arches we desire), but we are working through a couple kinks in getting it to build properly in launchpad. I've also opened a PR to upstream the snapcraft.yaml, and I think it's going to need some updates and ideas about how to enable developer builds as we go forward. Exciting stuff! -- Juju mailing list Juju@lists.ubuntu.com Modify settings or unsubscribe at: https://lists.ubuntu.com/mailman/listinfo/juju
Re: Let's talk retries
So, in my opinion, juju/retry is pretty good. I think with some tweaking and some intelligent use, it can feel every bit as lightweight as we'd like. If we have common use cases, we just need to write up an API to encapsulate them. If we want a "retry N times or until this channel is closed" then write an api for that. Having preconstructed call args with prepopulated best practices for delays, backoffs, etc. can make it a lot easier. There's no reason running a complex retry can't look like this: // closure to encapsulate local variables f := func() error { return foo.bar(a, b, c) } err := retry.WithCancel(f, juju.StdRetry, doneCh) Where StdRetry is the juju standard retry scheme (standard timeout, backoff strategy, etc), and doneCh is the channel you expect to get closed to cancel this action. That's doable with the current API if we add cancellation (though it would be nice if we extracted the function-to-run from the args struct, since it makes it more obvious that the retry args can be reused for different functions). I think goroutines should be controlled by the caller. If the caller wants to put retry.Foo() in a goroutine, it can... but I don't think it's useful to have that be retry's responsibility, it complicates the package too much. On Tue, Aug 9, 2016 at 11:26 AM Katherine Cox-Buday < katherine.cox-bu...@canonical.com> wrote: > Andrew's queue is certainly nice, but I agree with the points Roger is > raising: > > 1. Go's idiom for coordinating concurrent operations are goroutines and > channels. > 2. Sticking to these idioms makes it much easier to compose parts into a > whole (if only because the language strongly bends code that way). > > As a matter of opinion, I also dislike having to instantiate structs to > encapsulate logic; I would rather write the logic inline, or in a closure > -- possibly in a goroutine. > > William Readewrites: > > > On Tue, Aug 9, 2016 at 10:17 AM, roger peppe > > wrote: > > > > BTW I'm not saying that a timer queue is never the correct answer. > > In some > > circumstances, it can be the exactly the right thing to use. > > > > > > Yeah -- the context here is that katco has been looking at the > > provisioner, and I think a timer queue is sensible there. Firing off > > goroutines for every machine is not necessarily bad by any means, but > > it *is* somewhat harder to get right in every circumstance (e.g. what > > happens when a machine is removed while that other goroutine is > > working on provisioning it?). > > > > There are certainly many places where a single retryable op *is* the > > only important thing and there's no call for a control loop. I'm not > > suggesting we should always use a queue; but I'd tend to encourage it > > whenever we have a component dealing with many similar retryable > > tasks. (I definitely should be using it in dependency.Engine, for > > example.) > > > > Cheers > > William > > > > -- > Katherine > > -- > Juju-dev mailing list > Juju-dev@lists.ubuntu.com > Modify settings or unsubscribe at: > https://lists.ubuntu.com/mailman/listinfo/juju-dev > -- Juju-dev mailing list Juju-dev@lists.ubuntu.com Modify settings or unsubscribe at: https://lists.ubuntu.com/mailman/listinfo/juju-dev
Re: Let's talk retries
Andrew's queue is certainly nice, but I agree with the points Roger is raising: 1. Go's idiom for coordinating concurrent operations are goroutines and channels. 2. Sticking to these idioms makes it much easier to compose parts into a whole (if only because the language strongly bends code that way). As a matter of opinion, I also dislike having to instantiate structs to encapsulate logic; I would rather write the logic inline, or in a closure -- possibly in a goroutine. William Readewrites: > On Tue, Aug 9, 2016 at 10:17 AM, roger peppe > wrote: > > BTW I'm not saying that a timer queue is never the correct answer. > In some > circumstances, it can be the exactly the right thing to use. > > > Yeah -- the context here is that katco has been looking at the > provisioner, and I think a timer queue is sensible there. Firing off > goroutines for every machine is not necessarily bad by any means, but > it *is* somewhat harder to get right in every circumstance (e.g. what > happens when a machine is removed while that other goroutine is > working on provisioning it?). > > There are certainly many places where a single retryable op *is* the > only important thing and there's no call for a control loop. I'm not > suggesting we should always use a queue; but I'd tend to encourage it > whenever we have a component dealing with many similar retryable > tasks. (I definitely should be using it in dependency.Engine, for > example.) > > Cheers > William > -- Katherine -- Juju-dev mailing list Juju-dev@lists.ubuntu.com Modify settings or unsubscribe at: https://lists.ubuntu.com/mailman/listinfo/juju-dev
Re: Question about scripts and blocking
On 9 August 2016 at 18:32, Matthew Williamswrote: > > c) Is there some other solution that I've not considered? > Plugins can be used to add the behaviour you need, allowing time for the core team to catch up. I've already got a blocking action runner that just needs the juju 1 backport done for example, which we need to make the cli usable before we use actions in anger on production. Running actions on all units in a service is a small extension to that. git+ssh:// git.launchpad.net/~stub/+git/juju-act if you are curious. But yes, I think what you describe is in theory suitable for actions. But in practice, I think people are not using actions as 'juju run' is more usable and flexible. https://bugs.launchpad.net/juju-core/+bug/1445066 is the original bug report on needing blocking actions. I don't know about one for running on all units in a service, but I have heard it discussed before. -- Stuart Bishop -- Juju mailing list Juju@lists.ubuntu.com Modify settings or unsubscribe at: https://lists.ubuntu.com/mailman/listinfo/juju
Re: Juju and snappy implementation spike - feedback please
> > ... > > > The big difference to me is that $SNAP_USER_DATA will roll back if the > snap is rolled back. I'm not sure what happens if the snap is removed and > reinstalled. Given end users should no longer need to be messing around > with the dotfiles, I think the rollback behaviour is what should drive your > decision. Is it nice behaviour? Or will it mess things up because rollback > will cause things to get out of sync with the deployments? > > > So the conceptual "we can make changes to how juju.conf is written and have it roll forward" sound nice, but in actuality we're quite used to not doing that, and the problem of "I upgrade Juju, and bootstrapped a new Controller, and then reverted my Juju version, and can no longer access the old controller", actually puts versioned config into the bad column. But snaps have "common" to handle that sort of case, where each version of a single snap series can use the same data store. What I haven't heard a specific answer for is, I want to run "juju-wallyworld" or "juju-menno" or official "juju" and have them all see the same controllers and accounts. And honestly, if I was running firefox-custom-build I really don't think I'd want a different set of bookmarks. John =:-> -- Juju-dev mailing list Juju-dev@lists.ubuntu.com Modify settings or unsubscribe at: https://lists.ubuntu.com/mailman/listinfo/juju-dev
Re: Juju and snappy implementation spike - feedback please
On 9 August 2016 at 19:08, Ian Boothwrote: > I personally like the idea that the snap could use a juju-home interface to > allow access to the standard ~/.local/share/juju directory; thus allowing > a snap > and regular Juju to be used interchangeably (at least initially). This will > allow thw use case "hey, try my juju snap and you can use your existing > settings" But, isn't it verboten for snaps to access dot directories in > user > home in any way, regardless of what any interface says? We could provide an > import tool to copy from ~/.local/share/juju to ~/snap/blah... > > But in the other case, using a personal snap and sharing settings with the > official Juju snap - do we know what the official snappy story is around > this > scenario? I can't imagine this is the first time it's come up? > The big difference to me is that $SNAP_USER_DATA will roll back if the snap is rolled back. I'm not sure what happens if the snap is removed and reinstalled. Given end users should no longer need to be messing around with the dotfiles, I think the rollback behaviour is what should drive your decision. Is it nice behaviour? Or will it mess things up because rollback will cause things to get out of sync with the deployments? -- Stuart Bishop -- Juju-dev mailing list Juju-dev@lists.ubuntu.com Modify settings or unsubscribe at: https://lists.ubuntu.com/mailman/listinfo/juju-dev
Re: Juju and snappy implementation spike - feedback please
I personally like the idea that the snap could use a juju-home interface to allow access to the standard ~/.local/share/juju directory; thus allowing a snap and regular Juju to be used interchangeably (at least initially). This will allow thw use case "hey, try my juju snap and you can use your existing settings" But, isn't it verboten for snaps to access dot directories in user home in any way, regardless of what any interface says? We could provide an import tool to copy from ~/.local/share/juju to ~/snap/blah... But in the other case, using a personal snap and sharing settings with the official Juju snap - do we know what the official snappy story is around this scenario? I can't imagine this is the first time it's come up? On 09/08/16 17:27, John Meinel wrote: > On Aug 9, 2016 1:06 AM, "Nicholas Skaggs"> wrote: >> >> >> >> On Mon, Aug 8, 2016 at 11:49 AM, John Meinel > wrote: >>> >>> If we are installing in '--devmode' don't we have access to the > unfiltered $HOME directory if we look for it? And we could ask for an > interface which is to JUJU_HOME that would give us access to just > $HOME/.local/share/juju >>> >>> >>> We could then copy that information into the normal 'home' directory of > the snap. That might give a better experience than having to import your > credentials anytime you want to just evaluate a dev build of juju. >> >> I agree this gets more difficult with the idea of sharing builds -- as > you say, you have to re-add your credentials for each new developer.In > regards to your thoughts on --devmode, it does give you greater access, but > some things are still constrained. The HOME interface doesn't allow access > to dot files or folders by default. So it's not useful in this instance. If > juju were to change where it stores it's configuration files (aka, not in a > dotfolder) this technically becomes not a problem as the current home > interface would allow this. > > Sure. That's why I mention "We're in dev mode now, and can ask for a > JUJU_HOME interface vs the existing HOME one." We can also just ask for a > "give me the root filesystem" interface that doesn't get connected by > default. > > I think not being able to publish your version of a snap and have it work > with the "standard" version of a snap is going to be a general issue for > anyone using snaps for development. So maybe it's a general snap property > that can give you access to a "named" common directory. > > John > =:-> > >>> >>> >>> AIUI, the 'common' folder for a snap is only for different versions of > the exact same snap, which means that if I publish 'juju-jameinel' it won't > be able to share anything with 'juju-wallyworld' nor the official 'juju', > so there isn't any reason to use it. >>> >>> I don't know exactly how snap content interfaces work, but it might be > interesting to be able to share the JUJU_HOME between snaps (like they > intend to be able to share a "pictures" or "music" directory). >>> >>> If we *don't* share them, then we won't easily be able to try a new Juju > on an existing controller. (If I just want you to see how the new 'juju > status' is formatted, you'd rather run it on a controller that has a bunch > of stuff running.) >> >> It's worth mentioning / filing a bug about our needs with the snapcraft > folks to see what options might exist. I've started conversations a few > weeks ago and solved or got good bugs in on other juju issues. I think they > understand the application config limitations / issues, so we can push for > a resolution. > -- Juju-dev mailing list Juju-dev@lists.ubuntu.com Modify settings or unsubscribe at: https://lists.ubuntu.com/mailman/listinfo/juju-dev
Re: Let's talk retries
On Tue, Aug 9, 2016 at 10:17 AM, roger peppewrote: > BTW I'm not saying that a timer queue is never the correct answer. In some > circumstances, it can be the exactly the right thing to use. > Yeah -- the context here is that katco has been looking at the provisioner, and I think a timer queue is sensible there. Firing off goroutines for every machine is not necessarily bad by any means, but it *is* somewhat harder to get right in every circumstance (e.g. what happens when a machine is removed while that other goroutine is working on provisioning it?). There are certainly many places where a single retryable op *is* the only important thing and there's no call for a control loop. I'm not suggesting we should always use a queue; but I'd tend to encourage it whenever we have a component dealing with many similar retryable tasks. (I definitely should be using it in dependency.Engine, for example.) Cheers William -- Juju-dev mailing list Juju-dev@lists.ubuntu.com Modify settings or unsubscribe at: https://lists.ubuntu.com/mailman/listinfo/juju-dev
Re: RFC: proposed changes to debug-log API call
Sounds like a good idea. Can we get some form of versioning like we do with Facades so that we can do changes in the future? AIUI we could put it in the "Accept" header, in the URL or in a Query parameter. Please coordinate with Ian who has been designing the REST API for CMR so that we use the same mechanism. John =:-> On Aug 9, 2016 12:43 PM, "Tim Penhey"wrote: > Hi folks, > > This is regarding the streaming /api/:model-uuid/log api server endpoint. > > Currently this method sends an initial possible error, then just turns on > a tap of bytes of preformatted log messages. > > What I propose is to change the streaming protocol from just text to a > stream of structured log messages. > > This initial change will have no impact at all on the CLI, but allows for > future changes like: > - showing logs in local time instead of UTC > - hiding the filenames by default > - not showing the date by default > - colors > > Is anyone currently using the streaming text aspects of the API right now? > > How much warning would you like? Ideally this change needs to happen > before we freeze the API for backwards compatibility. > > Cheers, > Tim > > -- > Juju-dev mailing list > Juju-dev@lists.ubuntu.com > Modify settings or unsubscribe at: https://lists.ubuntu.com/mailm > an/listinfo/juju-dev > -- Juju-dev mailing list Juju-dev@lists.ubuntu.com Modify settings or unsubscribe at: https://lists.ubuntu.com/mailman/listinfo/juju-dev
Re: RFC: proposed changes to debug-log API call
On Tue, 9 Aug 2016 at 09:43 Tim Penheywrote: > Is anyone currently using the streaming text aspects of the API right now? > Not us. -- Juju-dev mailing list Juju-dev@lists.ubuntu.com Modify settings or unsubscribe at: https://lists.ubuntu.com/mailman/listinfo/juju-dev
RFC: proposed changes to debug-log API call
Hi folks, This is regarding the streaming /api/:model-uuid/log api server endpoint. Currently this method sends an initial possible error, then just turns on a tap of bytes of preformatted log messages. What I propose is to change the streaming protocol from just text to a stream of structured log messages. This initial change will have no impact at all on the CLI, but allows for future changes like: - showing logs in local time instead of UTC - hiding the filenames by default - not showing the date by default - colors Is anyone currently using the streaming text aspects of the API right now? How much warning would you like? Ideally this change needs to happen before we freeze the API for backwards compatibility. Cheers, Tim -- Juju-dev mailing list Juju-dev@lists.ubuntu.com Modify settings or unsubscribe at: https://lists.ubuntu.com/mailman/listinfo/juju-dev
Re: Juju and snappy implementation spike - feedback please
On Aug 9, 2016 1:06 AM, "Nicholas Skaggs"wrote: > > > > On Mon, Aug 8, 2016 at 11:49 AM, John Meinel wrote: >> >> If we are installing in '--devmode' don't we have access to the unfiltered $HOME directory if we look for it? And we could ask for an interface which is to JUJU_HOME that would give us access to just $HOME/.local/share/juju >> >> >> We could then copy that information into the normal 'home' directory of the snap. That might give a better experience than having to import your credentials anytime you want to just evaluate a dev build of juju. > > I agree this gets more difficult with the idea of sharing builds -- as you say, you have to re-add your credentials for each new developer.In regards to your thoughts on --devmode, it does give you greater access, but some things are still constrained. The HOME interface doesn't allow access to dot files or folders by default. So it's not useful in this instance. If juju were to change where it stores it's configuration files (aka, not in a dotfolder) this technically becomes not a problem as the current home interface would allow this. Sure. That's why I mention "We're in dev mode now, and can ask for a JUJU_HOME interface vs the existing HOME one." We can also just ask for a "give me the root filesystem" interface that doesn't get connected by default. I think not being able to publish your version of a snap and have it work with the "standard" version of a snap is going to be a general issue for anyone using snaps for development. So maybe it's a general snap property that can give you access to a "named" common directory. John =:-> >> >> >> AIUI, the 'common' folder for a snap is only for different versions of the exact same snap, which means that if I publish 'juju-jameinel' it won't be able to share anything with 'juju-wallyworld' nor the official 'juju', so there isn't any reason to use it. >> >> I don't know exactly how snap content interfaces work, but it might be interesting to be able to share the JUJU_HOME between snaps (like they intend to be able to share a "pictures" or "music" directory). >> >> If we *don't* share them, then we won't easily be able to try a new Juju on an existing controller. (If I just want you to see how the new 'juju status' is formatted, you'd rather run it on a controller that has a bunch of stuff running.) > > It's worth mentioning / filing a bug about our needs with the snapcraft folks to see what options might exist. I've started conversations a few weeks ago and solved or got good bugs in on other juju issues. I think they understand the application config limitations / issues, so we can push for a resolution. -- Juju-dev mailing list Juju-dev@lists.ubuntu.com Modify settings or unsubscribe at: https://lists.ubuntu.com/mailman/listinfo/juju-dev
Re: Let's talk retries
On 9 August 2016 at 07:28, roger peppewrote: > On 9 August 2016 at 01:22, Katherine Cox-Buday > wrote: >> 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) > > There's a fourth way that you haven't mentioned, which fits > somewhere in between 1 and 2 I think (it was the first > explicit general retry code in Juju AFAIK), which is > utils.Attempt. > > I'd suggest that the way it's used matches the pattern > you'd like to write quite well: > > for attempt := strategy.State(); attempt.Next(); { Oops; s/State/Start/ of course. > Do something > } > > AFAIR this pattern was arrived at after some discussion between myself > and Gustavo. At the time I was thinking of some kind of > callback-based scheme like the others schemes you mention, but > I think that leaving the caller in control and the loop explicit > is actually nicer - the logic is clear to the reader and more > composable with other primitives (it is also a good match > for the pattern you propose). > > How about making AttemptStrategy a little more flexible? > It was always my intention to admit other kinds of strategy - if > a BackoffFunc field were added to AttemptStrategy and > a Stop argument added to AttemptStrategy.Start it would > become as general as retry.Call, while keeping the nice > usage pattern and general straightforwardness. > > cheers, > rog. > >> 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? > > I'm not keen on the channel-based approach because if you want to > terminate early, you need to shut down the goroutine - it's just > one more resource to clean up. Also, the goroutine that's sending > the events is one step removed from what's going on in the loop - the > delay is calculated one step before the current iteration is complete. > > cheers, > rog. > >> >> Also, is it on the roadmap to address the inconsitant retries throughout >> Juju? I almost
Re: Let's talk retries
On 9 August 2016 at 01:22, Katherine Cox-Budaywrote: > 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) There's a fourth way that you haven't mentioned, which fits somewhere in between 1 and 2 I think (it was the first explicit general retry code in Juju AFAIK), which is utils.Attempt. I'd suggest that the way it's used matches the pattern you'd like to write quite well: for attempt := strategy.State(); attempt.Next(); { Do something } AFAIR this pattern was arrived at after some discussion between myself and Gustavo. At the time I was thinking of some kind of callback-based scheme like the others schemes you mention, but I think that leaving the caller in control and the loop explicit is actually nicer - the logic is clear to the reader and more composable with other primitives (it is also a good match for the pattern you propose). How about making AttemptStrategy a little more flexible? It was always my intention to admit other kinds of strategy - if a BackoffFunc field were added to AttemptStrategy and a Stop argument added to AttemptStrategy.Start it would become as general as retry.Call, while keeping the nice usage pattern and general straightforwardness. cheers, rog. > 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? I'm not keen on the channel-based approach because if you want to terminate early, you need to shut down the goroutine - it's just one more resource to clean up. Also, the goroutine that's sending the events is one step removed from what's going on in the loop - the delay is calculated one step before the current iteration is complete. cheers, rog. > > 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