checkStateAndAttemptConnections already performs a full scan of all potential targets, from which we can easily determine the nearest future time (or, rather, its duration from now) on which an attempt is due. We also need to kick the ConnectionMaker into action when we receive a topology update containing new connections.
316 lines
7.9 KiB
Go
316 lines
7.9 KiB
Go
package weave
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/gob"
|
|
"fmt"
|
|
"io"
|
|
"sync"
|
|
)
|
|
|
|
type PeerCache struct {
|
|
sync.RWMutex
|
|
table map[PeerName]*Peer
|
|
onGC func(*Peer)
|
|
}
|
|
|
|
func NewPeerCache(onGC func(*Peer)) *PeerCache {
|
|
return &PeerCache{table: make(map[PeerName]*Peer), onGC: onGC}
|
|
}
|
|
|
|
func (cache *PeerCache) FetchWithDefault(peer *Peer) *Peer {
|
|
cache.RLock()
|
|
res, found := cache.fetchAlias(peer)
|
|
cache.RUnlock()
|
|
if found {
|
|
return res
|
|
}
|
|
cache.Lock()
|
|
defer cache.Unlock()
|
|
res, found = cache.fetchAlias(peer)
|
|
if found {
|
|
return res
|
|
}
|
|
cache.table[peer.Name] = peer
|
|
peer.IncrementLocalRefCount()
|
|
return peer
|
|
}
|
|
|
|
func (cache *PeerCache) Fetch(name PeerName) (*Peer, bool) {
|
|
cache.RLock()
|
|
defer cache.RUnlock()
|
|
peer, found := cache.table[name]
|
|
return peer, found // GRRR, why can't I inline this!?
|
|
}
|
|
|
|
func (cache *PeerCache) ForEach(fun func(PeerName, *Peer)) {
|
|
cache.RLock()
|
|
defer cache.RUnlock()
|
|
for name, peer := range cache.table {
|
|
fun(name, peer)
|
|
}
|
|
}
|
|
|
|
func (cache *PeerCache) ApplyUpdate(update []byte, router *Router) ([]byte, error) {
|
|
cache.Lock()
|
|
|
|
newPeers, decodedUpdate, decodedConns, err := cache.decodeUpdate(update, router)
|
|
if err != nil {
|
|
cache.Unlock()
|
|
return nil, err
|
|
}
|
|
|
|
// By this point, we know the update doesn't refer to any peers we
|
|
// have no knowledge of. We can now apply the update. Start by
|
|
// adding in any new peers into the cache.
|
|
for name, newPeer := range newPeers {
|
|
cache.table[name] = newPeer
|
|
}
|
|
|
|
// Now apply the updates
|
|
newUpdate := cache.applyUpdate(decodedUpdate, decodedConns, router)
|
|
|
|
for _, peerRemoved := range cache.garbageCollect(router.Ourself) {
|
|
delete(newUpdate, peerRemoved.Name)
|
|
}
|
|
|
|
// Don't need to hold cache lock any longer
|
|
cache.Unlock()
|
|
|
|
if len(newUpdate) > 0 {
|
|
router.ConnectionMaker.Refresh()
|
|
}
|
|
|
|
return encodePeersMap(newUpdate), nil
|
|
}
|
|
|
|
func (cache *PeerCache) EncodeAllPeers() []byte {
|
|
cache.RLock()
|
|
defer cache.RUnlock()
|
|
return encodePeersMap(cache.table)
|
|
}
|
|
|
|
func EncodePeers(peers ...*Peer) []byte {
|
|
buf := new(bytes.Buffer)
|
|
enc := gob.NewEncoder(buf)
|
|
for _, peer := range peers {
|
|
peer.encodePeer(enc)
|
|
}
|
|
return buf.Bytes()
|
|
}
|
|
|
|
func (cache *PeerCache) GarbageCollect(router *Router) []*Peer {
|
|
cache.Lock()
|
|
defer cache.Unlock()
|
|
return cache.garbageCollect(router.Ourself)
|
|
}
|
|
|
|
func (cache *PeerCache) String() string {
|
|
var buf bytes.Buffer
|
|
cache.ForEach(func(name PeerName, peer *Peer) {
|
|
buf.WriteString(fmt.Sprint(peer, "\n"))
|
|
peer.ForEachConnection(func(remoteName PeerName, conn Connection) {
|
|
buf.WriteString(fmt.Sprintf(" -> %v [%v]\n", remoteName, conn.RemoteTCPAddr()))
|
|
})
|
|
})
|
|
return buf.String()
|
|
}
|
|
|
|
func (cache *PeerCache) fetchAlias(peer *Peer) (*Peer, bool) {
|
|
if existingPeer, found := cache.table[peer.Name]; found {
|
|
if existingPeer.UID == peer.UID {
|
|
existingPeer.IncrementLocalRefCount()
|
|
return existingPeer, true
|
|
} else {
|
|
return nil, true
|
|
}
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
func (cache *PeerCache) garbageCollect(ourself *Peer) []*Peer {
|
|
removed := []*Peer{}
|
|
for name, peer := range cache.table {
|
|
found, _ := ourself.HasPathTo(peer, false)
|
|
if !found && !peer.IsLocallyReferenced() {
|
|
cache.onGC(peer)
|
|
delete(cache.table, name)
|
|
ourself.Router.Macs.Delete(peer)
|
|
removed = append(removed, peer)
|
|
}
|
|
}
|
|
return removed
|
|
}
|
|
|
|
func encodePeersMap(peers map[PeerName]*Peer) []byte {
|
|
buf := new(bytes.Buffer)
|
|
enc := gob.NewEncoder(buf)
|
|
for _, peer := range peers {
|
|
peer.encodePeer(enc)
|
|
}
|
|
return buf.Bytes()
|
|
}
|
|
|
|
func (cache *PeerCache) decodeUpdate(update []byte, router *Router) (newPeers map[PeerName]*Peer, decodedUpdate []*Peer, decodedConns [][]byte, err error) {
|
|
newPeers = make(map[PeerName]*Peer)
|
|
decodedUpdate = []*Peer{}
|
|
decodedConns = [][]byte{}
|
|
|
|
updateBuf := new(bytes.Buffer)
|
|
updateBuf.Write(update)
|
|
decoder := gob.NewDecoder(updateBuf)
|
|
|
|
for {
|
|
nameByte, uid, version, connsBuf, decErr := decodePeerNoConns(decoder)
|
|
if decErr == io.EOF {
|
|
break
|
|
} else if decErr != nil {
|
|
err = decErr
|
|
return
|
|
}
|
|
name := PeerNameFromBin(nameByte)
|
|
newPeer := NewPeer(name, uid, version, router)
|
|
decodedUpdate = append(decodedUpdate, newPeer)
|
|
decodedConns = append(decodedConns, connsBuf)
|
|
existingPeer, found := cache.table[name]
|
|
if !found {
|
|
newPeers[name] = newPeer
|
|
} else if existingPeer.UID != newPeer.UID {
|
|
err = NameCollisionError{Name: newPeer.Name}
|
|
return
|
|
}
|
|
}
|
|
|
|
unknownPeers := false
|
|
for _, connsBuf := range decodedConns {
|
|
connsIterator(connsBuf, func(remoteNameByte []byte, _ string) {
|
|
remoteName := PeerNameFromBin(remoteNameByte)
|
|
if _, found := newPeers[remoteName]; found {
|
|
return
|
|
}
|
|
if _, found := cache.table[remoteName]; found {
|
|
return
|
|
}
|
|
// Update refers to a peer which we have no knowledge
|
|
// of. Thus we can't apply the update. Abort.
|
|
unknownPeers = true
|
|
})
|
|
}
|
|
if unknownPeers {
|
|
err = UnknownPeersError{}
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
func (cache *PeerCache) applyUpdate(decodedUpdate []*Peer, decodedConns [][]byte, router *Router) map[PeerName]*Peer {
|
|
newUpdate := make(map[PeerName]*Peer)
|
|
for idx, newPeer := range decodedUpdate {
|
|
connsBuf := decodedConns[idx]
|
|
name := newPeer.Name
|
|
// guaranteed to find peer in the cache.table
|
|
peer := cache.table[name]
|
|
if peer != newPeer {
|
|
if peer.Version() > newPeer.Version() {
|
|
// we know more about this one than the update. If
|
|
// peer is ourself, this is slightly racey (further
|
|
// changes could occur to ourself in the mean
|
|
// time). But it doesn't matter as we know that we're
|
|
// already > newPeer.Version() and that's not going to
|
|
// change.
|
|
newUpdate[name] = peer
|
|
continue
|
|
} else if peer == router.Ourself {
|
|
// nobody but us updates us
|
|
continue
|
|
} else if peer.Version() == newPeer.Version() {
|
|
// implication is that connections are equal too
|
|
continue
|
|
}
|
|
}
|
|
// If we're here, either it was a new peer, or the update has
|
|
// more info about the peer than we do. Either case, we need
|
|
// to set version and conns and include the updated peer in
|
|
// the outgoing update.
|
|
|
|
// Can peer have been updated by anyone else in the mean time?
|
|
// No - we know that peer is not ourself, so the only prospect
|
|
// for an update would be someone else calling
|
|
// router.Peers.ApplyUpdate. But ApplyUpdate takes the Lock on
|
|
// the peers cache, so there can be no race here.
|
|
conns := readConnsMap(peer, connsBuf, cache.table)
|
|
peer.SetVersionAndConnections(newPeer.Version(), conns)
|
|
newUpdate[name] = peer
|
|
}
|
|
return newUpdate
|
|
}
|
|
|
|
func (peer *Peer) encodePeer(enc *gob.Encoder) {
|
|
peer.RLock()
|
|
defer peer.RUnlock()
|
|
|
|
checkFatal(enc.Encode(peer.NameByte))
|
|
checkFatal(enc.Encode(peer.UID))
|
|
checkFatal(enc.Encode(peer.version))
|
|
|
|
connsBuf := new(bytes.Buffer)
|
|
connsEnc := gob.NewEncoder(connsBuf)
|
|
for _, conn := range peer.connections {
|
|
// DANGER holding rlock on peer, going to take rlock on conn
|
|
if !conn.Established() {
|
|
continue
|
|
}
|
|
checkFatal(connsEnc.Encode(conn.Remote().NameByte))
|
|
checkFatal(connsEnc.Encode(conn.RemoteTCPAddr()))
|
|
}
|
|
checkFatal(enc.Encode(connsBuf.Bytes()))
|
|
}
|
|
|
|
func decodePeerNoConns(dec *gob.Decoder) (nameByte []byte, uid uint64, version uint64, conns []byte, err error) {
|
|
err = dec.Decode(&nameByte)
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = dec.Decode(&uid)
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = dec.Decode(&version)
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = dec.Decode(&conns)
|
|
if err == io.EOF {
|
|
err = nil
|
|
}
|
|
return
|
|
}
|
|
|
|
func connsIterator(input []byte, fun func([]byte, string)) {
|
|
buf := new(bytes.Buffer)
|
|
buf.Write(input)
|
|
dec := gob.NewDecoder(buf)
|
|
for {
|
|
var nameByte []byte
|
|
err := dec.Decode(&nameByte)
|
|
if err == io.EOF {
|
|
return
|
|
}
|
|
checkFatal(err)
|
|
var foundAt string
|
|
checkFatal(dec.Decode(&foundAt))
|
|
fun(nameByte, string(foundAt))
|
|
}
|
|
}
|
|
|
|
func readConnsMap(peer *Peer, buf []byte, table map[PeerName]*Peer) map[PeerName]Connection {
|
|
conns := make(map[PeerName]Connection)
|
|
connsIterator(buf, func(nameByte []byte, remoteTCPAddr string) {
|
|
name := PeerNameFromBin(nameByte)
|
|
remotePeer := table[name]
|
|
conn := NewRemoteConnection(peer, remotePeer, remoteTCPAddr)
|
|
conns[name] = conn
|
|
})
|
|
return conns
|
|
}
|