Fix it for async exceptions in the sessions as well
This commit is contained in:
parent
e284a68a9f
commit
5b96d94915
@ -21,7 +21,6 @@ module Yesod.Core.Internal.Run
|
|||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
import qualified GHC.Conc.Sync as Sync
|
|
||||||
import qualified Control.Exception as EUnsafe
|
import qualified Control.Exception as EUnsafe
|
||||||
import Yesod.Core.Internal.Response
|
import Yesod.Core.Internal.Response
|
||||||
import Data.ByteString.Builder (toLazyByteString)
|
import Data.ByteString.Builder (toLazyByteString)
|
||||||
@ -72,6 +71,12 @@ unsafeAsyncCatch
|
|||||||
unsafeAsyncCatch f g = withRunInIO $ \run -> run f `EUnsafe.catch` \e -> do
|
unsafeAsyncCatch f g = withRunInIO $ \run -> run f `EUnsafe.catch` \e -> do
|
||||||
run (g e)
|
run (g e)
|
||||||
|
|
||||||
|
unsafeAsyncCatchAny :: (MonadUnliftIO m)
|
||||||
|
=> m a -- ^ action
|
||||||
|
-> (SomeException -> m a) -- ^ handler
|
||||||
|
-> m a
|
||||||
|
unsafeAsyncCatchAny = unsafeAsyncCatch
|
||||||
|
|
||||||
-- | Convert a synchronous exception into an ErrorResponse
|
-- | Convert a synchronous exception into an ErrorResponse
|
||||||
toErrorHandler :: SomeException -> IO ErrorResponse
|
toErrorHandler :: SomeException -> IO ErrorResponse
|
||||||
toErrorHandler e0 = handleAny errFromShow $
|
toErrorHandler e0 = handleAny errFromShow $
|
||||||
@ -204,11 +209,13 @@ handleContents handleError' finalSession headers contents =
|
|||||||
-- | Evaluate the given value. If an exception is thrown, use it to
|
-- | Evaluate the given value. If an exception is thrown, use it to
|
||||||
-- replace the provided contents and then return @mempty@ in place of the
|
-- replace the provided contents and then return @mempty@ in place of the
|
||||||
-- evaluated value.
|
-- evaluated value.
|
||||||
|
--
|
||||||
|
-- Note that this also catches async exceptions.
|
||||||
evalFallback :: (Monoid w, NFData w)
|
evalFallback :: (Monoid w, NFData w)
|
||||||
=> HandlerContents
|
=> HandlerContents
|
||||||
-> w
|
-> w
|
||||||
-> IO (w, HandlerContents)
|
-> IO (w, HandlerContents)
|
||||||
evalFallback contents val = catchAny
|
evalFallback contents val = unsafeAsyncCatchAny
|
||||||
(fmap (, contents) (evaluate $!! val))
|
(fmap (, contents) (evaluate $!! val))
|
||||||
(fmap ((mempty, ) . HCError) . toErrorHandler)
|
(fmap ((mempty, ) . HCError) . toErrorHandler)
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{-# LANGUAGE TypeFamilies, QuasiQuotes, TemplateHaskell, MultiParamTypeClasses, OverloadedStrings #-}
|
{-# LANGUAGE TypeFamilies, QuasiQuotes, TemplateHaskell, MultiParamTypeClasses, OverloadedStrings #-}
|
||||||
{-# LANGUAGE ScopedTypeVariables #-}
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
|
{-# LANGUAGE NumericUnderscores #-}
|
||||||
{-# LANGUAGE ViewPatterns #-}
|
{-# LANGUAGE ViewPatterns #-}
|
||||||
module YesodCoreTest.ErrorHandling
|
module YesodCoreTest.ErrorHandling
|
||||||
( errorHandlingTest
|
( errorHandlingTest
|
||||||
@ -53,6 +54,7 @@ mkYesod "App" [parseRoutes|
|
|||||||
|
|
||||||
/allocation-limit AlocationLimitR GET
|
/allocation-limit AlocationLimitR GET
|
||||||
/thread-killed ThreadKilledR GET
|
/thread-killed ThreadKilledR GET
|
||||||
|
/async-session AsyncSessionR GET
|
||||||
|]
|
|]
|
||||||
|
|
||||||
overrideStatus :: Status
|
overrideStatus :: Status
|
||||||
@ -128,7 +130,7 @@ getAlocationLimitR =
|
|||||||
defaultLayout $ [whamlet|
|
defaultLayout $ [whamlet|
|
||||||
<p> this will trigger https://hackage.haskell.org/package/base-4.16.0.0/docs/Control-Exception.html#t:AllocationLimitExceeded
|
<p> this will trigger https://hackage.haskell.org/package/base-4.16.0.0/docs/Control-Exception.html#t:AllocationLimitExceeded
|
||||||
which we need to catch
|
which we need to catch
|
||||||
|]) `finally` (liftIO $ Mem.disableAllocationLimit)
|
|]) `finally` liftIO Mem.disableAllocationLimit
|
||||||
|
|
||||||
-- this handler kills it's own thread
|
-- this handler kills it's own thread
|
||||||
getThreadKilledR :: Handler Html
|
getThreadKilledR :: Handler Html
|
||||||
@ -137,6 +139,14 @@ getThreadKilledR = do
|
|||||||
liftIO $ Async.withAsync (Conc.killThread x) Async.wait
|
liftIO $ Async.withAsync (Conc.killThread x) Async.wait
|
||||||
pure "unreachablle"
|
pure "unreachablle"
|
||||||
|
|
||||||
|
getAsyncSessionR :: Handler Html
|
||||||
|
getAsyncSessionR = do
|
||||||
|
setSession "jap" $ foldMap (pack . show) [0..999999999999999999999999] -- it's going to take a while to figure this one out
|
||||||
|
x <- liftIO Conc.myThreadId
|
||||||
|
liftIO $ forkIO $ do
|
||||||
|
liftIO $ Conc.threadDelay 100_000
|
||||||
|
Conc.killThread x
|
||||||
|
pure "reachable"
|
||||||
|
|
||||||
getErrorR :: Int -> Handler ()
|
getErrorR :: Int -> Handler ()
|
||||||
getErrorR 1 = setSession undefined "foo"
|
getErrorR 1 = setSession undefined "foo"
|
||||||
@ -183,11 +193,11 @@ errorHandlingTest = describe "Test.ErrorHandling" $ do
|
|||||||
it "accept video, bad method -> 405" caseVideoBadMethod
|
it "accept video, bad method -> 405" caseVideoBadMethod
|
||||||
it "thread killed = 500" caseThreadKilled500
|
it "thread killed = 500" caseThreadKilled500
|
||||||
it "allocation limit = 500" caseAllocationLimit500
|
it "allocation limit = 500" caseAllocationLimit500
|
||||||
|
it "async session exception = 500" asyncSessionKilled500
|
||||||
|
|
||||||
runner :: Session a -> IO a
|
runner :: Session a -> IO a
|
||||||
runner f = toWaiApp App >>= runSession f
|
runner f = toWaiApp App >>= runSession f
|
||||||
|
|
||||||
|
|
||||||
caseNotFound :: IO ()
|
caseNotFound :: IO ()
|
||||||
caseNotFound = runner $ do
|
caseNotFound = runner $ do
|
||||||
res <- request defaultRequest
|
res <- request defaultRequest
|
||||||
@ -333,3 +343,9 @@ caseThreadKilled500 = runner $ do
|
|||||||
res <- request defaultRequest { pathInfo = ["thread-killed"] }
|
res <- request defaultRequest { pathInfo = ["thread-killed"] }
|
||||||
assertStatus 500 res
|
assertStatus 500 res
|
||||||
assertBodyContains "Internal Server Error" res
|
assertBodyContains "Internal Server Error" res
|
||||||
|
|
||||||
|
asyncSessionKilled500 :: IO ()
|
||||||
|
asyncSessionKilled500 = runner $ do
|
||||||
|
res <- request defaultRequest { pathInfo = ["async-session"] }
|
||||||
|
assertStatus 500 res
|
||||||
|
assertBodyContains "Internal Server Error" res
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user