From 1a482cac6aba105cad250fc6e9e627f99a968f55 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 3 Mar 2026 08:07:49 +0100 Subject: [PATCH] p2p/tracker: fix crash in clean when tracker is stopped --- p2p/tracker/tracker.go | 4 ++++ p2p/tracker/tracker_test.go | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/p2p/tracker/tracker.go b/p2p/tracker/tracker.go index 3a9135aa3b..126474231b 100644 --- a/p2p/tracker/tracker.go +++ b/p2p/tracker/tracker.go @@ -138,6 +138,10 @@ func (t *Tracker) clean() { t.lock.Lock() defer t.lock.Unlock() + if t.expire == nil { + return + } + // Expire anything within a certain threshold (might be no items at all if // we raced with the delivery) for t.expire.Len() > 0 { diff --git a/p2p/tracker/tracker_test.go b/p2p/tracker/tracker_test.go index a37a59f70f..95e9629641 100644 --- a/p2p/tracker/tracker_test.go +++ b/p2p/tracker/tracker_test.go @@ -24,6 +24,25 @@ import ( "github.com/ethereum/go-ethereum/p2p" ) +// TestCleanAfterStop verifies that the clean method does not crash when called +// after Stop. This can happen because clean is scheduled via time.AfterFunc and +// may fire after Stop sets t.expire to nil. +func TestCleanAfterStop(t *testing.T) { + cap := p2p.Cap{Name: "test", Version: 1} + timeout := 50 * time.Millisecond + tr := New(cap, "peer1", timeout) + + // Track a request to start the expiration timer. + tr.Track(Request{ID: 1, ReqCode: 0x01, RespCode: 0x02, Size: 1}) + + // Stop the tracker, then wait for the timer to fire. + tr.Stop() + time.Sleep(timeout + 50*time.Millisecond) + + // Also verify that calling clean directly after stop doesn't panic. + tr.clean() +} + // This checks that metrics gauges for pending requests are be decremented when a // Tracker is stopped. func TestMetricsOnStop(t *testing.T) {