我花了很多时间来解决我正在处理的应用程序中遇到的问题。此应用程序是一个Web应用程序,使用scotty暴露REST端点。它使用TVar
来保持其状态,该状态通过前端层触发的STM a
操作进行更新。
由于此应用程序基于事件源原则,因此在 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
。怀疑序列化程序,我花了几个小时来确定罪魁祸首并解决问题。
虽然我当然对错误负全部责任(我应该对我的序列化程序进行更彻底的测试!),但我觉得这很容易产生误导。我想更好地了解此错误的来源以及如何防止它。我已经阅读了关于这个主题的Edward Yang's帖子,this mail thread但我必须承认导致观察到这个错误的逻辑事件链对我来说并不完全清楚。
我想我理解调用applyCommand
的线程,它是由scotty产生的,死于某些异常(堆栈耗尽?)在评估storeEvent
时启动,但我不明白这是如何与交易是垃圾。
答案 0 :(得分:3)
异常说一个线程试图进行一次交易,然后点击retry
,这会在发生变化时重新运行该事务。但它等待更改的事情不再在任何地方引用,因此重试可能永远不会发生。那是一个错误。基本上这个线程现在挂了。
我想想象某个线程某处假设来更新这个TVar
,但是由于异常而死了,从而丢弃了对它的最后一个引用TVar
并引发异常。
我认为发生的事情。没有看到整个应用程序,很难确定。