我花了很多時間解決我在我正在處理的應用程序中遇到的問題。此應用程序是一個Web應用程序,使用scotty公開REST端點。它使用TVar
來保持其狀態,該狀態通過由前端層觸發的STM a
動作更新。如何處理或避免BlockedIndefinitelyOnSTM異常?
由於這個應用程序是基於事件的採購原則,經過 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
,這是由斯科蒂催生,從一些異常(堆棧用盡?),而評估storeEvent
推出死了,但我不明白這是怎麼與交易相關的是垃圾。
感謝您的洞察力。我可以理解,如果'主'線程死亡,那麼'TVar'不再被引用,但是我不明白'重試'應該是在哪裏。我不明確地在我的代碼中使用'retry',所以這必須是隱含的... – insitu 2014-09-30 12:47:24
你使用'TChan'還是什麼?這將在內部使用'retry'來阻塞,直到通道中有數據可用。 – MathematicalOrchid 2014-09-30 13:58:48
這就是令我困惑的原因:我不使用TChan,只使用readTVar,writeTVar,modifyTVar。 – insitu 2014-09-30 14:38:43