Skip to content

Display action registry exceptions in human-readable format #529

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 43 additions & 5 deletions src-control/Control/ActionRegistry.hs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ module Control.ActionRegistry (
, withActionRegistry
, unsafeNewActionRegistry
, unsafeFinaliseActionRegistry
, CommitActionRegistryError
, AbortActionRegistryError
, CommitActionRegistryError (..)
, AbortActionRegistryError (..)
, AbortActionRegistryReason
-- * Registering actions #registeringActions#
-- $registering-actions
Expand Down Expand Up @@ -61,6 +61,23 @@ import GHC.Stack
#define HasCallStackIfDebug ()
#endif

{-------------------------------------------------------------------------------
Printing utilities
-------------------------------------------------------------------------------}

tabLines1 :: String -> String
tabLines1 = tabLinesN 1

#ifdef NO_IGNORE_ASSERTS
tabLines2 :: String -> String
tabLines2 = tabLinesN 2
#endif

tabLinesN :: Int -> String -> String
tabLinesN n = unlines . fmap (ts++) . lines
where
ts = concat $ replicate n " "

{-------------------------------------------------------------------------------
Modify mutable state
-------------------------------------------------------------------------------}
Expand Down Expand Up @@ -216,7 +233,15 @@ data Action m = Action {

data ActionError = ActionError SomeException CallStack
deriving stock Show
deriving anyclass Exception

instance Exception ActionError where
displayException (ActionError err registerSite) = unlines [
"A registered action threw an error: "
, tabLines1 "The error:"
, tabLines2 (displayException err)
, tabLines1 "Registration site:"
, tabLines2 (prettyCallStack registerSite)
]

mkAction a = Action a callStack

Expand Down Expand Up @@ -305,7 +330,13 @@ unsafeCommitActionRegistry reg = do

data CommitActionRegistryError = CommitActionRegistryError (NonEmpty ActionError)
deriving stock Show
deriving anyclass Exception

instance Exception CommitActionRegistryError where
displayException (CommitActionRegistryError es) = unlines $ [
"Exceptions thrown while committing an action registry."
] <> NE.toList (fmap displayOne es)
where
displayOne e = tabLines1 (displayException e)

{-# SPECIALISE unsafeAbortActionRegistry ::
ActionRegistry IO
Expand Down Expand Up @@ -338,7 +369,14 @@ data AbortActionRegistryReason =
data AbortActionRegistryError =
AbortActionRegistryError AbortActionRegistryReason (NonEmpty ActionError)
deriving stock Show
deriving anyclass Exception

instance Exception AbortActionRegistryError where
displayException (AbortActionRegistryError reason es) = unlines $ [
"Exceptions thrown while aborting an action registry."
, ("Reason for aborting the registry: " ++ show reason)
] <> NE.toList (fmap displayOne es)
where
displayOne e = tabLines1 (displayException e)

{-# SPECIALISE runActions :: [Action IO] -> IO [ActionError] #-}
-- | Run all actions even if previous actions threw exceptions.
Expand Down
9 changes: 7 additions & 2 deletions test-control/Test/Control/ActionRegistry.hs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ prop_commitActionRegistryError = once $ ioProperty $ do
delayedCommit reg
(throwIO (userError "delayed action failed"))
pure $ case eith of
Left e -> tabulate "Printed error" [show e] $ property True
Left e ->
tabulate "displayException" [displayExceptionNewline e] $ property True
Right () -> property False

-- | An example where an exception happens while an action registry is being
Expand All @@ -36,5 +37,9 @@ prop_abortActionRegistryError = once $ ioProperty $ do
(\_ -> throwIO (userError "rollback action failed"))
throwIO (userError "error in withActionRegistry scope")
pure $ case eith of
Left e -> tabulate "Printed error" [show e] $ property True
Left e ->
tabulate "displayException" [displayExceptionNewline e] $ property True
Right () -> property False

displayExceptionNewline :: Exception e => e -> String
displayExceptionNewline e = '\n':displayException e
47 changes: 42 additions & 5 deletions test/Database/LSMTree/Model/Session.hs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ import Database.LSMTree.Model.Table (LookupResult (..),
QueryResult (..), Range (..), ResolveSerialisedValue (..),
Update (..), getResolve, noResolve)
import qualified Database.LSMTree.Model.Table as Model
import GHC.Show (appPrec)

{-------------------------------------------------------------------------------
Model
Expand Down Expand Up @@ -232,7 +233,7 @@ runModelMWithInjectedErrors ::
runModelMWithInjectedErrors Nothing onNoErrors _ st =
runModelM onNoErrors st
runModelMWithInjectedErrors (Just _) _ onErrors st =
runModelM (onErrors >> throwError ErrFsError) st
runModelM (onErrors >> throwError (ErrFsError "modelled FsError")) st

--
-- Errors
Expand All @@ -245,11 +246,47 @@ data Err =
| ErrSnapshotWrongType
| ErrBlobRefInvalidated
| ErrCursorClosed
-- | Passed zero tables to 'unions'
| ErrUnionsZeroTables
-- | Some file system error occurred
| ErrFsError
deriving stock (Show, Eq)
| ErrFsError String

instance Show Err where
showsPrec d = \case
ErrTableClosed ->
showString "ErrTableClosed"
ErrSnapshotExists ->
showString "ErrSnapshotExists"
ErrSnapshotDoesNotExist ->
showString "ErrSnapshotDoesNotExist"
ErrSnapshotWrongType ->
showString "ErrSnapshotWrongType"
ErrBlobRefInvalidated ->
showString "ErrBlobRefInvalidated"
ErrCursorClosed ->
showString "ErrCursorCosed"
ErrFsError s ->
showParen (d > appPrec) $
showString "ErrFsError " .
showParen True (showString s)

instance Eq Err where
(==) ErrTableClosed ErrTableClosed = True
(==) ErrSnapshotExists ErrSnapshotExists = True
(==) ErrSnapshotDoesNotExist ErrSnapshotDoesNotExist = True
(==) ErrSnapshotWrongType ErrSnapshotWrongType = True
(==) ErrBlobRefInvalidated ErrBlobRefInvalidated = True
(==) ErrCursorClosed ErrCursorClosed = True
(==) (ErrFsError _) (ErrFsError _) = True
(==) _ _ = False
where
_coveredAllCases x = case x of
ErrTableClosed{} -> ()
ErrSnapshotExists{} -> ()
ErrSnapshotDoesNotExist{} -> ()
ErrSnapshotWrongType{} -> ()
ErrBlobRefInvalidated{} -> ()
ErrCursorClosed{} -> ()
ErrFsError{} -> ()


{-------------------------------------------------------------------------------
Tables
Expand Down
40 changes: 33 additions & 7 deletions test/Test/Database/LSMTree/StateMachine.hs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuantifiedConstraints #-}
Expand Down Expand Up @@ -64,11 +65,14 @@ module Test.Database.LSMTree.StateMachine (
, Action (..)
) where

import Control.ActionRegistry (AbortActionRegistryError (..),
CommitActionRegistryError (..))
import Control.Concurrent.Class.MonadMVar.Strict
import Control.Concurrent.Class.MonadSTM.Strict
import Control.Monad (forM_, void, (<=<))
import Control.Monad.Class.MonadThrow (Handler (..), MonadCatch (..),
MonadThrow (..), catches)
import Control.Monad.Class.MonadThrow (Exception (..), Handler (..),
MonadCatch (..), MonadThrow (..), catches,
displayException)
import Control.Monad.IOSim
import Control.Monad.Primitive
import Control.Monad.Reader (ReaderT (..))
Expand Down Expand Up @@ -160,7 +164,7 @@ tests = testGroup "Test.Database.LSMTree.StateMachine" [
Handler f -> do
throwIO (dummyFsError s) `catch` \e -> do
e' <- f e
pure (e' QC.=== Just Model.ErrFsError)
pure (e' QC.=== Just (Model.ErrFsError ("dummy: " ++ s)))
]

labelledExamples :: IO ()
Expand Down Expand Up @@ -284,7 +288,11 @@ propLockstep_RealImpl_RealFS_IO tr =
env :: RealEnv R.Table IO
env = RealEnv {
envSession = session
, envHandlers = [realHandler @IO, fsErrorHandler]
, envHandlers = [
realHandler @IO
, fsErrorHandler
, actionRegistryErrorHandler
]
, envErrors = errsVar
, envInjectFaultResults = faultsVar
}
Expand Down Expand Up @@ -321,7 +329,11 @@ propLockstep_RealImpl_MockFS_IO tr =
env :: RealEnv R.Table IO
env = RealEnv {
envSession = session
, envHandlers = [realHandler @IO, fsErrorHandler]
, envHandlers = [
realHandler @IO
, fsErrorHandler
, actionRegistryErrorHandler
]
, envErrors = errsVar
, envInjectFaultResults = faultsVar
}
Expand All @@ -346,7 +358,11 @@ propLockstep_RealImpl_MockFS_IOSim tr actions =
env :: RealEnv R.Table (IOSim s)
env = RealEnv {
envSession = session
, envHandlers = [realHandler @(IOSim s), fsErrorHandler]
, envHandlers = [
realHandler @(IOSim s)
, fsErrorHandler
, actionRegistryErrorHandler
]
, envErrors = errsVar
, envInjectFaultResults = faultsVar
}
Expand Down Expand Up @@ -423,7 +439,17 @@ fsErrorHandler :: Monad m => Handler m (Maybe Model.Err)
fsErrorHandler = Handler $ pure . handler'
where
handler' :: FsError -> Maybe Model.Err
handler' _ = Just Model.ErrFsError
handler' e = Just (Model.ErrFsError (displayException e))

actionRegistryErrorHandler :: Monad m => Handler m (Maybe Model.Err)
actionRegistryErrorHandler = Handler $ \e -> pure $
if
| Just AbortActionRegistryError{} <- fromException e
-> Just (Model.ErrFsError (displayException e))
| Just CommitActionRegistryError{} <- fromException e
-> Just (Model.ErrFsError (displayException e))
| otherwise
-> Nothing

createSystemTempDirectory :: [Char] -> IO (FilePath, HasFS IO HandleIO, HasBlockIO IO HandleIO)
createSystemTempDirectory prefix = do
Expand Down
Loading