Skip to content

Instantly share code, notes, and snippets.

@gobwas
Created January 24, 2017 15:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gobwas/ac2d0623efe6004a7717e9fd709d3a0c to your computer and use it in GitHub Desktop.
Save gobwas/ac2d0623efe6004a7717e9fd709d3a0c to your computer and use it in GitHub Desktop.
Timer race condition prove.
package main
import (
"flag"
"fmt"
"sync"
"time"
)
var withLock = flag.Bool("lock", false, "use lock on resets")
var withStop = flag.Bool("stop", false, "use stop and channel drain on resets")
func main() {
flag.Parse()
fmt.Printf("lock: %v; stop: %v\n", *withLock, *withStop)
var (
resetGoroutines sync.WaitGroup
timers sync.WaitGroup
delays sync.WaitGroup
)
var (
startReset = make(chan struct{})
stopReset = make(chan struct{})
)
const (
timersCount = 4
resetGoroutinesCount = 32
resetTo = time.Millisecond * 600
resetInterval = resetTo / 6
)
timers.Add(timersCount)
delays.Add(timersCount)
for i := 0; i < timersCount; i++ {
go func(x int) {
tm := time.AfterFunc(time.Hour, func() {
fmt.Printf("timer #%d fired\n", x)
delays.Done()
})
timers.Done()
var mu sync.Mutex
resetGoroutines.Add(resetGoroutinesCount)
for i := 0; i < resetGoroutinesCount; i++ {
go func(i int) {
defer resetGoroutines.Done()
<-startReset
tick := time.NewTicker(resetInterval)
for {
select {
case <-tick.C:
if *withLock {
mu.Lock()
}
if *withStop {
if !tm.Stop() {
select {
case <-tm.C:
default:
}
}
}
tm.Reset(resetTo)
if *withLock {
mu.Unlock()
}
case <-stopReset:
return
}
}
}(i)
}
}(i)
}
timers.Wait()
fmt.Printf("initialized %d timers\n", timersCount)
close(startReset)
fmt.Printf("will sleep for %s x 4 (delay which is set during resets)\n", resetTo)
time.Sleep(resetTo * 4)
close(stopReset)
fmt.Println("done, waiting...")
resetGoroutines.Wait()
fmt.Println("reset goroutines are done")
delays.Wait()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment