Skip to content

Commit d82a27b

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

2 files changed

Lines changed: 26 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: 25 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,24 @@ createConnectionTo creds validateCreds localEndPoint remoteAddress = do
409412
validateCreds
410413
(localEndPoint ^. localAddress)
411414
remoteAddress
412-
(\_ -> closeRemoteEndPoint Outgoing remoteEndPoint)
415+
-- The handler runs when QUIC.Client.run exits with an exception — i.e. the
416+
-- peer tore down the connection before we did. If the remote endpoint was
417+
-- still Valid when this fires, our side had not initiated the close, so we
418+
-- surface an EventConnectionLost. (If state is already Closed, we initiated
419+
-- the close ourselves and the QUIC exception is just teardown noise.)
420+
(\_ -> do
421+
mAct <- modifyMVar (remoteEndPoint ^. remoteEndPointState) $ \case
422+
RemoteEndPointInit -> pure (RemoteEndPointClosed, Nothing)
423+
RemoteEndPointClosed -> pure (RemoteEndPointClosed, Nothing)
424+
RemoteEndPointValid (ValidRemoteEndPointState stream isClosed conns _) ->
425+
let cleanup = do
426+
forM_ conns $ \c -> sendCloseConnection c stream
427+
putMVar isClosed ()
428+
onConnectionLost
429+
in pure (RemoteEndPointClosed, Just cleanup)
430+
case mAct of
431+
Nothing -> pure ()
432+
Just act -> act)
413433
onConnectionLost
414434
>>= \case
415435
Left exc -> pure $ Left exc

0 commit comments

Comments
 (0)