maddy/timewheel.go
fox.cpp 5b861c053c Address PR feedback
/state_directory/configuration_block_name =>
$MADDYSTATE/<configuration_block_name>

Typo: tmeporarly => temporarly

TimeWheel.Stop => TimeWheel.Close

Fix leaked os.File for messages on error (extract logic into separate
function and use defer).

Fix retry time scaling, it is now just q.initialRetryTime with
TriesCount = 1, as it should be.

Fix type assertion in meta-data serialization, IPAddr => TCPAddr.
2019-05-05 19:29:12 +03:00

115 lines
2.3 KiB
Go

package maddy
import (
"container/list"
"sync"
"time"
)
type TimeSlot struct {
Time time.Time
Value interface{}
}
type TimeWheel struct {
slots *list.List
slotsLock sync.Mutex
updateNotify chan time.Time
stopNotify chan struct{}
dispatch chan TimeSlot
}
func NewTimeWheel() *TimeWheel {
tw := &TimeWheel{
slots: list.New(),
stopNotify: make(chan struct{}),
updateNotify: make(chan time.Time),
dispatch: make(chan TimeSlot, 10),
}
go tw.tick()
return tw
}
func (tw *TimeWheel) Add(target time.Time, value interface{}) {
if value == nil {
panic("can't insert nil objects into TimeWheel queue")
}
tw.slotsLock.Lock()
tw.slots.PushBack(TimeSlot{Time: target, Value: value})
tw.slotsLock.Unlock()
tw.updateNotify <- target
}
func (tw *TimeWheel) Close() {
tw.stopNotify <- struct{}{}
<-tw.stopNotify
close(tw.updateNotify)
close(tw.dispatch)
}
func (tw *TimeWheel) tick() {
for {
now := time.Now()
// Look for list element closest to now.
tw.slotsLock.Lock()
var closestSlot TimeSlot
var closestEl *list.Element
for e := tw.slots.Front(); e != nil; e = e.Next() {
slot := e.Value.(TimeSlot)
if slot.Time.Sub(now) < closestSlot.Time.Sub(now) || closestSlot.Value == nil {
closestSlot = slot
closestEl = e
}
}
tw.slotsLock.Unlock()
// Only this goroutine removes elements from TimeWheel so we can be safe using closestSlot.
// Queue is empty. Just wait until update.
if closestEl == nil {
select {
case <-tw.updateNotify:
continue
case <-tw.stopNotify:
tw.stopNotify <- struct{}{}
return
}
}
timer := time.NewTimer(closestSlot.Time.Sub(now))
for {
select {
case <-timer.C:
tw.slotsLock.Lock()
tw.slots.Remove(closestEl)
tw.slotsLock.Unlock()
tw.dispatch <- closestSlot
// break inside of select exits select, not for loop
goto breakinnerloop
case newTarget := <-tw.updateNotify:
// Avoid unnecessary restarts if new target is not going to affect our
// current wait time.
if closestSlot.Time.Sub(now) <= newTarget.Sub(now) {
continue
}
timer.Stop()
// Recalculate new slot time.
case <-tw.stopNotify:
tw.stopNotify <- struct{}{}
return
}
}
breakinnerloop:
}
}
func (tw *TimeWheel) Dispatch() <-chan TimeSlot {
return tw.dispatch
}