mirror of
https://github.com/moby/moby.git
synced 2026-08-12 12:15:08 +00:00
NetworkDB's Watch() facility is problematic to use in practice. The stream of events begins when the watch is started, so the watch cannot be used to process table entries that existed beforehand. Either option to process existing table entries is racy: walking the table before starting the watch leaves a race window where events could be missed, and walking the table after starting the watch leaves a race window where created/updated entries could be processed twice. Modify Watch() to initialize the channel with synthetic CREATE events for all existing entries owned by remote nodes before hooking it up to the live event stream. This way watchers observe an equivalent sequence of events irrespective of whether the watch was started before or after entries from remote nodes are added to the database. Remove the bespoke and racy synthetic event replay logic for driver watches from the libnetwork agent. Signed-off-by: Cory Snider <csnider@mirantis.com>
148 lines
3.4 KiB
Go
148 lines
3.4 KiB
Go
package networkdb
|
|
|
|
import (
|
|
"net"
|
|
"strings"
|
|
|
|
"github.com/docker/go-events"
|
|
)
|
|
|
|
type opType uint8
|
|
|
|
const (
|
|
opCreate opType = 1 + iota
|
|
opUpdate
|
|
opDelete
|
|
)
|
|
|
|
type event struct {
|
|
Table string
|
|
NetworkID string
|
|
Key string
|
|
Value []byte
|
|
}
|
|
|
|
// NodeTable represents table event for node join and leave
|
|
const NodeTable = "NodeTable"
|
|
|
|
// NodeAddr represents the value carried for node event in NodeTable
|
|
type NodeAddr struct {
|
|
Addr net.IP
|
|
}
|
|
|
|
// CreateEvent generates a table entry create event to the watchers
|
|
type CreateEvent event
|
|
|
|
// UpdateEvent generates a table entry update event to the watchers
|
|
type UpdateEvent event
|
|
|
|
// DeleteEvent generates a table entry delete event to the watchers
|
|
type DeleteEvent event
|
|
|
|
// Watch creates a watcher with filters for a particular table or
|
|
// network or any combination of the tuple. If any of the
|
|
// filter is an empty string it acts as a wildcard for that
|
|
// field. Watch returns a channel of events, where the events will be
|
|
// sent. The watch channel is initialized with synthetic create events for all
|
|
// the existing table entries not owned by this node which match the filters.
|
|
func (nDB *NetworkDB) Watch(tname, nid string) (*events.Channel, func()) {
|
|
var matcher events.Matcher
|
|
|
|
if tname != "" || nid != "" {
|
|
matcher = events.MatcherFunc(func(ev events.Event) bool {
|
|
var evt event
|
|
switch ev := ev.(type) {
|
|
case CreateEvent:
|
|
evt = event(ev)
|
|
case UpdateEvent:
|
|
evt = event(ev)
|
|
case DeleteEvent:
|
|
evt = event(ev)
|
|
}
|
|
|
|
if tname != "" && evt.Table != tname {
|
|
return false
|
|
}
|
|
|
|
if nid != "" && evt.NetworkID != nid {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
})
|
|
}
|
|
|
|
ch := events.NewChannel(0)
|
|
sink := events.Sink(events.NewQueue(ch))
|
|
|
|
if matcher != nil {
|
|
sink = events.NewFilter(sink, matcher)
|
|
}
|
|
|
|
// Synthesize events for all the existing table entries not owned by
|
|
// this node so that the watcher receives all state without racing with
|
|
// any concurrent mutations to the table.
|
|
nDB.RLock()
|
|
defer nDB.RUnlock()
|
|
if tname == "" {
|
|
var prefix []byte
|
|
if nid != "" {
|
|
prefix = []byte("/" + nid + "/")
|
|
} else {
|
|
prefix = []byte("/")
|
|
}
|
|
nDB.indexes[byNetwork].Root().WalkPrefix(prefix, func(path []byte, v *entry) bool {
|
|
if !v.deleting && v.node != nDB.config.NodeID {
|
|
tuple := strings.SplitN(string(path[1:]), "/", 3)
|
|
if len(tuple) == 3 {
|
|
entryNid, entryTname, key := tuple[0], tuple[1], tuple[2]
|
|
sink.Write(makeEvent(opCreate, entryTname, entryNid, key, v.value))
|
|
}
|
|
}
|
|
return false
|
|
})
|
|
} else {
|
|
prefix := []byte("/" + tname + "/")
|
|
if nid != "" {
|
|
prefix = append(prefix, []byte(nid+"/")...)
|
|
}
|
|
nDB.indexes[byTable].Root().WalkPrefix(prefix, func(path []byte, v *entry) bool {
|
|
if !v.deleting && v.node != nDB.config.NodeID {
|
|
tuple := strings.SplitN(string(path[1:]), "/", 3)
|
|
if len(tuple) == 3 {
|
|
entryTname, entryNid, key := tuple[0], tuple[1], tuple[2]
|
|
sink.Write(makeEvent(opCreate, entryTname, entryNid, key, v.value))
|
|
}
|
|
}
|
|
return false
|
|
})
|
|
}
|
|
|
|
nDB.broadcaster.Add(sink)
|
|
return ch, func() {
|
|
nDB.broadcaster.Remove(sink)
|
|
ch.Close()
|
|
sink.Close()
|
|
}
|
|
}
|
|
|
|
func makeEvent(op opType, tname, nid, key string, value []byte) events.Event {
|
|
ev := event{
|
|
Table: tname,
|
|
NetworkID: nid,
|
|
Key: key,
|
|
Value: value,
|
|
}
|
|
|
|
switch op {
|
|
case opCreate:
|
|
return CreateEvent(ev)
|
|
case opUpdate:
|
|
return UpdateEvent(ev)
|
|
case opDelete:
|
|
return DeleteEvent(ev)
|
|
}
|
|
|
|
return nil
|
|
}
|