Files
weave/router/connection_maker.go
T
Matthias Radestock 6409a8647c simplify connection logic slightly
There is no need to remove entries from cm.targets that are in
ourConnectedTargets. None of these targets will be in validTargets
(since addTarget filters anything in ourConnectedTargets), so they
will be removed a few lines further down anyway...

Unless the target is in the 'attempting' state. But that is ok, and in
fact desirable. Any such targets indicate connection attempts that are
in progress, which is something we display in `weave status` and want
to be accurate. Eventually the connection attempt will either succeed
or fail, and the cm.targets entry will be removed on the next run of
connectToTargets.
2015-04-12 12:11:27 +01:00

214 lines
5.6 KiB
Go

package router
import (
"bytes"
"fmt"
"log"
"math/rand"
"net"
"time"
)
const (
InitialInterval = 5 * time.Second
MaxInterval = 10 * time.Minute
)
type ConnectionMaker struct {
ourself *LocalPeer
peers *Peers
normalisePeerAddr func(string) string
targets map[string]*Target
cmdLineAddress map[string]struct{}
actionChan chan<- ConnectionMakerAction
}
// Information about an address where we may find a peer
type Target struct {
attempting bool // are we currently attempting to connect there?
tryAfter time.Time // next time to try this address
tryInterval time.Duration // backoff time on next failure
}
type ConnectionMakerAction func() bool
func NewConnectionMaker(ourself *LocalPeer, peers *Peers, normalisePeerAddr func(string) string) *ConnectionMaker {
return &ConnectionMaker{
ourself: ourself,
peers: peers,
normalisePeerAddr: normalisePeerAddr,
cmdLineAddress: make(map[string]struct{}),
targets: make(map[string]*Target)}
}
func (cm *ConnectionMaker) Start() {
actionChan := make(chan ConnectionMakerAction, ChannelSize)
cm.actionChan = actionChan
go cm.queryLoop(actionChan)
}
func (cm *ConnectionMaker) InitiateConnection(address string) {
cm.actionChan <- func() bool {
cm.cmdLineAddress[cm.normalisePeerAddr(address)] = void
if target, found := cm.targets[address]; found {
target.tryAfter, target.tryInterval = tryImmediately()
}
return true
}
}
func (cm *ConnectionMaker) ForgetConnection(address string) {
cm.actionChan <- func() bool {
delete(cm.cmdLineAddress, cm.normalisePeerAddr(address))
return false
}
}
func (cm *ConnectionMaker) ConnectionTerminated(address string) {
cm.actionChan <- func() bool {
if target, found := cm.targets[address]; found {
target.attempting = false
target.tryAfter, target.tryInterval = tryAfter(target.tryInterval)
}
return true
}
}
func (cm *ConnectionMaker) Refresh() {
cm.actionChan <- func() bool { return true }
}
func (cm *ConnectionMaker) String() string {
resultChan := make(chan string, 0)
cm.actionChan <- func() bool {
var buf bytes.Buffer
for address, target := range cm.targets {
var fmtStr string
if target.attempting {
fmtStr = "%s (trying since %v)\n"
} else {
fmtStr = "%s (next try at %v)\n"
}
fmt.Fprintf(&buf, fmtStr, address, target.tryAfter)
}
resultChan <- buf.String()
return false
}
return <-resultChan
}
func (cm *ConnectionMaker) queryLoop(actionChan <-chan ConnectionMakerAction) {
timer := time.NewTimer(MaxDuration)
run := func() { timer.Reset(cm.checkStateAndAttemptConnections()) }
for {
select {
case action := <-actionChan:
if action() {
run()
}
case <-timer.C:
run()
}
}
}
func (cm *ConnectionMaker) checkStateAndAttemptConnections() time.Duration {
validTarget := make(map[string]struct{})
// copy the set of things we are connected to, so we can access them without locking
ourConnectedPeers := make(PeerNameSet)
ourConnectedTargets := make(map[string]struct{})
for conn := range cm.ourself.Connections() {
ourConnectedPeers[conn.Remote().Name] = void
ourConnectedTargets[conn.RemoteTCPAddr()] = void
}
addTarget := func(address string) {
if _, connected := ourConnectedTargets[address]; !connected {
validTarget[address] = void
cm.addTarget(address)
}
}
// Add command-line targets that are not connected
for address := range cm.cmdLineAddress {
addTarget(address)
}
// Add targets for peers that someone else is connected to, but we
// aren't
cm.peers.ForEach(func(peer *Peer) {
for conn := range peer.Connections() {
otherPeer := conn.Remote().Name
if otherPeer == cm.ourself.Name {
continue
}
if _, connected := ourConnectedPeers[otherPeer]; connected {
continue
}
address := conn.RemoteTCPAddr()
// try both portnumber of connection and standard port. Don't use remote side of inbound connection.
if conn.Outbound() {
addTarget(address)
}
if host, _, err := net.SplitHostPort(address); err == nil {
addTarget(cm.normalisePeerAddr(host))
}
}
})
return cm.connectToTargets(validTarget)
}
func (cm *ConnectionMaker) addTarget(address string) {
if _, found := cm.targets[address]; !found {
target := &Target{}
target.tryAfter, target.tryInterval = tryImmediately()
cm.targets[address] = target
}
}
func (cm *ConnectionMaker) connectToTargets(validTarget map[string]struct{}) time.Duration {
now := time.Now() // make sure we catch items just added
after := MaxDuration
for address, target := range cm.targets {
if target.attempting {
continue
}
if _, valid := validTarget[address]; !valid {
delete(cm.targets, address)
continue
}
switch duration := target.tryAfter.Sub(now); {
case duration <= 0:
target.attempting = true
_, isCmdLineAddress := cm.cmdLineAddress[address]
go cm.attemptConnection(address, isCmdLineAddress)
case duration < after:
after = duration
}
}
return after
}
func (cm *ConnectionMaker) attemptConnection(address string, acceptNewPeer bool) {
log.Printf("->[%s] attempting connection\n", address)
if err := cm.ourself.CreateConnection(address, acceptNewPeer); err != nil {
log.Printf("->[%s] error during connection attempt: %v\n", address, err)
cm.ConnectionTerminated(address)
}
}
func tryImmediately() (time.Time, time.Duration) {
interval := time.Duration(rand.Int63n(int64(InitialInterval)))
return time.Now(), interval
}
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
}