forked from TrueCloudLab/frostfs-qos
There is a possible call-chain `scheduleRequest()` -> `runAt()` -> `scheduleRequest()`, so second `scheduleRequest()` may be locked on mutext held by first `scheduleRequest()`. Signed-off-by: Dmitrii Stepanov <d.stepanov@yadro.com>
84 lines
1.2 KiB
Go
84 lines
1.2 KiB
Go
package scheduling
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type clock interface {
|
|
now() float64
|
|
runAt(ts float64, f func())
|
|
close()
|
|
}
|
|
|
|
type scheduleInfo struct {
|
|
ts float64
|
|
f func()
|
|
}
|
|
|
|
type systemClock struct {
|
|
since time.Time
|
|
schedule chan scheduleInfo
|
|
wg sync.WaitGroup
|
|
}
|
|
|
|
func newSystemClock() *systemClock {
|
|
c := &systemClock{
|
|
since: time.Now(),
|
|
schedule: make(chan scheduleInfo),
|
|
}
|
|
c.start()
|
|
return c
|
|
}
|
|
|
|
func (c *systemClock) now() float64 {
|
|
return time.Since(c.since).Seconds()
|
|
}
|
|
|
|
func (c *systemClock) runAt(ts float64, f func()) {
|
|
select {
|
|
case c.schedule <- scheduleInfo{ts: ts, f: f}:
|
|
default: // timer fired, scheduleRequest will call runAt again
|
|
}
|
|
}
|
|
|
|
func (c *systemClock) close() {
|
|
close(c.schedule)
|
|
c.wg.Wait()
|
|
}
|
|
|
|
func (c *systemClock) start() {
|
|
c.wg.Add(1)
|
|
go func() {
|
|
defer c.wg.Done()
|
|
t := time.NewTimer(0)
|
|
<-t.C
|
|
var f func()
|
|
for {
|
|
select {
|
|
case <-t.C:
|
|
if f != nil {
|
|
f()
|
|
f = nil
|
|
}
|
|
case s, ok := <-c.schedule:
|
|
if !ok {
|
|
return
|
|
}
|
|
var d time.Duration
|
|
now := c.now()
|
|
if now < s.ts {
|
|
d = time.Duration((s.ts - now) * 1e9)
|
|
}
|
|
if !t.Stop() {
|
|
select {
|
|
case <-t.C:
|
|
default:
|
|
}
|
|
}
|
|
t.Reset(d)
|
|
f = s.f
|
|
}
|
|
}
|
|
}()
|
|
}
|