From 0ad877fdad9f99b3ad274c59b92b7310e7b00781 Mon Sep 17 00:00:00 2001 From: Michael Fletcher Date: Thu, 16 Apr 2026 15:32:37 +0100 Subject: [PATCH] fix: close head tracker/broadcaster before balance monitor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reorder chain.Close() to stop event sources (headTracker, headBroadcaster) before event consumers (balanceMonitor). Previously, the balance monitor was closed while the head pipeline was still running, allowing headBroadcaster to deliver a last-second OnNewLongestChain that wakes the balance monitor's SleeperTask. When SleeperTask.Stop() then closes chStop, both chStop and chQueue are ready in the select — Go picks randomly, and 50% of the time runs one more Work() that calls BalanceAt on a torn-down mock client, triggering a data race detected by go_core_race_tests. --- pkg/chains/legacyevm/chain.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pkg/chains/legacyevm/chain.go b/pkg/chains/legacyevm/chain.go index a5560681c6..5018285ac6 100644 --- a/pkg/chains/legacyevm/chain.go +++ b/pkg/chains/legacyevm/chain.go @@ -377,20 +377,26 @@ func (c *chain) Close() error { return c.StopOnce("Chain", func() (merr error) { c.logger.Debug("Chain: stopping") + // Stop event sources before consumers to prevent late delivery + // (e.g. headBroadcaster calling balanceMonitor.OnNewLongestChain + // after the balance monitor has stopped, causing a data race). + if c.logPoller != logpoller.LogPollerDisabled { merr = multierr.Append(merr, c.logPoller.Close()) } - if c.balanceMonitor != nil { - c.logger.Debug("Chain: stopping balance monitor") - merr = c.balanceMonitor.Close() - } c.logger.Debug("Chain: stopping logBroadcaster") merr = multierr.Combine(merr, c.logBroadcaster.Close()) c.logger.Debug("Chain: stopping headTracker") merr = multierr.Combine(merr, c.headTracker.Close()) c.logger.Debug("Chain: stopping headBroadcaster") merr = multierr.Combine(merr, c.headBroadcaster.Close()) + + if c.balanceMonitor != nil { + c.logger.Debug("Chain: stopping balance monitor") + merr = multierr.Combine(merr, c.balanceMonitor.Close()) + } + c.logger.Debug("Chain: stopping evmTxm") merr = multierr.Combine(merr, c.txm.Close())