我正在使用Wai运行一个haskell websocket服务器:
application :: MVar ServerState -> Wai.Application
application state = WaiWS.websocketsOr WS.defaultConnectionOptions wsApp staticApp
where
wsApp :: WS.ServerApp
wsApp pendingConn = do
conn <- WS.acceptRequest pendingConn
talk conn state
为了允许单个客户端发送异步消息,talk定义如下:
talk :: WS.Connection -> MVar ServerState -> IO ()
talk conn state = forever $ do
msg <- WS.receiveMessage conn
putStrLn "received message"
successLock <- newEmptyMVar
tid <- timeoutAsync successLock $ processMessage c state msg
putStrLn "forked thread"
modifyMVar_ state $ \curState ->
return $ curState & threads %~ (M.insert mid tid) -- thread bookkeeping
putStrLn "modified state"
putMVar successLock ()
putStrLn "unlocked success"
where
mid = serverMessageId msg
timeoutAsync lock f = forkIO $ do
timeout S.process_message_timeout onTimeout (onSuccess lock) f
onSuccess lock = do
-- block until the first modifyMVar_ above finishes.
takeMVar lock
modifyMVar_ state $ \curState ->
return $ curState & threads %~ (M.delete mid) -- thread cleanup
onTimeout = ...
事情就是这样:当我用大量CPU消息(来自单个客户端)轰炸这个服务器时,主线程偶尔会挂起“分叉线程”。
这是令人惊讶的,因为所有关于消息的工作(理论上)都是在不同的线程中完成的,因此主线程(forever
)永远不应该阻止。
这里发生了什么?
[编辑]
在这种情况下,很难提供最小的可验证示例(工作在processMessage
完成,但包含许多移动部件,其中任何一个都可能是问题)。相反,我正在寻找可以调查的事情的高级指针。
以下是来自示例运行的数据(向服务器发送一个昂贵的请求,然后是一堆较小的较便宜的请求):
gc生产率36%:http://puu.sh/nSxnj/d8bb5995ae.png
事件日志(使用+RTS -ls
和-eventlog
):http://puu.sh/nSxDy/efe457bee2.eventlog
CPU使用率~300%(4个上限) - 让我觉得GC可能会与OS资源竞争;我将num功能减少到n-1
,这似乎提高了响应能力
此外,该应用程序具有以下属性,我认为这是导致问题的潜在原因:
GC'd与实时数据的比率很高; processMessage
基本上构建了一个巨大的列表aeson'd
并发回给用户,但没有保持状态
在一次请求中发出了许多外国电话(由于ZMQ,iirc发出不安全的外国电话)
ThreadScope告诉我发生了很多堆溢出,导致GC请求