mapper/batcher: replace connected map with per-node disconnectedAt
The Batcher's connected field (*xsync.Map[types.NodeID, *time.Time])
encoded three states via pointer semantics:
- nil value: node is connected
- non-nil time: node disconnected at that timestamp
- key missing: node was never seen
This was error-prone (nil meaning 'connected' inverts Go idioms),
redundant with b.nodes + hasActiveConnections(), and required keeping
two parallel maps in sync. It also contained a bug in RemoveNode where
new(time.Now()) was used instead of &now, producing a zero time.
Replace the separate connected map with a disconnectedAt field on
multiChannelNodeConn (atomic.Pointer[time.Time]), tracked directly
on the object that already manages the node's connections.
Changes:
- Add disconnectedAt field and helpers (markConnected, markDisconnected,
isConnected, offlineDuration) to multiChannelNodeConn
- Remove the connected field from Batcher
- Simplify IsConnected from two map lookups to one
- Simplify ConnectedMap and Debug from two-map iteration to one
- Rewrite cleanupOfflineNodes to scan b.nodes directly
- Remove the markDisconnectedIfNoConns helper
- Update all tests and benchmarks
Fixes #3141
This commit is contained in:
parent
60317064fd
commit
87b8507ac9
6 changed files with 148 additions and 191 deletions
|
|
@ -150,12 +150,11 @@ func BenchmarkUpdateSentPeers(b *testing.B) {
|
|||
// helper, it doesn't register cleanup and suppresses logging.
|
||||
func benchBatcher(nodeCount, bufferSize int) (*Batcher, map[types.NodeID]chan *tailcfg.MapResponse) {
|
||||
b := &Batcher{
|
||||
tick: time.NewTicker(1 * time.Hour), // never fires during bench
|
||||
workers: 4,
|
||||
workCh: make(chan work, 4*200),
|
||||
nodes: xsync.NewMap[types.NodeID, *multiChannelNodeConn](),
|
||||
connected: xsync.NewMap[types.NodeID, *time.Time](),
|
||||
done: make(chan struct{}),
|
||||
tick: time.NewTicker(1 * time.Hour), // never fires during bench
|
||||
workers: 4,
|
||||
workCh: make(chan work, 4*200),
|
||||
nodes: xsync.NewMap[types.NodeID, *multiChannelNodeConn](),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
|
||||
channels := make(map[types.NodeID]chan *tailcfg.MapResponse, nodeCount)
|
||||
|
|
@ -172,7 +171,6 @@ func benchBatcher(nodeCount, bufferSize int) (*Batcher, map[types.NodeID]chan *t
|
|||
entry.lastUsed.Store(time.Now().Unix())
|
||||
mc.addConnection(entry)
|
||||
b.nodes.Store(id, mc)
|
||||
b.connected.Store(id, nil)
|
||||
channels[id] = ch
|
||||
}
|
||||
|
||||
|
|
@ -471,7 +469,7 @@ func BenchmarkConnectedMap(b *testing.B) {
|
|||
|
||||
for _, nodeCount := range []int{10, 100, 1000} {
|
||||
b.Run(fmt.Sprintf("%dnodes", nodeCount), func(b *testing.B) {
|
||||
batcher, _ := benchBatcher(nodeCount, 1)
|
||||
batcher, channels := benchBatcher(nodeCount, 1)
|
||||
|
||||
defer func() {
|
||||
close(batcher.done)
|
||||
|
|
@ -481,8 +479,11 @@ func BenchmarkConnectedMap(b *testing.B) {
|
|||
// Disconnect 10% of nodes for a realistic mix
|
||||
for i := 1; i <= nodeCount; i++ {
|
||||
if i%10 == 0 {
|
||||
now := time.Now()
|
||||
batcher.connected.Store(types.NodeID(i), &now) //nolint:gosec // benchmark
|
||||
id := types.NodeID(i) //nolint:gosec // benchmark
|
||||
if mc, ok := batcher.nodes.Load(id); ok {
|
||||
mc.removeConnectionByChannel(channels[id])
|
||||
mc.markDisconnected()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue