@@ -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