Delete catchbevior and allow a user to provide a catch.

By default the one from unliftIO is used.
This commit is contained in:
Jappie Klooster 2022-07-20 12:32:48 +02:00
parent d04c22e3d6
commit 5ac65db1bf
6 changed files with 17 additions and 57 deletions

View File

@ -1,23 +0,0 @@
{-# LANGUAGE LambdaCase #-}
-- | module providing an abstract type around 'CatchBehavior'
-- through smart constructors.
-- providing future additional extensibility.
--
-- @since 1.6.24.0
module Yesod.Core.CatchBehavior(CatchBehavior, rethrow, catch, isCatch) where
-- | @since 1.6.24.0
data CatchBehavior = Rethrow -- ^ Rethrow an exception and let the webserver deal with it (usually warp)
| Catch -- ^ catch an exception and render in yesod
rethrow :: CatchBehavior
rethrow = Rethrow
catch :: CatchBehavior
catch = Catch
isCatch :: CatchBehavior -> Bool
isCatch = \case
Catch -> True
Rethrow -> False

View File

@ -57,10 +57,7 @@ import Data.CaseInsensitive (CI)
import qualified Network.Wai.Handler.Warp as Warp import qualified Network.Wai.Handler.Warp as Warp
import qualified Network.Wai.Request import qualified Network.Wai.Request
import Data.IORef import Data.IORef
import UnliftIO (SomeException, fromException, isSyncException, fromExceptionUnwrap) import UnliftIO (SomeException, catch)
import Data.Proxy(Proxy)
import Yesod.Core.CatchBehavior
import System.Timeout(Timeout)
-- | Define settings for a Yesod applications. All methods have intelligent -- | Define settings for a Yesod applications. All methods have intelligent
-- defaults, and therefore no implementation is required. -- defaults, and therefore no implementation is required.
@ -84,8 +81,8 @@ class RenderRoute site => Yesod site where
-- catching allows yesod to render the error page. -- catching allows yesod to render the error page.
-- the default 'rethrowAsync' is to rethrow async -- the default 'rethrowAsync' is to rethrow async
-- exceptions. -- exceptions.
catchBehavior :: site -> SomeException -> IO CatchBehavior catchBehavior :: site -> IO a -> (SomeException -> IO a) -> IO a
catchBehavior _ = pure . rethrowAsync catchBehavior _ = catch
-- | Output error response pages. -- | Output error response pages.
-- --
@ -651,10 +648,6 @@ widgetToPageContent w = do
runUniqueList :: Eq x => UniqueList x -> [x] runUniqueList :: Eq x => UniqueList x -> [x]
runUniqueList (UniqueList x) = nub $ x [] runUniqueList (UniqueList x) = nub $ x []
rethrowAsync :: SomeException -> CatchBehavior
rethrowAsync exception =
if isSyncException exception then catch else rethrow
-- | The default error handler for 'errorHandler'. -- | The default error handler for 'errorHandler'.
defaultErrorHandler :: Yesod site => ErrorResponse -> HandlerFor site TypedContent defaultErrorHandler :: Yesod site => ErrorResponse -> HandlerFor site TypedContent
defaultErrorHandler NotFound = selectRep $ do defaultErrorHandler NotFound = selectRep $ do

View File

@ -56,27 +56,17 @@ import Control.DeepSeq (($!!), NFData)
import UnliftIO.Exception import UnliftIO.Exception
import UnliftIO(MonadUnliftIO, withRunInIO) import UnliftIO(MonadUnliftIO, withRunInIO)
import Data.Proxy(Proxy(..)) import Data.Proxy(Proxy(..))
import Yesod.Core.CatchBehavior
-- | like `catch` but doesn't check for async exceptions, -- | wraps the provided catch fun in a unliftIO
-- thereby catching them too.
-- This is desirable for letting yesod generate a 500 error page
-- rather then warp.
--
-- Normally this is VERY dubious. you need to rethrow.
-- recovrery from async isn't allowed.
-- see async section: https://www.fpcomplete.com/blog/2018/04/async-exception-handling-haskell/
unsafeAsyncCatch unsafeAsyncCatch
:: (MonadUnliftIO m) :: (MonadUnliftIO m)
=> (SomeException -> IO CatchBehavior) => (IO a -> (SomeException -> IO a) -> IO a)
-> m a -- ^ action -> m a -- ^ action
-> (SomeException -> m a) -- ^ handler -> (SomeException -> m a) -- ^ handler
-> m a -> m a
unsafeAsyncCatch catchBehavior f g = withRunInIO $ \run -> run f `EUnsafe.catch` \e -> do unsafeAsyncCatch catchFun f g = withRunInIO $ \run ->
caught <- liftIO $ catchBehavior e run f `catchFun` \e -> run (g e)
if isCatch caught
then run (g e)
else liftIO $ EUnsafe.throwIO e
-- | Convert a synchronous exception into an ErrorResponse -- | Convert a synchronous exception into an ErrorResponse
toErrorHandler :: SomeException -> IO ErrorResponse toErrorHandler :: SomeException -> IO ErrorResponse
@ -213,7 +203,7 @@ handleContents handleError' finalSession headers contents =
-- --
-- Note that this also catches async exceptions. -- Note that this also catches async exceptions.
evalFallback :: (Monoid w, NFData w) evalFallback :: (Monoid w, NFData w)
=> (SomeException -> IO CatchBehavior) => (forall a. IO a -> (SomeException -> IO a) -> IO a)
-> HandlerContents -> HandlerContents
-> w -> w
-> IO (w, HandlerContents) -> IO (w, HandlerContents)

View File

@ -8,6 +8,7 @@
{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE GADTs #-} {-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-}
module Yesod.Core.Types where module Yesod.Core.Types where
import Data.Aeson (ToJSON) import Data.Aeson (ToJSON)
@ -56,7 +57,6 @@ import Control.DeepSeq (NFData (rnf))
import Yesod.Core.TypeCache (TypeMap, KeyedTypeMap) import Yesod.Core.TypeCache (TypeMap, KeyedTypeMap)
import Control.Monad.Logger (MonadLoggerIO (..)) import Control.Monad.Logger (MonadLoggerIO (..))
import UnliftIO (MonadUnliftIO (..), SomeException) import UnliftIO (MonadUnliftIO (..), SomeException)
import Yesod.Core.CatchBehavior
-- Sessions -- Sessions
type SessionMap = Map Text ByteString type SessionMap = Map Text ByteString
@ -186,7 +186,7 @@ data RunHandlerEnv child site = RunHandlerEnv
-- | @since 1.6.24.0 -- | @since 1.6.24.0
-- should we catch an exception, or rethrow it. -- should we catch an exception, or rethrow it.
, rheShouldCatch :: !(SomeException -> IO CatchBehavior) , rheShouldCatch :: !(forall a. IO a -> (SomeException -> IO a) -> IO a)
} }
data HandlerData child site = HandlerData data HandlerData child site = HandlerData

View File

@ -15,7 +15,6 @@ module YesodCoreTest.ErrorHandling.CustomApp
) where ) where
import Yesod.Core.CatchBehavior
import Yesod.Core.Types import Yesod.Core.Types
import Yesod.Core import Yesod.Core
import qualified UnliftIO.Exception as E import qualified UnliftIO.Exception as E
@ -34,7 +33,9 @@ data MyException = MkMyException
deriving (Show, E.Exception) deriving (Show, E.Exception)
instance Yesod CustomApp where instance Yesod CustomApp where
catchBehavior _ exception = pure $ -- something we couldn't do before, rethrow custom exceptions
case E.fromException exception of catchBehavior _ action handler =
Just MkMyException -> rethrow action `E.catch` \exception -> do
Nothing -> catch case E.fromException exception of
Just MkMyException -> E.throwIO MkMyException
Nothing -> handler exception

View File

@ -75,7 +75,6 @@ library
Yesod.Core.Types Yesod.Core.Types
Yesod.Core.Unsafe Yesod.Core.Unsafe
Yesod.Routes.TH.Types Yesod.Routes.TH.Types
Yesod.Core.CatchBehavior
other-modules: Yesod.Core.Internal.Session other-modules: Yesod.Core.Internal.Session
Yesod.Core.Internal.Request Yesod.Core.Internal.Request
Yesod.Core.Class.Handler Yesod.Core.Class.Handler