packages feed

network-transport-quic-0.1.0: src/Network/Transport/QUIC/Internal/Server.hs

{-# LANGUAGE ScopedTypeVariables #-}

module Network.Transport.QUIC.Internal.Server (forkServer) where

import Control.Concurrent (ThreadId, forkIOWithUnmask)
import Control.Exception (SomeException, catch, finally, mask, mask_)
import Data.List.NonEmpty (NonEmpty)
import Network.QUIC qualified as QUIC
import Network.QUIC.Server qualified as QUIC.Server
import Network.Socket (Socket)
import Network.Transport.QUIC.Internal.Configuration (Credential, mkServerConfig)

forkServer ::
  Socket ->
  NonEmpty Credential ->
  -- | Error handler that runs whenever an exception is thrown inside
  --  the thread that accepted an incoming connection
  (SomeException -> IO ()) ->
  -- | Termination handler that runs if the server thread catches an exception
  (SomeException -> IO ()) ->
  -- | Request handler. The stream is closed after this handler returns.
  (QUIC.Stream -> IO ()) ->
  IO ThreadId
forkServer socket creds errorHandler terminationHandler requestHandler = do
  serverConfig <- mkServerConfig creds

  let acceptConnection :: QUIC.Connection -> IO ()
      acceptConnection conn = mask $ \restore -> do
        QUIC.waitEstablished conn
        stream <- QUIC.acceptStream conn

        catch
          (restore (requestHandler stream `finally` QUIC.closeStream stream))
          errorHandler

  -- We have to make sure that the exception handler is
  -- installed /before/ any asynchronous exception occurs. So we mask_, then
  -- forkIOWithUnmask (the child thread inherits the masked state from the parent), then
  -- unmask only inside the catch.
  --
  -- See the documentation for `forkIOWithUnmask`.
  mask_ $
    forkIOWithUnmask
      ( \unmask ->
          catch
            (unmask $ QUIC.Server.runWithSockets [socket] serverConfig (\conn -> catch (acceptConnection conn) errorHandler))
            terminationHandler
      )