为什么`catch`不能捕获这个异常?

时间:2019-03-30 17:35:05

标签: haskell

我有一个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函数已在其他地方成功使用,因此可以确定它的定义不是问题。

有什么想法会导致这种情况吗?

1 个答案:

答案 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相比,它是处理回滚的更好选择。