MVarとWeakの混合に問題があります。MVarは関数のスコープからアクセス可能ですが(メインスレッドがブロックされています)、Weakはそれをファイナライズする必要があると考えています。この動作は、最適化を有効にしてコンパイルした場合にのみ発生するようです。
プラットフォーム:64ビットLinux
GHCバージョンで再現:6.10.4、6.12.3、7.0.4、7.2.2、7.4.1
module Main (main) where
import Control.Concurrent
import Control.Monad (forever, forM_)
import Data.IORef
import System.Mem
import System.Mem.Weak
dispatchPendingCalls :: IORef [Weak (MVar ())] -> IO ()
dispatchPendingCalls ref = forever $ do
threadDelay 100000
pending <- atomicModifyIORef ref (\p -> ([], p))
forM_ pending (\weak -> do
maybeMVar <- deRefWeak weak
case maybeMVar of
Just mvar -> putMVar mvar ()
Nothing -> putStrLn "dispatchPendingCalls: weak mvar is Nothing")
call :: IORef [Weak (MVar ())] -> IO ()
call ref = do
mvar <- newEmptyMVar
weak <- mkWeakPtr mvar (Just (putStrLn "call: finalising weak"))
putStrLn "call: about to insert weak into list"
atomicModifyIORef ref (\p -> (weak : p, ()))
putStrLn "call: inserted weak into list"
performGC
takeMVar mvar
putStrLn "call: took from mvar"
main :: IO ()
main = do
pendingCalls <- newIORef []
_ <- forkIO (dispatchPendingCalls pendingCalls)
call pendingCalls
期待される出力:
$ ghc --make WeakMvar.hs
$ ./WeakMvar
call: about to insert weak into list
call: inserted weak into list
call: took from mvar
$
実際の出力:
$ ghc --make -O2 WeakMvar.hs
$ ./WeakMvar
call: about to insert weak into list
call: inserted weak into list
call: finalizing weak
dispatchPendingCalls: weak mvar is Nothing
(never exits)
なぜこうなった?私がSystem.Mem.Weak
ドキュメントを正しく読んでいる場合、そのtakeMVar mvar
行はmvarを存続させ、したがってその弱いポインターを有効に保つはずです。代わりに、弱いポインタは、呼び出しが戻る前にMVarに到達できなくなったと見なしtakeMVar
ます。