Skip to content

Deadlock detection too eager in the presence of newStablePtr and apply_sp #300

@HeinrichApfelmus

Description

@HeinrichApfelmus

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

Start of main thread
0x1

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.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions