I want to call this in my haskell code. And I know the usual way is to create a foreign export and pass the FunPtr as the callback to the c++ ffi. However, this seems inefficient.
Besides, I know a better way is to use hs_try_putmvar as described in the ghc user guide. But this way only works if the callback is “one-shot”, which means only trigger one time.
So, my question is: if the callback will be called multiple times by a cpp worker, is there any efficient way to implement the haskell wrapper instead of using the usual FunPtr way?
I don’t see this mentioned in the GHC documentation. I guess a problem could be that the Haskell thread might not have enough time to clear the MVar and hence messages may get lost. Is that what you mean?
Another option is perhaps to keep some kind of queue on the C/C++ side. And only use the MVar to signal that something is in that queue, not to transfer the actual data. Then it doesn’t matter if successive events get lost. And the Haskell thread can come around later and use a different (possibly unsafe) FFI function to empty the queue.
If you’re concerned about efficiency, just write the callback in C++ instead of Haskell, and apply it via an auxiliary C++ definition which is then called by Haskell:
extern void setRecvCallback(Reader* reader, std::function<void(std::string&&)> callback);
// you write this
std::function<void(std::string&&)> myRecvCallback /* ... */
// Haskell calls this
void setMyRecv(Reader* reader) {
setRecvCallback(reader, myRecvCallback);
}
If we want to wake up Haskell threads, we must call hs_try_putmvar, and
The Haskell allocated StablePtr is freed by hs_try_putmvar()
Which means when the callback is called the second time, it will use an already freed HsStablePtr, which is a terrible thing and will cause a coredump.
Using an async queue seems a good idea. However, in the real world, things will become complex. Let’s say I have a multi-threaded CPP TCP server, I want to write a Haskell handler to handle TCP streaming io. Do I need to allocate a queue for each request? And how can Haskell know the address of the queue for the request? (There may also be other issues)
So, my question is, can we do better? can I only have some unsafe ffi like the way through hs_try_putmvar to achieve this? Since unsafe ffi is more efficient than all else.
No - if there was a “more efficient”and trustworthy way to implement GHC’s FFI, it would already be in use. You only have to look at the libraries GHC provides by default (without bringing in extra packages) to see how important the FFI is, and not just in terms of efficiency.
As for hs_try_putmvar, the documentation you referred to makes it quite clear that it’s a considerably-specialised variant of tryPutMVar: it’s an “apples-and-oranges” comparison.