I'm currently trying to port parts of the Windows API to Haskell. One of the functions considered is SetConsoleCtrlHandler , which registers a callback for a handler that is called whenever Ctrl + C or Ctrl + Break comes from the console input. So, here is a small sample program:
{-# LANGUAGE CPP #-} {-# LANGUAGE ForeignFunctionInterface #-} module Main where import Control.Monad import GHC.ConsoleHandler import Foreign import System.Win32.Process import System.Win32.Types foreign import ccall "wrapper" makeHandlerPtr :: (DWORD -> IO BOOL) -> IO (FunPtr (DWORD -> IO BOOL)) foreign import stdcall "windows.h SetConsoleCtrlHandler" c_SetConsoleCtrlHandler :: FunPtr (DWORD -> IO BOOL) -> BOOL -> IO BOOL main :: IO () main = unsafeCtrl unsafeCtrl :: IO () unsafeCtrl = do ptrHandler <- makeHandlerPtr $ \d -> do putStrLn $ "received event " ++ show d return True c_SetConsoleCtrlHandler ptrHandler True forM_ [1..50] $ \_ -> sleep 100 freeHaskellFunPtr ptrHandler safeCtrl :: IO () safeCtrl = do installHandler $ Catch $ \e -> do putStrLn $ "received event " ++ show e forM_ [1..50] $ \_ -> sleep 100
If you compile the above program through ghc --make AsyncCallback.hs and run the program, it will exit with the following error as soon as a control event is received:
$ AsyncCallback AsyncCallback.exe: schedule: re-entered unsafely. Perhaps a 'foreign import unsafe' should be 'safe'?
However, compiling with the -threaded option -threaded seems fine:
$ AsyncCallback received event 0 received event 1 received event 0
Not sure why this makes it work, since I am not using the thread here explicitly, and I do not see where the GHC will implicitly start a new thread.
When asking about this at #haskell, someone noticed that calling Haskell functions asynchronously from a C callback is inherently unsafe and that the above example works more or less. I also pointed to GHC.ConsoleHandler.installHandler , which looks like a safe wrapper around SetConsoleCtrlHandler , and running the above program with main = safeCtrl really works fine. I tried to understand the implementation of installHandler ( C-side and Haskell-side ), but I donβt quite get this.
The whole problem here is that the callback is issued asynchronously, so the Haskell RTS is not βpreparedβ when the callback tries to run the Haskell code. Therefore i would like to know
- How does the GHC
installHandler implementation installHandler ? What is the key part that makes this implementation work and my version doesn't work? - Are there other options / common patterns, how to call Haskell code asynchronously from a C callback? In this particular case, I can switch to the GHC implementation. But I may run into similar C functions that asynchronously go into Haskell code, where I have to write secure binding myself.
asynchronous callback haskell ffi
siracusa
source share