1
0
Fork 0
mirror of https://github.com/diamondburned/arikawa.git synced 2024-11-16 20:03:38 +00:00
arikawa/gateway/pacemaker.go

117 lines
2.1 KiB
Go
Raw Normal View History

2020-01-15 04:43:34 +00:00
package gateway
import (
"sync"
2020-01-29 03:54:22 +00:00
"sync/atomic"
2020-01-15 04:43:34 +00:00
"time"
"github.com/pkg/errors"
)
var ErrDead = errors.New("no heartbeat replied")
2020-01-29 03:54:22 +00:00
// Time is a UnixNano timestamp.
type Time = int64
2020-01-15 04:43:34 +00:00
type Pacemaker struct {
// Heartrate is the received duration between heartbeats.
Heartrate time.Duration
2020-01-29 03:54:22 +00:00
// Time in nanoseconds, guarded by atomic read/writes.
SentBeat Time
EchoBeat Time
2020-01-15 04:43:34 +00:00
// Any callback that returns an error will stop the pacer.
Pace func() error
// Event
OnDead func() error
stop chan struct{}
death chan error
2020-01-15 04:43:34 +00:00
}
func (p *Pacemaker) Echo() {
// Swap our received heartbeats
2020-01-19 06:22:03 +00:00
// p.LastBeat[0], p.LastBeat[1] = time.Now(), p.LastBeat[0]
2020-01-29 03:54:22 +00:00
atomic.StoreInt64(&p.EchoBeat, time.Now().UnixNano())
2020-01-15 04:43:34 +00:00
}
// Dead, if true, will have Pace return an ErrDead.
func (p *Pacemaker) Dead() bool {
2020-01-19 06:22:03 +00:00
/* Deprecated
2020-01-15 04:43:34 +00:00
if p.LastBeat[0].IsZero() || p.LastBeat[1].IsZero() {
return false
}
return p.LastBeat[0].Sub(p.LastBeat[1]) > p.Heartrate*2
2020-01-19 06:22:03 +00:00
*/
2020-01-29 03:54:22 +00:00
var (
echo = atomic.LoadInt64(&p.EchoBeat)
sent = atomic.LoadInt64(&p.SentBeat)
)
if echo == 0 || sent == 0 {
2020-01-19 06:22:03 +00:00
return false
}
2020-01-29 03:54:22 +00:00
return sent-echo > int64(p.Heartrate)*2
2020-01-15 04:43:34 +00:00
}
func (p *Pacemaker) Stop() {
2020-01-18 07:40:44 +00:00
if p.stop != nil {
p.stop <- struct{}{}
WSDebug("(*Pacemaker).stop was sent a stop signal.")
} else {
WSDebug("(*Pacemaker).stop is nil, skipping.")
2020-01-18 07:40:44 +00:00
}
2020-01-15 04:43:34 +00:00
}
func (p *Pacemaker) start() error {
2020-01-16 03:28:21 +00:00
tick := time.NewTicker(p.Heartrate)
defer tick.Stop()
2020-01-15 04:43:34 +00:00
2020-01-16 03:28:21 +00:00
// Echo at least once
p.Echo()
for {
2020-03-02 00:39:40 +00:00
if err := p.Pace(); err != nil {
return err
}
// Paced, save:
atomic.StoreInt64(&p.SentBeat, time.Now().UnixNano())
if p.Dead() {
return ErrDead
}
2020-01-15 04:43:34 +00:00
select {
case <-p.stop:
2020-01-15 04:43:34 +00:00
return nil
2020-01-18 07:40:44 +00:00
2020-01-15 04:43:34 +00:00
case <-tick.C:
}
}
}
// StartAsync starts the pacemaker asynchronously. The WaitGroup is optional.
func (p *Pacemaker) StartAsync(wg *sync.WaitGroup) (death chan error) {
p.death = make(chan error)
p.stop = make(chan struct{})
2020-01-16 03:28:21 +00:00
wg.Add(1)
2020-01-15 04:43:34 +00:00
go func() {
p.death <- p.start()
// Debug.
WSDebug("Pacemaker returned.")
// Mark the stop channel as nil, so later Close() calls won't block forever.
p.stop = nil
// Mark the pacemaker loop as done.
wg.Done()
2020-01-15 04:43:34 +00:00
}()
2020-01-16 03:28:21 +00:00
return p.death
2020-01-15 04:43:34 +00:00
}