mirror of
https://github.com/juanfont/headscale.git
synced 2026-09-17 14:02:07 +09:00
@@ -589,20 +589,13 @@ func (b *Batcher) addToBatch(changes ...change.Change) {
|
||||
// still has it registered. By cleaning up here, we prevent "node not found"
|
||||
// errors when workers try to generate map responses for deleted nodes.
|
||||
//
|
||||
// Safety: [change.Change.PeersRemoved] is ONLY populated when nodes are actually
|
||||
// deleted from the system (via [change.NodeRemoved] in [state.State.DeleteNode]).
|
||||
// Policy changes that affect peer visibility do NOT use this field - they set
|
||||
// RequiresRuntimePeerComputation=true and compute removed peers at runtime,
|
||||
// putting them in [tailcfg.MapResponse.PeersRemoved] (a different struct).
|
||||
// Therefore, this cleanup only removes nodes that are truly being deleted,
|
||||
// not nodes that are still connected but have lost visibility of certain peers.
|
||||
// This loop now also terminates the node's map sessions, so a future
|
||||
// [change.Change.PeersRemoved] producer that is not a deletion would kill a
|
||||
// live node's long poll, not merely evict its batcher entry.
|
||||
// [change.Change.DeletedNodes] is an explicit internal lifecycle signal;
|
||||
// [change.Change.PeersRemoved] remains only the protocol delta sent to clients
|
||||
// when peers disappear from their view.
|
||||
//
|
||||
// See: https://github.com/juanfont/headscale/issues/2924
|
||||
for _, ch := range changes {
|
||||
for _, removedID := range ch.PeersRemoved {
|
||||
for _, removedID := range ch.DeletedNodes {
|
||||
if nc, existed := b.nodes.LoadAndDelete(removedID); existed {
|
||||
b.totalNodes.Add(-1)
|
||||
|
||||
|
||||
@@ -275,7 +275,7 @@ func TestAddToBatch_FullUpdateOverrides(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// TestAddToBatch_NodeRemovalCleanup verifies that PeersRemoved in a change
|
||||
// TestAddToBatch_NodeRemovalCleanup verifies that a permanent node deletion
|
||||
// cleans up the node from the batcher's internal state.
|
||||
func TestAddToBatch_NodeRemovalCleanup(t *testing.T) {
|
||||
lb := setupLightweightBatcher(t, 5, 10)
|
||||
@@ -287,11 +287,7 @@ func TestAddToBatch_NodeRemovalCleanup(t *testing.T) {
|
||||
_, exists := lb.b.nodes.Load(removedNode)
|
||||
require.True(t, exists, "node 3 should exist before removal")
|
||||
|
||||
// Send a change that includes node 3 in PeersRemoved
|
||||
lb.b.addToBatch(change.Change{
|
||||
Reason: "node deleted",
|
||||
PeersRemoved: []types.NodeID{removedNode},
|
||||
})
|
||||
lb.b.addToBatch(change.NodeRemoved(removedNode))
|
||||
|
||||
// Node should be removed from the nodes map
|
||||
_, exists = lb.b.nodes.Load(removedNode)
|
||||
|
||||
@@ -455,6 +455,32 @@ func TestAddToBatch_NodeRemovedStopsSession(t *testing.T) {
|
||||
assert.Equal(t, int64(0), lb.b.totalNodes.Load())
|
||||
}
|
||||
|
||||
func TestAddToBatch_PeersRemovedKeepsSession(t *testing.T) {
|
||||
lb := setupLightweightBatcher(t, 1, 1)
|
||||
defer lb.cleanup()
|
||||
|
||||
mc, ok := lb.b.nodes.Load(1)
|
||||
require.True(t, ok)
|
||||
|
||||
stopped := make(chan struct{})
|
||||
|
||||
mc.mutex.Lock()
|
||||
mc.connections[0].stop = func() { close(stopped) }
|
||||
mc.mutex.Unlock()
|
||||
|
||||
lb.b.AddWork(change.PeersRemoved(1))
|
||||
|
||||
select {
|
||||
case <-stopped:
|
||||
t.Fatal("a peer visibility delta must not stop the peer's own map session")
|
||||
default:
|
||||
}
|
||||
|
||||
_, stillTracked := lb.b.nodes.Load(1)
|
||||
assert.True(t, stillTracked, "a visible peer removal must remain tracked by the batcher")
|
||||
assert.Equal(t, int64(1), lb.b.totalNodes.Load())
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// multiChannelNodeConn connection management Tests
|
||||
// ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user