我花了很多时间对正在处理的应用程序中的问题进行故障排除。该应用程序是一个Web应用程序,使用scotty公开REST端点。它使用aTVar
来保持其状态,该状态通过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)关于该主题的帖子,以及该邮件主题,但是我必须承认导致观察到此错误的逻辑事件链对我来说并不完全清楚。
我想我理解applyCommand
由scotty生成的线程调用死于评估时启动的某些异常(堆栈耗尽吗?)storeEvent
,但是我不明白这与事务成为垃圾有关。
唯一的例外说一个线程试图做一个交易,并且命中retry
,这将重新运行该交易改变的东西时。但是它等待更改的内容不再在任何地方引用,因此重试永远不会发生。那是一个错误。基本上,该线程现在已挂起。
我可以想象某个地方的某个线程应该对此进行更新TVar
,但是它由于异常而死亡,从而删除了对该位置的最后引用TVar
并引发了异常。
我就是这么想的。如果没有看到整个应用程序,就很难确定。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句