我正在使用 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 密集型消息(来自单个客户端)轰炸这个服务器时,主线程偶尔会挂在 "forked thread"上。
这是令人惊讶的,因为所有关于消息的工作(理论上)都是在单独的线程中完成的,因此主线程 ( forever
)永远不应该阻塞。
这里发生了什么?
[编辑]
在这种情况下,很难提供一个最小的可验证示例(工作在 中完成processMessage
,但包含许多活动部件,其中任何一个都可能是问题)。相反,我正在寻找我可以调查的事情的高级指针。
这是来自示例运行的数据(向服务器发送一个昂贵的请求,然后是一堆较小的较便宜的请求):
gc 生产力 36%: http: //puu.sh/nSxnj/d8bb5995ae.png
事件日志(使用
+RTS -ls
和-eventlog
): http: //puu.sh/nSxDy/efe457bee2.eventlogCPU 使用率 ~300%(4 个上限)——让我觉得 GC 可能会与操作系统资源竞争;我将 num 功能降低到
n-1
,这似乎提高了响应能力
此外,该应用程序具有以下属性,我认为这是问题的潜在原因:
GC'd 与实时数据的比率很高;
processMessage
基本上构建了一个巨大的列表,该列表被aeson'd
发送回用户,但不保持状态在单个请求上进行了许多外部调用(由于ZMQ ,iirc 进行了不安全的外部调用)
ThreadScope 告诉我发生了很多 heapoverflow,导致 GC 请求