From 7e636553d69ed1ac8f461c38e17cbf19d8d38783 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Tue, 12 Aug 2025 15:59:39 +0200 Subject: [PATCH] fix repeated initialization of outgoingPathManager in Conn.AddPath (#5280) In case of concurrent calls, this may still be called multiple times. Co-authored-by: Marco Munizaga --- connection.go | 26 ++++++++++++++++++-------- path_manager_outgoing.go | 3 +++ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/connection.go b/connection.go index 6ba1e3a0b..710125f0b 100644 --- a/connection.go +++ b/connection.go @@ -2645,16 +2645,26 @@ func (c *Conn) LocalAddr() net.Addr { return c.conn.LocalAddr() } // RemoteAddr returns the remote address of the QUIC connection. func (c *Conn) RemoteAddr() net.Addr { return c.conn.RemoteAddr() } +// getPathManager lazily initializes the Conn's pathManagerOutgoing. +// May create multiple pathManagerOutgoing objects if called concurrently. func (c *Conn) getPathManager() *pathManagerOutgoing { - c.pathManagerOutgoing.CompareAndSwap(nil, - func() *pathManagerOutgoing { // this function is only called if a swap is performed - return newPathManagerOutgoing( - c.connIDManager.GetConnIDForPath, - c.connIDManager.RetireConnIDForPath, - c.scheduleSending, - ) - }(), + old := c.pathManagerOutgoing.Load() + if old != nil { + // Path manager is already initialized + return old + } + + // Initialize the path manager + new := newPathManagerOutgoing( + c.connIDManager.GetConnIDForPath, + c.connIDManager.RetireConnIDForPath, + c.scheduleSending, ) + if c.pathManagerOutgoing.CompareAndSwap(old, new) { + return new + } + + // Swap failed. A concurrent writer wrote first, use their value. return c.pathManagerOutgoing.Load() } diff --git a/path_manager_outgoing.go b/path_manager_outgoing.go index e8c065ffb..78f68eeab 100644 --- a/path_manager_outgoing.go +++ b/path_manager_outgoing.go @@ -128,6 +128,9 @@ type pathManagerOutgoing struct { pathToSwitchTo *pathOutgoing } +// newPathManagerOutgoing creates a new pathManagerOutgoing object. This +// function must be side-effect free as it may be called multiple times for a +// single connection. func newPathManagerOutgoing( getConnID func(pathID) (_ protocol.ConnectionID, ok bool), retireConnID func(pathID),