我有一个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
)a 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
,然后catch
将其捕获ServantErr
并制定回滚功能。相反,发生的事情是插入成功,InvalidFileError
记录了日志,但是回滚从未发生(即,执行后记录仍存在于数据库中)。此deleteRecord
函数已在其他地方成功使用,因此可以确定它的定义不是问题。
知道是什么原因造成的吗?
如果您的App
类型最终是ExceptT
,则问题可能是MonadError
和的MonadCatch
实例ExceptT
不匹配:
MonadError
实例会引发错误的e
在ExceptT e
MonadCatch
实例在基础monad中捕获异常,而不是ExceptT e
错误。的实例定义为MonadCatch (ExceptT e m)
:
-- | Catches exceptions from the base monad.
instance MonadCatch m => MonadCatch (ExceptT e m) where
catch (ExceptT m) f = ExceptT $ catch m (runExceptT . f)
ServantErr
有一个Exception
实例,因此可以将两者同时抛出。
编辑:“ exceptions”类MonadMask
提供了onError
功能,即使对于ExceptT
它来说也表现得很好:在ExceptT e
异常和常规异常的情况下,它都运行清除操作:
仅在主操作中引发错误时才运行操作。与onException不同,这适用于各种错误,而不仅仅是异常。例如,如果f是用Left终止的ExceptT计算,则onError fg计算将执行g,而onException fg则不会。
这比catch
处理回滚更好。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句