mirror of
https://github.com/foxcpp/maddy.git
synced 2025-04-04 21:47:40 +03:00
/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.
115 lines
2.3 KiB
Go
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
|
|
}
|