Skip to content

Instantly share code, notes, and snippets.

@kmwenja
Created October 31, 2017 09:17
Show Gist options
  • Save kmwenja/44ffa791a4b3faaa3bd6684c2bb98c9c to your computer and use it in GitHub Desktop.
Save kmwenja/44ffa791a4b3faaa3bd6684c2bb98c9c to your computer and use it in GitHub Desktop.
Life Token for Goroutines
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.
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
}
}
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