change catchbehavior to get app be in io, make it abstract type

This commit is contained in:
Jappie Klooster 2022-07-07 12:06:56 +02:00
parent 710adc7329
commit 27042c93ce
7 changed files with 57 additions and 28 deletions

View File

@ -0,0 +1,23 @@
{-# 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

@ -55,8 +55,9 @@ 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) import UnliftIO (SomeException, fromException, isSyncException, fromExceptionUnwrap)
import Data.Proxy(Proxy) import Data.Proxy(Proxy)
import Yesod.Core.CatchBehavior
-- | 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.
@ -81,8 +82,8 @@ class RenderRoute site => Yesod site where
-- the default 'defaultCatchBehavior' is to catch everything -- the default 'defaultCatchBehavior' is to catch everything
-- (even async), except for the -- (even async), except for the
-- 'Warp.ConnectionClosedByPeer' constructor. -- 'Warp.ConnectionClosedByPeer' constructor.
catchBehavior :: Proxy site -> SomeException -> CatchBehavior catchBehavior :: site -> SomeException -> IO CatchBehavior
catchBehavior _ = defaultCatchBehavior catchBehavior _ = pure . defaultCatchBehavior
-- | Output error response pages. -- | Output error response pages.
-- --
@ -648,10 +649,14 @@ 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
defaultCatchBehavior :: SomeException -> CatchBehavior defaultCatchBehavior :: SomeException -> CatchBehavior
defaultCatchBehavior exception = case fromException exception of defaultCatchBehavior exception = case fromExceptionUnwrap exception of
Just Warp.ConnectionClosedByPeer -> Rethrow Just Warp.ConnectionClosedByPeer -> rethrow
_ -> Catch _ -> catch
-- | The default error handler for 'errorHandler'. -- | The default error handler for 'errorHandler'.

View File

@ -56,6 +56,7 @@ 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, -- | like `catch` but doesn't check for async exceptions,
-- thereby catching them too. -- thereby catching them too.
@ -67,14 +68,15 @@ import Data.Proxy(Proxy(..))
-- see async section: https://www.fpcomplete.com/blog/2018/04/async-exception-handling-haskell/ -- see async section: https://www.fpcomplete.com/blog/2018/04/async-exception-handling-haskell/
unsafeAsyncCatch unsafeAsyncCatch
:: (MonadUnliftIO m) :: (MonadUnliftIO m)
=> (SomeException -> CatchBehavior) => (SomeException -> IO CatchBehavior)
-> 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 catchBehavior f g = withRunInIO $ \run -> run f `EUnsafe.catch` \e -> do
case catchBehavior e of caught <- liftIO $ catchBehavior e
Catch -> run (g e) if isCatch caught
Rethrow -> liftIO $ throwIO e 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
@ -211,7 +213,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 -> CatchBehavior) => (SomeException -> IO CatchBehavior)
-> HandlerContents -> HandlerContents
-> w -> w
-> IO (w, HandlerContents) -> IO (w, HandlerContents)
@ -296,7 +298,7 @@ runFakeHandler fakeSessionMap logger site handler = liftIO $ do
, rheLog = messageLoggerSource site $ logger site , rheLog = messageLoggerSource site $ logger site
, rheOnError = errHandler , rheOnError = errHandler
, rheMaxExpires = maxExpires , rheMaxExpires = maxExpires
, rheShouldCatch = catchBehavior (Proxy :: Proxy site) , rheShouldCatch = catchBehavior site
} }
handler' handler'
errHandler err req = do errHandler err req = do
@ -373,7 +375,7 @@ yesodRunner handler' YesodRunnerEnv {..} route req sendResponse = do
, rheLog = log' , rheLog = log'
, rheOnError = safeEh log' , rheOnError = safeEh log'
, rheMaxExpires = maxExpires , rheMaxExpires = maxExpires
, rheShouldCatch = catchBehavior (Proxy :: Proxy site) , rheShouldCatch = catchBehavior yreSite
} }
rhe = rheSafe rhe = rheSafe
{ rheOnError = runHandler rheSafe . errorHandler { rheOnError = runHandler rheSafe . errorHandler

View File

@ -56,6 +56,7 @@ 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
@ -169,13 +170,6 @@ newtype WaiSubsite = WaiSubsite { runWaiSubsite :: W.Application }
-- @since 1.4.34 -- @since 1.4.34
newtype WaiSubsiteWithAuth = WaiSubsiteWithAuth { runWaiSubsiteWithAuth :: W.Application } newtype WaiSubsiteWithAuth = WaiSubsiteWithAuth { runWaiSubsiteWithAuth :: W.Application }
-- | @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
-- defaultShouldCatch = pure ()
data RunHandlerEnv child site = RunHandlerEnv data RunHandlerEnv child site = RunHandlerEnv
{ rheRender :: !(Route site -> [(Text, Text)] -> Text) { rheRender :: !(Route site -> [(Text, Text)] -> Text)
, rheRoute :: !(Maybe (Route child)) , rheRoute :: !(Maybe (Route child))
@ -192,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 -> CatchBehavior) , rheShouldCatch :: !(SomeException -> IO CatchBehavior)
} }
data HandlerData child site = HandlerData data HandlerData child site = HandlerData

View File

@ -133,8 +133,10 @@ getThreadKilledR = do
getConnectionClosedPeerR :: Handler Html getConnectionClosedPeerR :: Handler Html
getConnectionClosedPeerR = getConnectionClosedPeerR = do
liftIO $ E.throwIO Warp.ConnectionClosedByPeer x <- liftIO Conc.myThreadId
liftIO $ Async.withAsync (E.throwTo x Warp.ConnectionClosedByPeer) Async.wait
pure "unreachablle"
getAsyncSessionR :: Handler Html getAsyncSessionR :: Handler Html
@ -339,8 +341,9 @@ caseThreadKilled500 = runner $ do
caseDefaultConnectionCloseRethrows :: IO () caseDefaultConnectionCloseRethrows :: IO ()
caseDefaultConnectionCloseRethrows = caseDefaultConnectionCloseRethrows =
shouldThrow testcode $ \case Warp.ConnectionClosedByPeer -> True shouldThrow testcode $ \e -> case E.fromExceptionUnwrap e of
_ -> False Just Warp.ConnectionClosedByPeer -> True
_ -> False
where where

View File

@ -15,6 +15,7 @@ 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
@ -33,7 +34,7 @@ data MyException = MkMyException
deriving (Show, E.Exception) deriving (Show, E.Exception)
instance Yesod CustomApp where instance Yesod CustomApp where
catchBehavior _ exception = catchBehavior _ exception = pure $
case E.fromException exception of case E.fromException exception of
Just MkMyException -> Rethrow Just MkMyException -> rethrow
Nothing -> Catch Nothing -> catch

View File

@ -75,6 +75,7 @@ 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