Ouch. I think I found an issue with StablePtr and concurrency.
While working on threepenny-gui again, I find myself in a situation where I need to run an event loop in a thread (that is separate from the main thread) that reads events from a TQueue. Every now and then, the JavaScript side calls apply_sp to write an event to the queue. Thanks to concurrency, the event loop will pick it up at the appropriate time — typically only after the call to apply_sp has finished.
The issue is this: The single occurrence of writeTQueue events is exported to the JavaScript side via newStablePtr. The event loop uses readTQueue events, but the Haskell side thinks that this is a deadlock, because it cannot see that the JavaScript can, in principle, still write to the events :: TQueue.
Long story short: I expect that the following program
import Control.Monad
( join )
import Control.Concurrent.MVar
( MVar, newEmptyMVar, putMVar, takeMVar )
import Control.Concurrent
( forkFinally )
import Foreign.StablePtr
( StablePtr, deRefStablePtr, newStablePtr, castStablePtrToPtr )
main :: IO ()
main = do
putStrLn "Start of main thread"
done <- newEmptyMVar
events <- newEmptyMVar
ptr <- newStablePtr $ putMVar events ()
print $ castStablePtrToPtr ptr
forkFinally (eventLoop events) (\_ -> putMVar done ())
-- putMVar events ()
takeMVar done
eventLoop :: MVar () -> IO ()
eventLoop mvar = do
_ <- takeMVar mvar
putStrLn "End of eventLoop"
prints
to the console and hangs. However, the actual behavior is that the program prints
Start of main thread
0x1
ERR: deadlock
and exits.
Uncommenting the line -- putMVar events () makes the program finish. I think this means that events is garbage collected, even though it should be protected by the call to newStablePtr. (EDIT: I'm no longer sure about this claim.)
The print $ castStablePtrToPtr ptr statement is just for show, to indicate that a foreign runtime could use the printed value with apply_sp to put something into the events variable.
(EDIT: Sorry, in the previous version, I think I got the expected result wrong. It's expected for the program to hang instead of detecting a deadlock. GHC does the expected thing.)
Ouch. I think I found an issue with
StablePtrand concurrency.While working on threepenny-gui again, I find myself in a situation where I need to run an event loop in a thread (that is separate from the main thread) that reads events from a
TQueue. Every now and then, the JavaScript side callsapply_spto write an event to the queue. Thanks to concurrency, the event loop will pick it up at the appropriate time — typically only after the call toapply_sphas finished.The issue is this: The single occurrence of
writeTQueue eventsis exported to the JavaScript side vianewStablePtr. The event loop usesreadTQueue events, but the Haskell side thinks that this is a deadlock, because it cannot see that the JavaScript can, in principle, still write to theevents :: TQueue.Long story short: I expect that the following program
prints
to the console and hangs. However, the actual behavior is that the program prints
and exits.
Uncommenting the line
-- putMVar events ()makes the program finish. I think this means thateventsis garbage collected, even though it should be protected by the call tonewStablePtr. (EDIT: I'm no longer sure about this claim.)The
print $ castStablePtrToPtr ptrstatement is just for show, to indicate that a foreign runtime could use the printed value withapply_spto put something into theeventsvariable.(EDIT: Sorry, in the previous version, I think I got the expected result wrong. It's expected for the program to hang instead of detecting a deadlock. GHC does the expected thing.)