我花了很多时间来解决我正在处理的应用程序中遇到的问题。该应用程序是一个 Web 应用程序,使用 scotty 公开 REST 端点。它使用一个TVar
保持其更新的状态STM a
由前端层触发的动作。
由于该应用程序基于事件溯源原则,因此业务层生成的任何事件after完成的 STM 事务存储到EventStore
(目前是一个简单的平面文件...)。这是相关的代码片段:
newtype (EventStore m) => WebStateM s m a = WebStateM { runWebM :: ReaderT (TVar s) m a }
deriving (Functor,Applicative,Monad, MonadIO, MonadTrans, MonadReader (TVar s))
applyCommand :: (EventStore m, Serializable (Event a)) =>
Command a
-> TVar s
-> WebStateM s m (Event a)
applyCommand command = \ v -> do
(e, etype :: EventType s) <- liftIO $ atomically $ actAndApply v
storeEvent e etype
return e
where
actAndApply = \ v -> do
s <- readTVar v
let view = getView s
let e = view `act` command
let bv = view `apply` e
modifyTVar' v (setView bv)
return (e, getType view)
这工作得很完美,直到一个错误溜进了storeEvent
功能。该函数负责使用适当的类型序列化事件,而我在某些类型的序列化例程中犯了一个(严重)错误,导致无限循环!然后突然之间,我的cabal test
开始挂起并因超时而失败(我使用 wreq 作为客户端库来测试 REST 服务)。我花了几个小时才确定服务器端的实际错误:tests: thread blocked indefinitely in an STM transaction
。由于怀疑序列化例程,我又花了几个小时才确定罪魁祸首并解决问题。
尽管我当然对这个错误负全部责任(我应该更彻底地测试我的序列化例程!),但我发现它相当具有误导性。我想更好地了解这个错误从何而来以及如何防止它。我读过了杨德昌的 http://blog.ezyang.com/2011/07/blockedindefinitelyonmvar/关于该主题的帖子,以及这个邮件线程 http://haskell.1045720.n5.nabble.com/Avoiding-BlockedIndefinitelyOnSTM-exceptions-td5752644.html但我必须承认,导致观察到这个错误的事件的逻辑链对我来说并不完全清楚。
我想我理解线程调用applyCommand
,由 scotty 生成,因评估时启动的某些异常(堆栈耗尽?)而死亡storeEvent
,但我不明白这与交易有何关系garbage.