Lazy IO and asynchronous callbacks?

Hello everyone, I'm currently in the process of wrapping a C API, and I've run across an interesting possibility. Basically, the C API exposes non-blocking versions of some potentially long-running operations, and will invoke a callback to indicate that the long running operation has finished. For instance, I have something like: int longRunningReadOperation(int length, byte * buf, void (*callback)()) When callback is called, then there are length bytes in buf. What I'd like to do is wrap this in Haskell function like: longRunningReadOperation :: Int -> IO [Byte] such that the function returns immediately, and only blocks when someone pulls on the results if the callback hasn't been triggered yet. I figure I'm going to need unsafeInterleaveIO, but I'm not sure what to do in the computation I pass to it. I had a small hope that if the callback put the results into an MVar (say var), and I returned (unsafeInterleaveIO (readMVar var)) that might work, but it seems like if the readMVar blocks then the callback doesn't execute either. This doesn't surprise me, but it does leave me needing another option. I don't see a way to get the callback into a new Haskell thread, so that it can do the putMVar from there... am I missing something obvious? Thanks in advance! /g

On Thu, Jul 8, 2010 at 3:25 PM, J. Garrett Morris
Hello everyone,
I'm currently in the process of wrapping a C API, and I've run across an interesting possibility. Basically, the C API exposes non-blocking versions of some potentially long-running operations, and will invoke a callback to indicate that the long running operation has finished. For instance, I have something like:
int longRunningReadOperation(int length, byte * buf, void (*callback)())
I've done something much like this for a MIDI driver. My approach was to pass a haskell callback to the async reader (via "foreign import ccall "wrapper"). The haskell function then shoves its data into a chan. There may very well be a better way to do this, but it definitely feels better to me than relying on unsafeInterleaveIO magic. Your chan reader has to be in IO, but that's as it should be and you can still pass the chunks off to pure functions. initialize :: (ReadChan -> IO a) -> IO a initialize app = do chan <- STM.newTChanIO Exception.bracket (make_read_callback (chan_callback chan)) freeHaskellFunPtr $ \cb -> do check_ =<< c_initialize cb app chan `Exception.finally` terminate foreign import ccall "core_midi_terminate" terminate :: IO () foreign import ccall "core_midi_initialize" c_initialize :: FunPtr ReadCallback -> IO CError foreign import ccall "wrapper" make_read_callback :: ReadCallback -> IO (FunPtr ReadCallback) chan_callback :: ReadChan -> ReadCallback chan_callback chan sourcep ctimestamp len bytesp = do -- Oddly enough, even though ByteString is Word8, the ptr packing function -- wants CChar. bytes <- ByteString.packCStringLen (castPtr bytesp, fromIntegral len) rdev <- deRefStablePtr sourcep let rmsg = Midi.ReadMessage rdev (decode_timestamp ctimestamp) (Parse.decode bytes) STM.atomically $ STM.writeTChan chan rmsg

Hello Evan, Friday, July 9, 2010, 3:01:28 AM, you wrote:
There may very well be a better way to do this, but it definitely feels better to me than relying on unsafeInterleaveIO magic. Your chan reader has to be in IO, but that's as it should be and you can still pass the chunks off to pure functions.
unsafeInterleaveIO isn't magic, it's just a way to convert IO calls to pure lazy list. one should look into getContents implementation in this case longRunningReadOperation should read from the channel when it needs more data -- Best regards, Bulat mailto:Bulat.Ziganshin@gmail.com

On 08/07/2010 23:25, J. Garrett Morris wrote:
Hello everyone,
I'm currently in the process of wrapping a C API, and I've run across an interesting possibility. Basically, the C API exposes non-blocking versions of some potentially long-running operations, and will invoke a callback to indicate that the long running operation has finished. For instance, I have something like:
int longRunningReadOperation(int length, byte * buf, void (*callback)())
When callback is called, then there are length bytes in buf. What I'd like to do is wrap this in Haskell function like:
longRunningReadOperation :: Int -> IO [Byte]
such that the function returns immediately, and only blocks when someone pulls on the results if the callback hasn't been triggered yet. I figure I'm going to need unsafeInterleaveIO, but I'm not sure what to do in the computation I pass to it. I had a small hope that if the callback put the results into an MVar (say var), and I returned (unsafeInterleaveIO (readMVar var)) that might work, but it seems like if the readMVar blocks then the callback doesn't execute either.
I don't see why that wouldn't work. You are compiling with -threaded, right? Cheers, Simon
participants (4)
-
Bulat Ziganshin
-
Evan Laforge
-
J. Garrett Morris
-
Simon Marlow