如何在不终止 yesod-websocket 连接的情况下停止收听消息?

How to stop listening for a message without killing a yesod-websocket connection?

我正在使用 yesod-websockets 编写一个应用程序,每当我收到一条 "Start" 消息时,我需要 运行 一个不断产生数据并将其发送到客户端的线程,直到客户告诉它停止。生产者也可能会自行停止生产数据。

当生产者停止时(不管它是自己停止生产,还是客户告诉它停止),它会回到主循环并等待接收另一个 "Start" 消息。

代码看起来像这样(运行下方可实现最小重现):

wsApp :: WebSocketsT Handler ()
wsApp = 
  forever $ do
    msg <- receiveMsg
    case msg of
      StartMsg -> do
        race_
          (produceData)
          (whileM ((/= StopMsg) <$> receiveMsg))

问题是,如果produceData自行停止,那么线程运行ning receiveMsg将被取消,这会导致websocket连接被关闭。

21/Mar/2018:08:21:06 +0000 [Error#yesod] Exception from Warp: ConnectionClosed @(app-0.0.0-5bzI9Onrk2fFepGGsdocDz:Application src/Application.hs:122:15)

有没有办法在不终止连接的情况下取消正在侦听连接的线程?

这是一个最小的重现:

wsApp :: WebSocketsT Handler ()
wsApp = forever $ do
  race_
    (receiveData :: WebSocketsT Handler Text)
    (pure ())
  $logDebug "Trying again"

第二个线程将完成,第一个线程将被取消,导致连接被终止。

由于找不到更优雅的解决方案,我最终使用 IORef Bool 来同步 websockets 线程和生产者线程。

wsApp :: WebSocketsT Handler ()
wsApp = do
  producing <- newIORef False
  forever $ do
    msg <- receiveMsg
    case msg of
      StartMsg -> do
        whenM (not <$> readIORef producing) $ do
          atomicWriteIORef producing True
          void . async $ produceData producing
      StopMsg -> atomicWriteIORef producing False

produceData :: IORef Bool -> WebSocketsT Handler ()
produceData producing =
  whenM (readIORef producing) $
    case produce of
      Nothing -> atomicWriteIORef producing False
      Just x  -> sendMsg x >> produceData producing