Repository: quic-go/quic-go
Pull request #5117 (2025-05-04 10:07:49)

delete closed connections after 3 PTOs
In most cases, this should be earlier than the current timeout value of 5s.

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/quic-go/quic-go/pull/5117?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=quic-go) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 84.03%. Comparing base [(`be709c1`)](https://app.codecov.io/gh/quic-go/quic-go/commit/be709c10fddf69420f90c05972b01d643a0e48c1?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=quic-go) to head [(`a6b2c0f`)](https://app.codecov.io/gh/quic-go/quic-go/commit/a6b2c0f17c0526a49273d5a4076d8ad29470bb31?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=quic-go).

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #5117      +/-   ##
==========================================
- Coverage   84.17%   84.03%   -0.14%     
==========================================
  Files         153      150       -3     
  Lines       16992    16646     -346     
==========================================
- Hits        14302    13987     -315     
+ Misses       2161     2144      -17     
+ Partials      529      515      -14     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/quic-go/quic-go/pull/5117?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=quic-go).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=quic-go).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>

Patch:
diff --git a/conn_id_generator.go b/conn_id_generator.go
index 0b0b90fd..394d5b8a 100644
--- a/conn_id_generator.go
+++ b/conn_id_generator.go
@@ -13,7 +13,7 @@ import (
 type connRunnerCallbacks struct {
 	AddConnectionID    func(protocol.ConnectionID)
 	RemoveConnectionID func(protocol.ConnectionID)
-	ReplaceWithClosed  func([]protocol.ConnectionID, []byte)
+	ReplaceWithClosed  func([]protocol.ConnectionID, []byte, time.Duration)
 }
 
 // The memory address of the Transport is used as the key.
@@ -31,9 +31,9 @@ func (cr connRunners) RemoveConnectionID(id protocol.ConnectionID) {
 	}
 }
 
-func (cr connRunners) ReplaceWithClosed(ids []protocol.ConnectionID, b []byte) {
+func (cr connRunners) ReplaceWithClosed(ids []protocol.ConnectionID, b []byte, expiry time.Duration) {
 	for _, c := range cr {
-		c.ReplaceWithClosed(ids, b)
+		c.ReplaceWithClosed(ids, b, expiry)
 	}
 }
 
@@ -188,7 +188,7 @@ func (m *connIDGenerator) RemoveAll() {
 	}
 }
 
-func (m *connIDGenerator) ReplaceWithClosed(connClose []byte) {
+func (m *connIDGenerator) ReplaceWithClosed(connClose []byte, expiry time.Duration) {
 	connIDs := make([]protocol.ConnectionID, 0, len(m.activeSrcConnIDs)+len(m.connIDsToRetire)+1)
 	if m.initialClientDestConnID != nil {
 		connIDs = append(connIDs, *m.initialClientDestConnID)
@@ -199,7 +199,7 @@ func (m *connIDGenerator) ReplaceWithClosed(connClose []byte) {
 	for _, c := range m.connIDsToRetire {
 		connIDs = append(connIDs, c.connID)
 	}
-	m.connRunners.ReplaceWithClosed(connIDs, connClose)
+	m.connRunners.ReplaceWithClosed(connIDs, connClose, expiry)
 }
 
 func (m *connIDGenerator) AddConnRunner(id *Transport, r connRunnerCallbacks) {
diff --git a/connection.go b/connection.go
index 1165078e..e03e8c94 100644
--- a/connection.go
+++ b/connection.go
@@ -87,7 +87,7 @@ func (p *receivedPacket) Clone() *receivedPacket {
 type connRunner interface {
 	Add(protocol.ConnectionID, packetHandler) bool
 	Remove(protocol.ConnectionID)
-	ReplaceWithClosed([]protocol.ConnectionID, []byte)
+	ReplaceWithClosed([]protocol.ConnectionID, []byte, time.Duration)
 	AddResetToken(protocol.StatelessResetToken, packetHandler)
 	RemoveResetToken(protocol.StatelessResetToken)
 }
@@ -1908,7 +1908,7 @@ func (s *connection) handleCloseError(closeErr *closeError) {
 
 	// If this is a remote close we're done here
 	if isRemoteClose {
-		s.connIDGenerator.ReplaceWithClosed(nil)
+		s.connIDGenerator.ReplaceWithClosed(nil, 3*s.rttStats.PTO(false))
 		return
 	}
 	if closeErr.immediate {
@@ -1925,7 +1925,7 @@ func (s *connection) handleCloseError(closeErr *closeError) {
 	if err != nil {
 		s.logger.Debugf("Error sending CONNECTION_CLOSE: %s", err)
 	}
-	s.connIDGenerator.ReplaceWithClosed(connClosePacket)
+	s.connIDGenerator.ReplaceWithClosed(connClosePacket, 3*s.rttStats.PTO(false))
 }
 
 func (s *connection) dropEncryptionLevel(encLevel protocol.EncryptionLevel, now time.Time) error {
diff --git a/internal/protocol/params.go b/internal/protocol/params.go
index f0aa3ad9..fe86317f 100644
--- a/internal/protocol/params.go
+++ b/internal/protocol/params.go
@@ -102,10 +102,6 @@ const DefaultIdleTimeout = 30 * time.Second
 // DefaultHandshakeIdleTimeout is the default idle timeout used before handshake completion.
 const DefaultHandshakeIdleTimeout = 5 * time.Second
 
-// RetiredConnectionIDDeleteTimeout is the time we keep closed connections around in order to retransmit the CONNECTION_CLOSE.
-// after this time all information about the old connection will be deleted
-const RetiredConnectionIDDeleteTimeout = 5 * time.Second
-
 // MinStreamFrameSize is the minimum size that has to be left in a packet, so that we add another STREAM frame.
 // This avoids splitting up STREAM frames into small pieces, which has 2 advantages:
 // 1. it reduces the framing overhead
diff --git a/packet_handler_map.go b/packet_handler_map.go
index 83afdbfd..ec65ae0b 100644
--- a/packet_handler_map.go
+++ b/packet_handler_map.go
@@ -49,8 +49,6 @@ type packetHandlerMap struct {
 
 	enqueueClosePacket func(closePacket)
 
-	deleteRetiredConnsAfter time.Duration
-
 	logger utils.Logger
 }
 
@@ -58,11 +56,10 @@ var _ packetHandlerManager = &packetHandlerMap{}
 
 func newPacketHandlerMap(enqueueClosePacket func(closePacket), logger utils.Logger) *packetHandlerMap {
 	return &packetHandlerMap{
-		handlers:                make(map[protocol.ConnectionID]packetHandler),
-		resetTokens:             make(map[protocol.StatelessResetToken]packetHandler),
-		deleteRetiredConnsAfter: protocol.RetiredConnectionIDDeleteTimeout,
-		enqueueClosePacket:      enqueueClosePacket,
-		logger:                  logger,
+		handlers:           make(map[protocol.ConnectionID]packetHandler),
+		resetTokens:        make(map[protocol.StatelessResetToken]packetHandler),
+		enqueueClosePacket: enqueueClosePacket,
+		logger:             logger,
 	}
 }
 
@@ -112,7 +109,7 @@ func (h *packetHandlerMap) Remove(id protocol.ConnectionID) {
 // Depending on which side closed the connection, we need to:
 // * remote close: absorb delayed packets
 // * local close: retransmit the CONNECTION_CLOSE packet, in case it was lost
-func (h *packetHandlerMap) ReplaceWithClosed(ids []protocol.ConnectionID, connClosePacket []byte) {
+func (h *packetHandlerMap) ReplaceWithClosed(ids []protocol.ConnectionID, connClosePacket []byte, expiry time.Duration) {
 	var handler packetHandler
 	if connClosePacket != nil {
 		handler = newClosedLocalConn(
@@ -132,7 +129,7 @@ func (h *packetHandlerMap) ReplaceWithClosed(ids []protocol.ConnectionID, connCl
 	h.mutex.Unlock()
 	h.logger.Debugf("Replacing connection for connection IDs %s with a closed connection.", ids)
 
-	time.AfterFunc(h.deleteRetiredConnsAfter, func() {
+	time.AfterFunc(expiry, func() {
 		h.mutex.Lock()
 		for _, id := range ids {
 			delete(h.handlers, id)


Test patch:
diff --git a/conn_id_generator_test.go b/conn_id_generator_test.go
index bdd31bdb..2c482cdb 100644
--- a/conn_id_generator_test.go
+++ b/conn_id_generator_test.go
@@ -41,7 +41,7 @@ func testConnIDGeneratorIssueAndRetire(t *testing.T, hasInitialClientDestConnID
 		connRunnerCallbacks{
 			AddConnectionID:    func(c protocol.ConnectionID) { added = append(added, c) },
 			RemoveConnectionID: func(c protocol.ConnectionID) { removed = append(removed, c) },
-			ReplaceWithClosed:  func([]protocol.ConnectionID, []byte) {},
+			ReplaceWithClosed:  func([]protocol.ConnectionID, []byte, time.Duration) {},
 		},
 		func(f wire.Frame) { queuedFrames = append(queuedFrames, f) },
 		&protocol.DefaultConnectionIDGenerator{ConnLen: 5},
@@ -114,7 +114,7 @@ func TestConnIDGeneratorRetiring(t *testing.T) {
 		connRunnerCallbacks{
 			AddConnectionID:    func(c protocol.ConnectionID) { added = append(added, c) },
 			RemoveConnectionID: func(c protocol.ConnectionID) { removed = append(removed, c) },
-			ReplaceWithClosed:  func([]protocol.ConnectionID, []byte) {},
+			ReplaceWithClosed:  func([]protocol.ConnectionID, []byte, time.Duration) {},
 		},
 		func(f wire.Frame) {},
 		&protocol.DefaultConnectionIDGenerator{ConnLen: 5},
@@ -185,7 +185,7 @@ func testConnIDGeneratorRemoveAll(t *testing.T, hasInitialClientDestConnID bool)
 		connRunnerCallbacks{
 			AddConnectionID:    func(c protocol.ConnectionID) { added = append(added, c) },
 			RemoveConnectionID: func(c protocol.ConnectionID) { removed = append(removed, c) },
-			ReplaceWithClosed:  func([]protocol.ConnectionID, []byte) {},
+			ReplaceWithClosed:  func([]protocol.ConnectionID, []byte, time.Duration) {},
 		},
 		func(f wire.Frame) {},
 		&protocol.DefaultConnectionIDGenerator{ConnLen: 5},
@@ -235,7 +235,7 @@ func testConnIDGeneratorReplaceWithClosed(t *testing.T, hasInitialClientDestConn
 		connRunnerCallbacks{
 			AddConnectionID:    func(c protocol.ConnectionID) { added = append(added, c) },
 			RemoveConnectionID: func(c protocol.ConnectionID) { t.Fatal("didn't expect conn ID removals") },
-			ReplaceWithClosed: func(connIDs []protocol.ConnectionID, b []byte) {
+			ReplaceWithClosed: func(connIDs []protocol.ConnectionID, b []byte, _ time.Duration) {
 				replaced = connIDs
 				replacedWith = b
 			},
@@ -252,7 +252,7 @@ func testConnIDGeneratorReplaceWithClosed(t *testing.T, hasInitialClientDestConn
 	require.NoError(t, g.Retire(4, protocol.ParseConnectionID([]byte{1, 1, 1, 1}), time.Now()))
 	require.Len(t, added, protocol.MaxIssuedConnectionIDs+1)
 
-	g.ReplaceWithClosed([]byte("foobar"))
+	g.ReplaceWithClosed([]byte("foobar"), time.Second)
 	if hasInitialClientDestConnID {
 		require.Len(t, replaced, protocol.MaxIssuedConnectionIDs+3)
 		require.Contains(t, replaced, *initialClientDestConnID)
@@ -278,21 +278,21 @@ func TestConnIDGeneratorAddConnRunner(t *testing.T) {
 	runner1 := connRunnerCallbacks{
 		AddConnectionID:    func(c protocol.ConnectionID) { tracker1.added = append(tracker1.added, c) },
 		RemoveConnectionID: func(c protocol.ConnectionID) { tracker1.removed = append(tracker1.removed, c) },
-		ReplaceWithClosed: func(connIDs []protocol.ConnectionID, _ []byte) {
+		ReplaceWithClosed: func(connIDs []protocol.ConnectionID, _ []byte, _ time.Duration) {
 			tracker1.replaced = append(tracker1.replaced, connIDs...)
 		},
 	}
 	runner2 := connRunnerCallbacks{
 		AddConnectionID:    func(c protocol.ConnectionID) { tracker2.added = append(tracker2.added, c) },
 		RemoveConnectionID: func(c protocol.ConnectionID) { tracker2.removed = append(tracker2.removed, c) },
-		ReplaceWithClosed: func(connIDs []protocol.ConnectionID, _ []byte) {
+		ReplaceWithClosed: func(connIDs []protocol.ConnectionID, _ []byte, _ time.Duration) {
 			tracker2.replaced = append(tracker2.replaced, connIDs...)
 		},
 	}
 	runner3 := connRunnerCallbacks{
 		AddConnectionID:    func(c protocol.ConnectionID) { tracker3.added = append(tracker3.added, c) },
 		RemoveConnectionID: func(c protocol.ConnectionID) { tracker3.removed = append(tracker3.removed, c) },
-		ReplaceWithClosed: func(connIDs []protocol.ConnectionID, _ []byte) {
+		ReplaceWithClosed: func(connIDs []protocol.ConnectionID, _ []byte, _ time.Duration) {
 			tracker3.replaced = append(tracker3.replaced, connIDs...)
 		},
 	}
@@ -345,7 +345,7 @@ func TestConnIDGeneratorAddConnRunner(t *testing.T) {
 	require.Equal(t, []protocol.ConnectionID{clientDestConnID}, tracker1.removed)
 	require.Equal(t, []protocol.ConnectionID{clientDestConnID}, tracker2.removed)
 
-	g.ReplaceWithClosed([]byte("connection closed"))
+	g.ReplaceWithClosed([]byte("connection closed"), time.Second)
 	require.True(t, len(tracker1.replaced) > 0)
 	require.Equal(t, tracker1.replaced, tracker2.replaced)
 
diff --git a/connection_test.go b/connection_test.go
index 75e33166..691494f4 100644
--- a/connection_test.go
+++ b/connection_test.go
@@ -442,7 +442,7 @@ func TestConnectionTransportError(t *testing.T) {
 	b.Data = append(b.Data, []byte("connection close")...)
 	tc.packer.EXPECT().PackConnectionClose(expectedErr, gomock.Any(), protocol.Version1).Return(&coalescedPacket{buffer: b}, nil)
 	tc.sendConn.EXPECT().Write([]byte("connection close"), gomock.Any(), gomock.Any())
-	tc.connRunner.EXPECT().ReplaceWithClosed(gomock.Any(), gomock.Any()).AnyTimes()
+	tc.connRunner.EXPECT().ReplaceWithClosed(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
 	gomock.InOrder(
 		tracer.EXPECT().ClosedConnection(expectedErr),
 		tracer.EXPECT().Close(),
@@ -476,7 +476,7 @@ func TestConnectionApplicationClose(t *testing.T) {
 	b.Data = append(b.Data, []byte("connection close")...)
 	tc.packer.EXPECT().PackApplicationClose(expectedErr, gomock.Any(), protocol.Version1).Return(&coalescedPacket{buffer: b}, nil)
 	tc.sendConn.EXPECT().Write([]byte("connection close"), gomock.Any(), gomock.Any())
-	tc.connRunner.EXPECT().ReplaceWithClosed(gomock.Any(), gomock.Any()).AnyTimes()
+	tc.connRunner.EXPECT().ReplaceWithClosed(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
 	gomock.InOrder(
 		tracer.EXPECT().ClosedConnection(expectedErr),
 		tracer.EXPECT().Close(),
@@ -835,7 +835,7 @@ func testConnectionUnpackFailureFatal(t *testing.T, unpackErr error) error {
 		connectionOptUnpacker(unpacker),
 	)
 
-	tc.connRunner.EXPECT().ReplaceWithClosed(gomock.Any(), gomock.Any())
+	tc.connRunner.EXPECT().ReplaceWithClosed(gomock.Any(), gomock.Any(), gomock.Any())
 	unpacker.EXPECT().UnpackShortHeader(gomock.Any(), gomock.Any()).Return(protocol.PacketNumber(0), protocol.PacketNumberLen(0), protocol.KeyPhaseBit(0), nil, unpackErr)
 	tc.packer.EXPECT().PackConnectionClose(gomock.Any(), gomock.Any(), protocol.Version1).Return(&coalescedPacket{buffer: getPacketBuffer()}, nil)
 	errChan := make(chan error, 1)
@@ -952,7 +952,7 @@ func TestConnectionRemoteClose(t *testing.T) {
 	tracer.EXPECT().ReceivedShortHeaderPacket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
 
 	expectedErr := &qerr.TransportError{ErrorCode: qerr.StreamLimitError, Remote: true}
-	tc.connRunner.EXPECT().ReplaceWithClosed(gomock.Any(), gomock.Any())
+	tc.connRunner.EXPECT().ReplaceWithClosed(gomock.Any(), gomock.Any(), gomock.Any())
 	streamErrChan := make(chan error, 1)
 	mockStreamManager.EXPECT().CloseWithError(gomock.Any()).Do(func(e error) { streamErrChan <- e })
 	tracerErrChan := make(chan error, 1)
@@ -1407,7 +1407,7 @@ func TestConnection0RTTTransportParameters(t *testing.T) {
 	)
 	tc.packer.EXPECT().PackCoalescedPacket(false, gomock.Any(), gomock.Any(), protocol.Version1).Return(nil, nil).AnyTimes()
 	tc.packer.EXPECT().PackConnectionClose(gomock.Any(), gomock.Any(), protocol.Version1).Return(&coalescedPacket{buffer: getPacketBuffer()}, nil)
-	tc.connRunner.EXPECT().ReplaceWithClosed(gomock.Any(), gomock.Any())
+	tc.connRunner.EXPECT().ReplaceWithClosed(gomock.Any(), gomock.Any(), gomock.Any())
 
 	errChan := make(chan error, 1)
 	go func() { errChan <- tc.conn.run() }()
diff --git a/integrationtests/self/close_test.go b/integrationtests/self/close_test.go
index 94ab8e8d..de4f7da5 100644
--- a/integrationtests/self/close_test.go
+++ b/integrationtests/self/close_test.go
@@ -31,7 +31,7 @@ func TestConnectionCloseRetransmission(t *testing.T) {
 		Conn:       newUDPConnLocalhost(t),
 		ServerAddr: server.Addr().(*net.UDPAddr),
 		DelayPacket: func(quicproxy.Direction, net.Addr, net.Addr, []byte) time.Duration {
-			return 5 * time.Millisecond // 10ms RTT
+			return scaleDuration(5 * time.Millisecond) // 10ms RTT
 		},
 		DropPacket: func(dir quicproxy.Direction, _, _ net.Addr, b []byte) bool {
 			if drop := drop.Load(); drop && dir == quicproxy.DirectionOutgoing {
@@ -57,7 +57,7 @@ func TestConnectionCloseRetransmission(t *testing.T) {
 	sconn.CloseWithError(1337, "closing")
 
 	// send 100 packets
-	for i := 0; i < 100; i++ {
+	for range 100 {
 		str, err := conn.OpenStream()
 		require.NoError(t, err)
 		_, err = str.Write([]byte("foobar"))
@@ -68,7 +68,7 @@ func TestConnectionCloseRetransmission(t *testing.T) {
 	// Expect retransmissions of the CONNECTION_CLOSE for the
 	// 1st, 2nd, 4th, 8th, 16th, 32th, 64th packet: 7 in total (+1 for the original packet)
 	var packets [][]byte
-	for i := 0; i < 8; i++ {
+	for range 8 {
 		select {
 		case p := <-dropped:
 			packets = append(packets, p)
diff --git a/mock_conn_runner_test.go b/mock_conn_runner_test.go
index b6868fc3..f2d68168 100644
--- a/mock_conn_runner_test.go
+++ b/mock_conn_runner_test.go
@@ -11,6 +11,7 @@ package quic
 
 import (
 	reflect "reflect"
+	time "time"
 
 	protocol "github.com/quic-go/quic-go/internal/protocol"
 	gomock "go.uber.org/mock/gomock"
@@ -187,15 +188,15 @@ func (c *MockConnRunnerRemoveResetTokenCall) DoAndReturn(f func(protocol.Statele
 }
 
 // ReplaceWithClosed mocks base method.
-func (m *MockConnRunner) ReplaceWithClosed(arg0 []protocol.ConnectionID, arg1 []byte) {
+func (m *MockConnRunner) ReplaceWithClosed(arg0 []protocol.ConnectionID, arg1 []byte, arg2 time.Duration) {
 	m.ctrl.T.Helper()
-	m.ctrl.Call(m, "ReplaceWithClosed", arg0, arg1)
+	m.ctrl.Call(m, "ReplaceWithClosed", arg0, arg1, arg2)
 }
 
 // ReplaceWithClosed indicates an expected call of ReplaceWithClosed.
-func (mr *MockConnRunnerMockRecorder) ReplaceWithClosed(arg0, arg1 any) *MockConnRunnerReplaceWithClosedCall {
+func (mr *MockConnRunnerMockRecorder) ReplaceWithClosed(arg0, arg1, arg2 any) *MockConnRunnerReplaceWithClosedCall {
 	mr.mock.ctrl.T.Helper()
-	call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReplaceWithClosed", reflect.TypeOf((*MockConnRunner)(nil).ReplaceWithClosed), arg0, arg1)
+	call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReplaceWithClosed", reflect.TypeOf((*MockConnRunner)(nil).ReplaceWithClosed), arg0, arg1, arg2)
 	return &MockConnRunnerReplaceWithClosedCall{Call: call}
 }
 
@@ -211,13 +212,13 @@ func (c *MockConnRunnerReplaceWithClosedCall) Return() *MockConnRunnerReplaceWit
 }
 
 // Do rewrite *gomock.Call.Do
-func (c *MockConnRunnerReplaceWithClosedCall) Do(f func([]protocol.ConnectionID, []byte)) *MockConnRunnerReplaceWithClosedCall {
+func (c *MockConnRunnerReplaceWithClosedCall) Do(f func([]protocol.ConnectionID, []byte, time.Duration)) *MockConnRunnerReplaceWithClosedCall {
 	c.Call = c.Call.Do(f)
 	return c
 }
 
 // DoAndReturn rewrite *gomock.Call.DoAndReturn
-func (c *MockConnRunnerReplaceWithClosedCall) DoAndReturn(f func([]protocol.ConnectionID, []byte)) *MockConnRunnerReplaceWithClosedCall {
+func (c *MockConnRunnerReplaceWithClosedCall) DoAndReturn(f func([]protocol.ConnectionID, []byte, time.Duration)) *MockConnRunnerReplaceWithClosedCall {
 	c.Call = c.Call.DoAndReturn(f)
 	return c
 }
diff --git a/mock_packet_handler_manager_test.go b/mock_packet_handler_manager_test.go
index 27c8f3b8..98b2ea17 100644
--- a/mock_packet_handler_manager_test.go
+++ b/mock_packet_handler_manager_test.go
@@ -11,6 +11,7 @@ package quic
 
 import (
 	reflect "reflect"
+	time "time"
 
 	protocol "github.com/quic-go/quic-go/internal/protocol"
 	gomock "go.uber.org/mock/gomock"
@@ -339,15 +340,15 @@ func (c *MockPacketHandlerManagerRemoveResetTokenCall) DoAndReturn(f func(protoc
 }
 
 // ReplaceWithClosed mocks base method.
-func (m *MockPacketHandlerManager) ReplaceWithClosed(arg0 []protocol.ConnectionID, arg1 []byte) {
+func (m *MockPacketHandlerManager) ReplaceWithClosed(arg0 []protocol.ConnectionID, arg1 []byte, arg2 time.Duration) {
 	m.ctrl.T.Helper()
-	m.ctrl.Call(m, "ReplaceWithClosed", arg0, arg1)
+	m.ctrl.Call(m, "ReplaceWithClosed", arg0, arg1, arg2)
 }
 
 // ReplaceWithClosed indicates an expected call of ReplaceWithClosed.
-func (mr *MockPacketHandlerManagerMockRecorder) ReplaceWithClosed(arg0, arg1 any) *MockPacketHandlerManagerReplaceWithClosedCall {
+func (mr *MockPacketHandlerManagerMockRecorder) ReplaceWithClosed(arg0, arg1, arg2 any) *MockPacketHandlerManagerReplaceWithClosedCall {
 	mr.mock.ctrl.T.Helper()
-	call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReplaceWithClosed", reflect.TypeOf((*MockPacketHandlerManager)(nil).ReplaceWithClosed), arg0, arg1)
+	call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReplaceWithClosed", reflect.TypeOf((*MockPacketHandlerManager)(nil).ReplaceWithClosed), arg0, arg1, arg2)
 	return &MockPacketHandlerManagerReplaceWithClosedCall{Call: call}
 }
 
@@ -363,13 +364,13 @@ func (c *MockPacketHandlerManagerReplaceWithClosedCall) Return() *MockPacketHand
 }
 
 // Do rewrite *gomock.Call.Do
-func (c *MockPacketHandlerManagerReplaceWithClosedCall) Do(f func([]protocol.ConnectionID, []byte)) *MockPacketHandlerManagerReplaceWithClosedCall {
+func (c *MockPacketHandlerManagerReplaceWithClosedCall) Do(f func([]protocol.ConnectionID, []byte, time.Duration)) *MockPacketHandlerManagerReplaceWithClosedCall {
 	c.Call = c.Call.Do(f)
 	return c
 }
 
 // DoAndReturn rewrite *gomock.Call.DoAndReturn
-func (c *MockPacketHandlerManagerReplaceWithClosedCall) DoAndReturn(f func([]protocol.ConnectionID, []byte)) *MockPacketHandlerManagerReplaceWithClosedCall {
+func (c *MockPacketHandlerManagerReplaceWithClosedCall) DoAndReturn(f func([]protocol.ConnectionID, []byte, time.Duration)) *MockPacketHandlerManagerReplaceWithClosedCall {
 	c.Call = c.Call.DoAndReturn(f)
 	return c
 }
diff --git a/packet_handler_map_test.go b/packet_handler_map_test.go
index e4442d4f..a285ba80 100644
--- a/packet_handler_map_test.go
+++ b/packet_handler_map_test.go
@@ -74,12 +74,11 @@ func TestPacketHandlerMapReplaceWithLocalClosed(t *testing.T) {
 		utils.DefaultLogger,
 	)
 	dur := scaleDuration(10 * time.Millisecond)
-	m.deleteRetiredConnsAfter = dur
 
 	handler := &mockPacketHandler{}
 	connID := protocol.ParseConnectionID([]byte{4, 3, 2, 1})
 	require.True(t, m.Add(connID, handler))
-	m.ReplaceWithClosed([]protocol.ConnectionID{connID}, []byte("foobar"))
+	m.ReplaceWithClosed([]protocol.ConnectionID{connID}, []byte("foobar"), dur)
 	h, ok := m.Get(connID)
 	require.True(t, ok)
 	require.NotEqual(t, handler, h)
@@ -93,7 +92,7 @@ func TestPacketHandlerMapReplaceWithLocalClosed(t *testing.T) {
 	require.Eventually(t, func() bool {
 		_, ok := m.Get(connID)
 		return !ok
-	}, time.Second, 10*time.Millisecond)
+	}, time.Second, dur)
 }
 
 func TestPacketHandlerMapReplaceWithRemoteClosed(t *testing.T) {
@@ -102,13 +101,12 @@ func TestPacketHandlerMapReplaceWithRemoteClosed(t *testing.T) {
 		func(p closePacket) { closePackets = append(closePackets, p) },
 		utils.DefaultLogger,
 	)
-	dur := scaleDuration(50 * time.Millisecond)
-	m.deleteRetiredConnsAfter = dur
+	dur := scaleDuration(25 * time.Millisecond)
 
 	handler := &mockPacketHandler{}
 	connID := protocol.ParseConnectionID([]byte{4, 3, 2, 1})
 	require.True(t, m.Add(connID, handler))
-	m.ReplaceWithClosed([]protocol.ConnectionID{connID}, nil)
+	m.ReplaceWithClosed([]protocol.ConnectionID{connID}, nil, dur)
 	h, ok := m.Get(connID)
 	require.True(t, ok)
 	require.NotEqual(t, handler, h)