Created
October 31, 2017 09:17
-
-
Save kmwenja/44ffa791a4b3faaa3bd6684c2bb98c9c to your computer and use it in GitHub Desktop.
Life Token for Goroutines
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
A tactic for managing long lived goroutines by handing them a "life" token. | |
The goroutines can check if the token "is dead" and quit while signalling that they have quit. | |
See `main.go` for an example usage. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"sync" | |
"time" | |
) | |
type Life struct { | |
shutdown chan struct{} | |
wg *sync.WaitGroup | |
errChan chan error | |
} | |
func NewLife() *Life { | |
return &Life{ | |
shutdown: make(chan struct{}), | |
wg: new(sync.WaitGroup), | |
errChan: make(chan error), | |
} | |
} | |
func (l *Life) Beget() *Life { | |
l.wg.Add(1) | |
return l | |
} | |
func (l *Life) Fail(err error) { | |
l.errChan <- err | |
} | |
func (l *Life) Failed() chan error { | |
return l.errChan | |
} | |
func (l *Life) Kill() { | |
close(l.shutdown) | |
} | |
func (l *Life) IsDead() chan struct{} { | |
return l.shutdown | |
} | |
func (l *Life) Died() { | |
l.wg.Done() | |
} | |
func (l *Life) Wait(t time.Duration) bool { | |
done := make(chan struct{}) | |
go func() { | |
l.wg.Wait() | |
done <- struct{}{} | |
}() | |
select { | |
case <-done: | |
return true | |
case <-time.After(t): | |
return false | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
"time" | |
) | |
func main() { | |
l := NewLife() | |
fmt.Println("Starting") | |
go service(l.Beget(), 1*time.Second) | |
go service(l.Beget(), 5*time.Second) | |
go service(l.Beget(), 10*time.Second) | |
// go rogueService(l.Beget(), 2*time.Second) | |
// go evilService(l.Beget(), 2*time.Second) | |
// go failingService(l.Beget()) | |
fmt.Println("Giving it a while") | |
select { | |
case <-l.Failed(): | |
fmt.Println("Someone failed! Kill everyone.") | |
case <-time.After(30 * time.Second): | |
fmt.Println("Some good work was done.") | |
} | |
fmt.Println("Killing all life") | |
l.Kill() | |
fmt.Println("Waiting for the end") | |
ok := l.Wait(5 * time.Second) | |
if ok { | |
fmt.Println("Everyone turned off safe") | |
} else { | |
fmt.Println("Someone didn't turn off, dying anyway") | |
} | |
} | |
func service(l *Life, t time.Duration) { | |
defer l.Died() | |
ticker := time.NewTicker(t) | |
for { | |
select { | |
case <-ticker.C: | |
fmt.Printf("%s has lapsed\n", t) | |
case <-l.IsDead(): | |
return | |
} | |
} | |
} | |
func rogueService(l *Life, t time.Duration) { | |
// defer l.Died() | |
ticker := time.NewTicker(t) | |
for { | |
select { | |
case <-ticker.C: | |
fmt.Printf("%s has lapsed\n", t) | |
case <-l.IsDead(): | |
return | |
} | |
} | |
} | |
func evilService(l *Life, t time.Duration) { | |
defer l.Died() | |
ticker := time.NewTicker(t) | |
for { | |
select { | |
case <-ticker.C: | |
fmt.Printf("%s has lapsed\n", t) | |
case <-l.IsDead(): | |
// return | |
fmt.Println("Lols, not dying.") | |
<-time.After(t) | |
} | |
} | |
} | |
func failingService(l *Life) { | |
defer l.Died() | |
l.Fail(fmt.Errorf("Couldn't cut it")) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment