Deadlock detection too eager in the presence of `newStablePtr` and `apply_sp`
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.)
I don't think there are any guarantees if you use apply_sp in a threaded setting. I asked about this, and you said there was going to be no multi threading. At least that's how I interpreted the answer.
I will have to think about how to make that work.
I agree that there should be no deadlock here. I'll fix that. I need to scan the StablePtr table for MVars.
I think apply_sp could be written in Haskell for GHC and exported with foreign export.
To flesh out my comments above. It seems that you have one thread executing the MicroHs runtime and another thread in JS that sometimes calls apply_sp. This is definitely not allowed. The only way apply_sp is guaranteed to work is
- a single thread executes MicroHs runtime
- MicroHs Haskell code calls JS
- JS calls back using
apply_sp
If there are multiple threads things can go very wrong, because there is no lock protecting the RTS data structures. It assumes a single thread.
If you need multiple threads I can probably make a hack for that and have a pthread mutex protecting the RTS data structures. I think I can make the overhead really small by having the RTS hold the mutex almost all the time.
I asked about this, and you said there was going to be no multi threading. At least that's how I interpreted the answer.
Sorry about my confusion. 🙈 What I meant was that I don't need concurrency in the sense that at any point in time, I only need one Haskell thread to something useful while all other Haskell threads are waiting on some blocked MVars. In this way, the program is not sequential in the strict sense, but it does have a well-defined sequence. I think that I need multi-threading in order to have more control over that sequence.
Essentially, I want to use Haskell threads to decouple
- JS calls Haskell code using
apply_sp→ MicroHs Haskell code calls JS code
into
- JS calls Haskell code using
apply_sp→ write call arguments in aTQueue - MicroHs code reads
TQueue→ MicroHs Haskell code calls JS code
At least, this is what the web server version of threepenny-gui does, and I'm seeing some differences in semantics for the CurrencyConverter example which — I think — are due to the different implementation that uses the MicroHs JS FFI.
The only way apply_sp is guaranteed to work is
- a single thread executes MicroHs runtime
- MicroHs Haskell code calls JS
- JS calls back using apply_sp
Ah. 🤔 I was hoping that I could use Haskell threads to pause JS calls from apply_sp and pick them up later. But it seems that the main Haskell threads needs to run to completion before apply_sp can even be called? 🤔
On the other hand, maybe I can use JS code to do the shepherding of calls to apply_sp? 🤔
Also, I think I need to write down more clearly what my desired sequence of calls is. 😅 Essentially, I suspend execution in order to break nested JS → Haskell → JS → Haskell → … calls.
I still don't understand your situation. Are there multiple (non-Haskell) threads?
Something I didn't spell out is that the code executed by apply_sp must not suspend. If it does, then crazy things will happen.
I think that you might want more than what is currently supported, where FFI calls can suspend and other Haskell threads can continue running.
I still don't understand your situation. Are there multiple (non-Haskell) threads?
Let me try to illustrate my situation with a picture.
The following picture contains four distinct diagrams labeled 1) to 4).
Each diagram contains two vertical gray lines labeled "JS" and "Haskell". These represent the JS and Haskell runtimes, respectively. These runtimes run in separate threads (processes) for the "web server" variant of threepenny-gui, but for the "MicroHs" variant of threepenny-gui, the JS runtime and the Haskell runtime currently run in a single thread.
In each diagram,
- a vertical solid black line indicates the passage of time due to some computation
- a solid horizontal arrow indicates a procedure call with a transfer of control to the other runtime
- a solid horizontal line indicates giving back control to the caller
- a dashed horizontal line indicates a concurrent procedure call, with immediate return of control to the caller (think
forkIO).
Diagram 1) shows the default situation where the main Haskell program starts and calls a JS function every now and then.
Diagram 2) shows an optimization that makes sense for the "web server" variant of threepenny-gui: For some JS procedures, the Haskell side only cares about the side effect, not the return value, and we "fire and forget" their execution in the JS runtime.
Diagram 3) represents the situation where the main Haskell program has ended, but certain UI events, such as clicking a mouse button or entering characters in an input box triggers the execution of Haskell code. Here, the JS runtime calls apply_sp and Haskell code calls a couple of JS procedures to show the effects of the button click.
Note that the diagram 3) represents two levels of "nesting": One arrow is followed by a second arrow without a horizontal solid line in between that would give back control to the procedure caller.
Diagram 4) shows how the "web server" variant of threepenny-gui works around the nesting in 3): As I don't want to support nested procedure calls, I treat the initial call from JS to Haskel as a "fire and forget" call. This works for events such as button clicks as these situations do not expect a return value.
For the "MicroHs" variant of threepenny-gui, we have the following situation:
- Diagram 1) works.
- Diagram 2) is something that I need to implement for compatibility reasons. I think I can do this with multi-threading in Haskell-land.
- Diagram 3) or diagram 4) are necessary in order to support events like button clicks.
In the context of this Github issue here, I was trying to implement 4) by using multi-threading that can switch between the JS runtime and the Haskell runtime, which may be misguided.
I just realized that apply_sp has a bad bug. It can randomly fail to return to the caller. I'll fix that soon. And I'll think more about your use case. I might have to bite the bullet and use pthreads to get multiple threads for use in FFI calls.
I just realized that, in principle, it is possible to exchange information between the JavaScript runtime and the Haskell runtime without foreign import or foreign export — by mimicking inter-process communication and exchanging messages via stdin and stdout. This would essentially match what the "web server" variant of threepenny-gui is currently doing (except that it's using HTTP instead of stdin/stdout), and would be a standard thing to do in a C/UNIX environment.
But unfortunately, the above idea requires preemptive multitasking — and as far as I can tell, the JavaScript runtime in browsers uses co-operative multitasking. Ouch. 🙈 I will investigate further.