STM retry坑一例

May 6, 2013

今天凌晨,用long-polling做聊天功能,服务器端直接用TChan广播:

get "/recv" $ do
    msg <- liftIO $ atomically $
        c <- dupTChan chan
        readTChan c
    json (msg::Message)

我想当然的在一个atomically里用dupTChan复制一个全局的TChan,然后用readTChan读数据,最后把结果转成json返回给客户端。因为readTChanTChan没数据的时候会阻塞直到有数据,所以这里就实现了long-polling。

但是,测试的时候发现即使有向全局的chan写入数据,请求/recv还是会一直阻塞,检查一遍JS之后没发现问题,再看TChan的文档才发现问题所在。

dupTChan复制得到的TChan是空的,所以后面紧跟的readTChan一定会通过retry阻塞。retry的时候会重新执行事务,dupTChan又得到一个空TChan,然后就死循环了。Haskell的retry又挺厉害,不会真的在这里死循环,CPU使用也没太大变化,所以很难发现。

所以这里要分成两个事务:

get "/recv" $ do
    c   <- liftIO $ atomically $ dupTChan chan
    msg <- liftIO $ atomically $ readTChan c
    json (msg::Message)