Skip to content

Commit aecd90c

Browse files
committed
Better handling of connections being lost or closed
1 parent 062fa81 commit aecd90c

2 files changed

Lines changed: 28 additions & 5 deletions

File tree

packages/network-transport-quic/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
* Eliminated a rare race condition that allowed the transport to read messages before
55
marking the connection as open, violating the interface expectations.
6+
* Better handling of lost connections
67

78
2026-01-01 Laurent P. René de Cotret <laurent.decotret@outlook.com> 0.1.1
89

packages/network-transport-quic/src/Network/Transport/QUIC/Internal/QUICTransport.hs

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -311,10 +311,13 @@ closeLocalEndpoint quicTransport localEndPoint = do
311311
LocalEndPointStateClosed -> pure (LocalEndPointStateClosed, Nothing)
312312
LocalEndPointStateValid st -> pure (LocalEndPointStateClosed, Just st)
313313

314-
forM_ mPreviousState $ \vst ->
315-
forConcurrently_
316-
(vst ^. incomingConnections <> vst ^. outgoingConnections)
317-
tryCloseRemoteStream
314+
-- Close outgoing remote endpoints before incoming. The peer's handleIncomingMessages
315+
-- reader writes ConnectionClosed in response to our outgoing close; its listenForClose
316+
-- writes ErrorEvent in response to our incoming close. Processing outgoing first gives
317+
-- the peer's event queue the expected ConnectionClosed-before-ErrorEvent ordering.
318+
forM_ mPreviousState $ \vst -> do
319+
forConcurrently_ (vst ^. outgoingConnections) tryCloseRemoteStream
320+
forConcurrently_ (vst ^. incomingConnections) tryCloseRemoteStream
318321
atomically $ writeTQueue (localEndPoint ^. localQueue) EndPointClosed
319322
where
320323
tryCloseRemoteStream :: RemoteEndPoint -> IO ()
@@ -409,7 +412,7 @@ createConnectionTo creds validateCreds localEndPoint remoteAddress = do
409412
validateCreds
410413
(localEndPoint ^. localAddress)
411414
remoteAddress
412-
(\_ -> closeRemoteEndPoint Outgoing remoteEndPoint)
415+
(onException remoteEndPoint)
413416
onConnectionLost
414417
>>= \case
415418
Left exc -> pure $ Left exc
@@ -438,6 +441,25 @@ createConnectionTo creds validateCreds localEndPoint remoteAddress = do
438441
Left (exc :: SomeException) -> pure . Left $ TransportError ConnectFailed (displayException exc)
439442
Right () -> pure $ Right (remoteEndPoint, clientConnId)
440443
where
444+
onException remoteEndPoint _exception = do
445+
-- The handler runs when QUIC.Client.run exits with an exception — i.e. the
446+
-- peer tore down the connection before we did. If the remote endpoint was
447+
-- still Valid when this fires, our side had not initiated the close, so we
448+
-- surface an EventConnectionLost. (If state is already Closed, we initiated
449+
-- the close ourselves and the QUIC exception is just teardown noise.)
450+
do
451+
mAct <- modifyMVar (remoteEndPoint ^. remoteEndPointState) $ \case
452+
RemoteEndPointInit -> pure (RemoteEndPointClosed, Nothing)
453+
RemoteEndPointClosed -> pure (RemoteEndPointClosed, Nothing)
454+
RemoteEndPointValid (ValidRemoteEndPointState stream isClosed conns _) ->
455+
let cleanup = do
456+
forM_ conns $ \c -> sendCloseConnection c stream
457+
putMVar isClosed ()
458+
onConnectionLost
459+
in pure (RemoteEndPointClosed, Just cleanup)
460+
case mAct of
461+
Nothing -> pure ()
462+
Just act -> act
441463
onConnectionLost =
442464
atomically
443465
. writeTQueue (localEndPoint ^. localQueue)

0 commit comments

Comments
 (0)