You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

114 lines
2.3 KiB
Go

package manager
import (
"errors"
"math"
"math/rand"
"sync"
"sync/atomic"
"time"
)
// max allowable connection attempts is 255
const MaxConnAttempts = 0xFF
// basic manager for starting/stopping checks plus built in heartbeat for downtime detection
type Connection struct {
Attempts float64 // float for pow
MaxAttempts int // max allowed
sync.Mutex
}
type Manager struct {
*Connection // embedded for timeout stuff
Active int32 // atomic checks
}
// errors
var (
ErrInvalidMaxConn = errors.New("invalid max connection attempts")
ErrManagerInactive = errors.New("manager inactive")
ErrManagerActive = errors.New("manager active")
ErrMaxAttemptsExceeded = errors.New("max connection attempts exceeded")
)
func New(maxConn int) (*Manager, error) {
if maxConn < 0 || maxConn > MaxConnAttempts {
return &Manager{}, ErrInvalidMaxConn
}
c := &Connection{MaxAttempts: maxConn}
m := &Manager{
Connection: c,
}
return m, nil
}
func (m *Manager) Start() error {
if atomic.CompareAndSwapInt32(&m.Active, 0, 1) {
m.ResetConnections()
return nil
}
return ErrManagerActive
}
func (m *Manager) Stop() error {
if atomic.CompareAndSwapInt32(&m.Active, 1, 0) {
return nil
}
return ErrManagerInactive
}
func (m *Manager) IsActive() int {
return int(atomic.LoadInt32(&m.Active))
}
// Heartbeat tracker
func (m *Manager) HeartBeat(ping chan struct{}, hb, interval int, scale time.Duration) {
// pings channel every (HB + randInterval) * time.Duration
// can be used anywhere a heartbeat is needed
// closes channel on exit
if interval > 0 {
rand.Seed(time.Now().UnixNano())
}
for atomic.LoadInt32(&m.Active) > 0 {
// atomoic read may cause memory leak, can revisit
ping <- struct{}{} // no mem
sleep := time.Duration(hb-interval) * scale
if interval > 0 {
sleep += time.Duration(rand.Intn(2*interval)) * scale
}
time.Sleep(sleep)
}
// exited, close chan
close(ping)
}
// connection timeout generator
func (c *Connection) Timeout() (time.Duration, error) {
// exponential backoff
c.Lock()
defer c.Unlock()
if int(c.Attempts) < c.MaxAttempts {
to := time.Duration(50*math.Pow(2, c.Attempts)) * time.Millisecond
c.Attempts += 1
return to, nil
}
return 0, ErrMaxAttemptsExceeded
}
func (c *Connection) ResetConnections() {
c.Lock()
defer c.Unlock()
c.Attempts = 0
}