143 lines
3.8 KiB
Go
143 lines
3.8 KiB
Go
package weave
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"log"
|
|
"math/rand"
|
|
"net"
|
|
"strconv"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
InitialInterval = 5 * time.Second
|
|
MaxInterval = 10 * time.Minute
|
|
MaxAttemptCount = 100
|
|
CMEnsure = iota
|
|
CMStatus = iota
|
|
)
|
|
|
|
func StartConnectionMaker(router *Router) *ConnectionMaker {
|
|
queryChan := make(chan *ConnectionMakerInteraction, ChannelSize)
|
|
state := &ConnectionMaker{
|
|
router: router,
|
|
queryChan: queryChan,
|
|
failedConnections: make(map[PeerName]*FailedConnection)}
|
|
go state.queryLoop(queryChan)
|
|
return state
|
|
}
|
|
|
|
func (cm *ConnectionMaker) EnsureConnection(name PeerName, foundAt string) {
|
|
cm.queryChan <- &ConnectionMakerInteraction{
|
|
Interaction: Interaction{code: CMEnsure},
|
|
name: name,
|
|
foundAt: foundAt}
|
|
}
|
|
|
|
func (cm *ConnectionMaker) String() string {
|
|
resultChan := make(chan interface{}, 0)
|
|
cm.queryChan <- &ConnectionMakerInteraction{
|
|
Interaction: Interaction{code: CMStatus, resultChan: resultChan}}
|
|
result := <-resultChan
|
|
return result.(string)
|
|
}
|
|
|
|
func (cm *ConnectionMaker) queryLoop(queryChan <-chan *ConnectionMakerInteraction) {
|
|
var tick <-chan time.Time
|
|
maybeTick := func() {
|
|
if tick == nil && len(cm.failedConnections) > 0 {
|
|
tick = time.After(5 * time.Second)
|
|
}
|
|
}
|
|
for {
|
|
select {
|
|
case query, ok := <-queryChan:
|
|
if !ok {
|
|
return
|
|
}
|
|
switch {
|
|
case query.code == CMEnsure:
|
|
if query.name != cm.router.Ourself.Name {
|
|
cm.addToFailedConnection(query.name, query.foundAt)
|
|
maybeTick()
|
|
}
|
|
case query.code == CMStatus:
|
|
query.resultChan <- cm.status()
|
|
default:
|
|
log.Fatal("Unexpected connection maker query:", query)
|
|
}
|
|
case now := <-tick:
|
|
for name, failedConn := range cm.failedConnections {
|
|
if now.After(failedConn.tryAfter) {
|
|
if _, found := cm.router.Ourself.ConnectionTo(name); found {
|
|
delete(cm.failedConnections, name)
|
|
continue
|
|
} else if failedConn.attemptCount == MaxAttemptCount {
|
|
delete(cm.failedConnections, name)
|
|
continue
|
|
}
|
|
after, interval := tryAfter(failedConn.tryInterval)
|
|
failedConn.tryInterval = interval
|
|
failedConn.tryAfter = after
|
|
failedConn.attemptCount += 1
|
|
for target := range failedConn.foundAt {
|
|
go cm.attemptConnection(target, name)
|
|
}
|
|
}
|
|
}
|
|
tick = nil
|
|
maybeTick()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (cm *ConnectionMaker) addToFailedConnection(name PeerName, foundAt string) {
|
|
failed := cm.failedConnections[name]
|
|
if failed == nil {
|
|
after, interval := tryAfter(InitialInterval)
|
|
failed = &FailedConnection{
|
|
foundAt: make(map[string]bool),
|
|
tryInterval: interval,
|
|
tryAfter: after}
|
|
}
|
|
foundAtHost, foundAtPortStr, err := net.SplitHostPort(foundAt)
|
|
if err == nil {
|
|
// ensure port-less version is there
|
|
failed.foundAt[foundAtHost] = true
|
|
if foundAtPort, err := strconv.Atoi(foundAtPortStr); err == nil && foundAtPort != Port {
|
|
failed.foundAt[foundAt] = true
|
|
}
|
|
} else {
|
|
// can't split it, assume it must not have port on it
|
|
failed.foundAt[foundAt] = true
|
|
}
|
|
cm.failedConnections[name] = failed
|
|
}
|
|
|
|
func (cm *ConnectionMaker) status() string {
|
|
var buf bytes.Buffer
|
|
for name, failedConn := range cm.failedConnections {
|
|
foundAt := make([]string, 0, len(failedConn.foundAt))
|
|
for target := range failedConn.foundAt {
|
|
foundAt = append(foundAt, target)
|
|
}
|
|
buf.WriteString(fmt.Sprintf("%s (%v attempts, next at %v): %v\n", name, failedConn.attemptCount, failedConn.tryAfter, foundAt))
|
|
}
|
|
return buf.String()
|
|
}
|
|
|
|
func (cm *ConnectionMaker) attemptConnection(foundAt string, targetName PeerName) {
|
|
if err := cm.router.Ourself.CreateConnection(foundAt, targetName); err != nil {
|
|
log.Println(err)
|
|
}
|
|
}
|
|
|
|
func tryAfter(interval time.Duration) (time.Time, time.Duration) {
|
|
interval += time.Duration(rand.Int63n(int64(interval)))
|
|
if interval > MaxInterval {
|
|
interval = MaxInterval
|
|
}
|
|
return time.Now().Add(interval), interval
|
|
}
|