cwtch/app/plugins/contactRetry.go

145 lines
3.5 KiB
Go
Raw Normal View History

package plugins
import (
"cwtch.im/cwtch/event"
"cwtch.im/cwtch/protocol/connections"
"git.openprivacy.ca/openprivacy/log"
"sync"
"time"
)
const tickTime = 10 * time.Second
const maxBakoff int = 32 // 320 seconds or ~5 min
type connectionType int
const (
peerConn connectionType = iota
serverConn
)
type contact struct {
id string
state connections.ConnectionState
ctype connectionType
ticks int
backoff int
}
type contactRetry struct {
bus event.Manager
queue event.Queue
networkUp bool
running bool
breakChan chan bool
onion string
connections sync.Map //[string]*contact
}
// NewConnectionRetry returns a Plugin that when started will retry connecting to contacts with a backoff timing
func NewConnectionRetry(bus event.Manager, onion string) Plugin {
cr := &contactRetry{bus: bus, queue: event.NewQueue(), breakChan: make(chan bool), connections: sync.Map{}, networkUp: false, onion: onion}
return cr
}
func (cr *contactRetry) Start() {
if !cr.running {
go cr.run()
} else {
log.Errorf("Attempted to start Contact Retry plugin twice for %v", cr.onion)
}
}
func (cr *contactRetry) run() {
cr.running = true
cr.bus.Subscribe(event.PeerStateChange, cr.queue)
cr.bus.Subscribe(event.ACNStatus, cr.queue)
cr.bus.Subscribe(event.ServerStateChange, cr.queue)
for {
select {
case e := <-cr.queue.OutChan():
switch e.EventType {
case event.PeerStateChange:
state := connections.ConnectionStateToType()[e.Data[event.ConnectionState]]
peer := e.Data[event.RemotePeer]
cr.handleEvent(peer, state, peerConn)
case event.ServerStateChange:
state := connections.ConnectionStateToType()[e.Data[event.ConnectionState]]
server := e.Data[event.GroupServer]
cr.handleEvent(server, state, serverConn)
case event.ACNStatus:
prog := e.Data[event.Progreess]
if prog == "100" && cr.networkUp == false {
cr.networkUp = true
cr.connections.Range(func(k, v interface{}) bool {
p := v.(*contact)
p.ticks = 0
p.backoff = 1
2020-09-21 21:26:28 +00:00
if p.ctype == peerConn {
cr.bus.Publish(event.NewEvent(event.RetryPeerRequest, map[event.Field]string{event.RemotePeer: p.id}))
}
return true
})
} else if prog != "100" {
cr.networkUp = false
}
}
case <-time.After(tickTime):
cr.connections.Range(func(k, v interface{}) bool {
p := v.(*contact)
if p.state == connections.DISCONNECTED {
p.ticks++
if p.ticks == p.backoff {
p.ticks = 0
if cr.networkUp {
if p.ctype == peerConn {
cr.bus.Publish(event.NewEvent(event.RetryPeerRequest, map[event.Field]string{event.RemotePeer: p.id}))
}
}
}
}
return true
})
case <-cr.breakChan:
cr.running = false
return
}
}
}
func (cr *contactRetry) handleEvent(id string, state connections.ConnectionState, ctype connectionType) {
if _, exists := cr.connections.Load(id); !exists {
p := &contact{id: id, state: connections.DISCONNECTED, backoff: 1, ticks: 0, ctype: ctype}
cr.connections.Store(id, p)
return
}
pinf, _ := cr.connections.Load(id)
p := pinf.(*contact)
if state == connections.DISCONNECTED || state == connections.FAILED || state == connections.KILLED {
p.state = connections.DISCONNECTED
if p.backoff < maxBakoff {
p.backoff *= 2
}
p.ticks = 0
} else if state == connections.CONNECTING || state == connections.CONNECTED {
p.state = state
} else if state == connections.AUTHENTICATED {
p.state = state
p.backoff = 1
}
}
func (cr *contactRetry) Shutdown() {
cr.breakChan <- true
}