I am trying to create a Circuit breaker pattern, I want to execute a command exec.Command and if it fails, retry in X defined amount of time, for testing purposes, I am doing something like this for testing time.AfterFunc
:
package main
import (
"fmt"
"time"
)
func myFunc() error {
for i := 1; i < 10; i++ {
fmt.Printf("i = %+v
", i)
if i%3 == 0 {
return fmt.Errorf("error")
}
}
return nil
}
func main() {
run := make(chan struct{}, 1)
run <- struct{}{}
for {
select {
case <-run:
err := myFunc()
if err != nil {
time.AfterFunc(3*time.Second, func() {
run <- struct{}{}
})
}
default:
}
}
}
time.AfterFunc
works for the above code, but not for the example below, I had to replace it with a sleep
in order to achieve the expected results:
package main
import (
"fmt"
"os/exec"
"time"
)
func Exec(done chan<- error) error {
cmd := exec.Command("./start")
if err := cmd.Start(); err != nil {
return err
}
go func() {
done <- cmd.Wait()
}()
return nil
}
func main() {
var (
run = make(chan struct{}, 1)
done = make(chan error, 1)
)
Exec(done)
for {
select {
case <-run:
err := Exec(done)
if err != nil {
fmt.Println(err)
// time.AfterFunc(3*time.Second, func() {
time.Sleep(3 * time.Second)
run <- struct{}{}
}
default:
select {
case err := <-done:
fmt.Println(err)
run <- struct{}{}
}
}
}
}
The content of ./sleep
:
#!/bin/sh
sleep 3
And for testing, creating an error, I toggle perms:
chmod -x sleep
chmod +x sleep
Therefore wondering what are the differences between using time.AfterFunc
and time.Sleep
and what could be the best way of implementing this pattern.
Any time you hit the default case, the select ends immediately. In the top example, after you execute AfterFunc
the for loop runs continually until run
has items (after 3 seconds). Busy waits are usually bad. With the sleep
solution, you never have a busy wait, which is good. I'm not sure I fully follow what you are trying to accomplish with the nested selects in the second example though.
Why do you need channels and asynchrony at all? Why not just:
retryCount := 0
for retryCount < 3 {
err := doSomethingScary()
if err == nil{
//success! return results!
} else{
//failure! wait and retry
time.Sleep(time.Second) //time.sleep is a good non-busy wait
}
}
// max tries exceeded. Return error.