packages feed

grapesy-1.2.0: src/Network/GRPC/Common/Exception.hs

{-# LANGUAGE CPP               #-}
{-# LANGUAGE ImplicitParams    #-}
{-# LANGUAGE OverloadedStrings #-}

{-# OPTIONS_GHC -Wno-orphans #-}

-- | Exception utilities
--
-- Most users will never need to import from this module; it's mostly here to
-- facilitate debugging, primarily of @grapesy@ itself and the libraries it
-- depends on, such as @http2@.
module Network.GRPC.Common.Exception (
    -- * Key exception types
    ClientDisconnected(..)
  , ServerDisconnected(..)

    -- * Rendering
  , grapesyFormatCtx

    -- * Re-exports
  , module X
  ) where

import Data.Typeable
import GHC.Generics
import System.ThreadManager qualified as TimeManager

#if MIN_VERSION_base(4,20,0)
import Control.Exception (backtraceDesired)
#endif

import Network.GRPC.Util.Imports

import Network.GRPC.Util.Exception.Doc            as X
import Network.GRPC.Util.Exception.Exact          as X
import Network.GRPC.Util.Exception.FormatCtx      as X
import Network.GRPC.Util.Exception.Shims          as X
import Network.GRPC.Util.Exception.ToExceptionDoc as X

{-------------------------------------------------------------------------------
  Key exception types

  TODO: /All/ exceptions that indicate a client disconnect or a server
  disconnect should be wrapped in these two types, so that client code does not
  have to deal with many exception types.
  <https://github.com/well-typed/grapesy/issues/339>
-------------------------------------------------------------------------------}

-- | Client disconnected unexpectedly
data ClientDisconnected = ClientDisconnected {
      -- | The exception as reported by the transport (typically @http2@)
      clientDisconnectedException :: ExactException

      -- | Backtrace, /if/ different
      --
      -- In most cases we wrap the underlying exception the moment it arises.
      -- When this happens, there is no meaningful difference between the
      -- backtrace associated with the exception and the backtrace to where we
      -- wrap it.
      --
      -- Sometimes, however, transport exceptions are thrown asynchronously. For
      -- example, @http2@ will throw 'KilledByThreadManager' to server handlers
      -- when a client disconnects. In this case the backtrace of the exception
      -- and where we catch it might be quite different.
    , clientDisconnectedBacktrace :: Maybe Backtraces
    }
  deriving stock (Generic, Show)
  deriving anyclass ToExceptionDoc

-- | Server disconnected unexpectedly
--
-- See also 'ClientDisconnected' for additional discussion.
data ServerDisconnected = ServerDisconnected {
      -- | The exception as reported by the transport (typically @http2@)
      serverDisconnectedException :: ExactException

      -- | Backtrace, /if/ different
      --
      -- As discussed in 'clientDisconnectedBacktrace', normally we there is
      -- no meaningful difference between the backtrace of the exception and the
      -- backtrace to where we wrap it. An example where they /are/ different is
      -- when the entire connection to the server is closed, and we notice this
      -- elsewhere and close individual RPCs on that connection; the backtrace
      -- of the latter will be different to the former.
    , serverDisconnectedBacktrace :: Maybe Backtraces
    }
  deriving stock (Generic, Show)
  deriving anyclass ToExceptionDoc

#if MIN_VERSION_base(4,20,0)
-- See discussion of 'clientDisconnectedBacktrace'
instance Exception ClientDisconnected where backtraceDesired _ = False
instance Exception ServerDisconnected where backtraceDesired _ = False
#else
instance Exception ClientDisconnected
instance Exception ServerDisconnected
#endif

{-------------------------------------------------------------------------------
  Orphans
-------------------------------------------------------------------------------}

instance ToExceptionDoc TimeManager.KilledByThreadManager where
  toExceptionDoc ctx = \case
      TimeManager.KilledByThreadManager mse ->
        withHeader "KilledByThreadManager" $ toExceptionDoc ctx mse

{-------------------------------------------------------------------------------
  Rendering
-------------------------------------------------------------------------------}

grapesyFormatCtx :: FormatCtx
grapesyFormatCtx = defaultFormatCtx
  & insertFormatCtx_ (Proxy @ClientDisconnected)
  & insertFormatCtx_ (Proxy @ServerDisconnected)
  & insertFormatCtx_ (Proxy @TimeManager.KilledByThreadManager)