我有一个Servant应用程序和一个端点,该端点在数据库中创建一条记录,然后尝试在S3位置之间复制文件。如果复制失败,我想回滚事务。我有这个接线员
{-# LANGUAGE TemplateHaskell #-}
import Control.Monad.Catch
import Control.Monad.Except
import Control.Monad.Logger
(<??)
:: (MonadError e m, MonadCatch m, MonadLogger m)
=> e
-> m a
-> m a
(<??) err a = a `catchAll` (\e -> $(logErrorSH) e >> throwError err)
infixr 0 <??
捕获所有异常的,记录异常的性质,然后抛出App
(在我的情况下,因为我的MonadError ServantErr
类型具有ServantErr
的实例)。
我的处理程序是这样的:
{-# LANGUAGE ScopedTypeVariables #-}
import Control.Monad
import Control.Monad.Catch
import Control.Monad.IO.Class
import qualified Network.AWS as AWS
import Servant
import App.Types
import App.Db
copy :: Copy -> App Text
copy (Copy user bucket srcKey tgtKey) = do
err400 <?? runDb (insertRecord $ User user bucket srcKey tgtKey)
catch (err500 <?? liftIO $ do
env <- AWS.newEnv AWS.Discover
void . AWS.runResourceT . AWS.runAWS env $ copyFiles bucket srcKey tgtKey
return "OK") (\(e :: ServantErr) -> rollback e user)
where rollback e u = runDb (deleteRecord u) >> throwError e
为了测试逻辑,我移动了我的AWS凭证文件,期望内部AWS操作将引发InvalidFileError
,然后(<??)
会将其转换为ServantErr
,然后转换为{{ 1}}将捕获此catch
并执行回滚功能。相反,发生的事情是插入成功,记录了ServantErr
,但之后从未发生回滚(即,执行后记录仍存在于数据库中)。此InvalidFileError
函数已在其他地方成功使用,因此可以确定它的定义不是问题。
有什么想法会导致这种情况吗?
答案 0 :(得分:5)
如果您的App
类型最终是ExceptT
,则问题可能是MonadError
的{{1}}和MonadCatch
实例不匹配:
ExceptT
实例引发错误,因为MonadError
中的e
ExceptT e
实例捕获底层单子中的异常,而不是MonadCatch
错误。 ExceptT e
的{{3}}是:
MonadCatch (ExceptT e m)
instance definition有一个ServantErr
实例,因此可以将两者同时抛出。
编辑:“ exceptions”类-- | Catches exceptions from the base monad.
instance MonadCatch m => MonadCatch (ExceptT e m) where
catch (ExceptT m) f = ExceptT $ catch m (runExceptT . f)
提供了Exception
函数,即使对于MonadMask
来说也表现得很好:它运行清理操作在ExceptT
异常和常规异常中都是这样:
仅在主操作中引发错误时才执行操作。不像 onException,它适用于各种错误,而不仅仅是异常。 例如,如果f是一个ExceptT计算,并因Left而中止, 计算onError f g将执行g,而onException f g将执行 不是。
与ExceptT e
相比,它是处理回滚的更好选择。