p2p/discover: restore nextTimeout update in UDPv4 resetTimeout loop

The refactor from `for el := plist.Front(); ...; el = el.Next()` to the
new `iterList` iterator in #34743 silently dropped two things needed by
resetTimeout:

  1. `nextTimeout = el.Value.(*replyMatcher)` at the top of the loop.
     This assignment is what gives `nextTimeout` its documented meaning
     ("head of plist when timeout was last reset"), and what makes the
     early-return optimization at the top of resetTimeout work. Without
     it, nextTimeout is only ever written to nil, so
     `nextTimeout == plist.Front().Value` is always false and the
     optimization is dead.

  2. `nextTimeout.errc <- errClockWarp` in the clock-warp branch now
     reads a stale or nil pointer. Prior to the refactor, the inner
     assignment kept nextTimeout pointing at the current matcher so its
     errc was the right channel to receive the errClockWarp signal.
     After the refactor, on first entry into the clock-warp branch
     nextTimeout is nil, which panics the UDPv4 loop goroutine with a
     nil pointer deref and takes discv4 down.

Re-assign `nextTimeout = p` at the head of the loop (restoring the
documented invariant) and send the clock-warp error on `p.errc` rather
than the now-stale `nextTimeout.errc`.

The clock-warp branch triggers only when the system clock jumps
backward after a deadline is assigned (deadline - time.Now() >=
2*respTimeout, i.e. at least ~500ms backward jump), which is why this
regression slipped past CI - it is not exercised by any existing unit
test, and writing one would require plumbing a clock through the loop.
This commit is contained in:
rayoo 2026-05-05 15:53:58 +08:00
parent efd6cdcff1
commit c944ff3d54

View file

@ -447,6 +447,7 @@ func (t *UDPv4) loop() {
// Start the timer so it fires when the next pending reply has expired. // Start the timer so it fires when the next pending reply has expired.
now := time.Now() now := time.Now()
for p, el := range iterList[*replyMatcher](plist) { for p, el := range iterList[*replyMatcher](plist) {
nextTimeout = p
if dist := p.deadline.Sub(now); dist < 2*respTimeout { if dist := p.deadline.Sub(now); dist < 2*respTimeout {
timeout.Reset(dist) timeout.Reset(dist)
return return
@ -454,7 +455,7 @@ func (t *UDPv4) loop() {
// Remove pending replies whose deadline is too far in the // Remove pending replies whose deadline is too far in the
// future. These can occur if the system clock jumped // future. These can occur if the system clock jumped
// backwards after the deadline was assigned. // backwards after the deadline was assigned.
nextTimeout.errc <- errClockWarp p.errc <- errClockWarp
plist.Remove(el) plist.Remove(el)
} }
nextTimeout = nil nextTimeout = nil