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

use*_*931 1 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 <??
Run Code Online (Sandbox Code Playgroud)

捕获所有异常,记录异常的性质,然后引发(在我的情况下,因为我的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
Run Code Online (Sandbox Code Playgroud)

为了测试逻辑,我移动了我的AWS凭证文件,期望内部AWS操作将抛出InvalidFileError,然后(<??)将其转换为ServantErr,然后catch将其捕获ServantErr并实施回滚功能。相反,发生的事情是插入成功,InvalidFileError记录了日志,但随后从未发生回滚(即,执行后记录仍存在于数据库中)。此deleteRecord函数已在其他地方成功使用,因此可以确定它的定义不是问题。

知道是什么原因造成的吗?

dan*_*iaz 5

如果您的App类型最终是ExceptT,则问题可能是MonadError和的MonadCatch实例ExceptT不匹配:

  • MonadError实例会引发错误的eExceptT 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)
Run Code Online (Sandbox Code Playgroud)

ServantErr有一个Exception实例,因此可以将两者同时抛出。


编辑:“ exceptions”类MonadMask提供了onError功能,即使对于ExceptT它来说也表现得相当不错:在ExceptT e异常常规异常的情况下,它都运行清除操作:

仅在主操作中引发错误时才运行操作。与onException不同,这适用于各种错误,而不仅仅是异常。例如,如果f是用Left终止的ExceptT计算,则onError fg计算将执行g,而onException fg则不会。

catch处理回滚更好。