mirror of
https://github.com/juanfont/headscale.git
synced 2026-09-15 13:02:02 +09:00
poll: do not cancel ephemeral GC until Connect succeeds
With node.ephemeral.inactivity_timeout set, ephemeral nodes are usually deleted after they go offline, but under reconnect churn some departed nodes stayed in the node list as disconnected indefinitely until removed manually or until Headscale restarted. Ephemeral cleanup is timer-based via EphemeralGarbageCollector, not a periodic LastSeen scan. serveLongPoll cancelled any pending GC timer at the very start of a long-poll attempt and only rescheduled on a clean disconnect after Connect. If a reconnect cancelled the timer and then failed before Connect (for example an UpdateNodeFromMapRequest error), the deferred cleanup saw connectGen == 0 and returned without Schedule. The node remained offline with no deletion timer and no reconciler to recover it. Cancel the ephemeral GC timer only after a successful Connect, so a failed reconnect leaves an already-armed inactivity timer intact. Successful reconnects still cancel GC once the node is online, and a later disconnect reschedules as before. Add TestFailedReconnectDoesNotCancelEphemeralGC to lock in the ordering, plus IsScheduled and DeleteNodeFromStoreForTest helpers for the test. Fixes #3382 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
committed by
Kristoffer Dalby
parent
dc3c0bc587
commit
cfd845cb53
+7
-8
@@ -96,12 +96,6 @@ func (m *mapSession) stopFromBatcher() {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mapSession) beforeServeLongPoll() {
|
||||
if m.node.IsEphemeral() {
|
||||
m.h.ephemeralGC.Cancel(m.node.ID)
|
||||
}
|
||||
}
|
||||
|
||||
// afterServeLongPoll is called when a long-polling session ends and the node
|
||||
// is disconnected.
|
||||
func (m *mapSession) afterServeLongPoll() {
|
||||
@@ -144,8 +138,6 @@ func (m *mapSession) serve() {
|
||||
//
|
||||
//nolint:gocyclo
|
||||
func (m *mapSession) serveLongPoll() {
|
||||
m.beforeServeLongPoll()
|
||||
|
||||
m.log.Trace().Caller().Msg("long poll session started")
|
||||
|
||||
// connectGen is set by [state.State.Connect] below and captured by the deferred cleanup closure.
|
||||
@@ -248,6 +240,13 @@ func (m *mapSession) serveLongPoll() {
|
||||
|
||||
connectChanges, connectGen = m.h.state.Connect(m.node.ID)
|
||||
|
||||
// Cancel ephemeral GC only after Connect succeeds. Cancelling at the start
|
||||
// of serveLongPoll left departed nodes without a deletion timer when a
|
||||
// reconnect attempt failed before Connect (issue #3382).
|
||||
if m.node.IsEphemeral() {
|
||||
m.h.ephemeralGC.Cancel(m.node.ID)
|
||||
}
|
||||
|
||||
m.log.Info().Caller().Str(zf.Chan, fmt.Sprintf("%p", m.ch)).Msg("node has connected")
|
||||
|
||||
// TODO(kradalby): Redo the comments here
|
||||
|
||||
Reference in New Issue
Block a user