diff options
author | Simon Marlow <marlowsd@gmail.com> | 2016-08-30 20:55:10 +0100 |
---|---|---|
committer | Simon Marlow <marlowsd@gmail.com> | 2016-09-12 08:33:24 +0100 |
commit | 454033b54e2f7eef2354cc9d7ae7e7cba4dff09a (patch) | |
tree | 3577ed7b0b42e2acff1502673e1ee474fba31319 /docs | |
parent | 0e7ccf6d233c66b23a60de4e35e039f78ea3e162 (diff) | |
download | haskell-454033b54e2f7eef2354cc9d7ae7e7cba4dff09a.tar.gz |
Add hs_try_putmvar()
Summary:
This is a fast, non-blocking, asynchronous, interface to tryPutMVar that
can be called from C/C++.
It's useful for callback-based C/C++ APIs: the idea is that the callback
invokes hs_try_putmvar(), and the Haskell code waits for the callback to
run by blocking in takeMVar.
The callback doesn't block - this is often a requirement of
callback-based APIs. The callback wakes up the Haskell thread with
minimal overhead and no unnecessary context-switches.
There are a couple of benchmarks in
testsuite/tests/concurrent/should_run. Some example results comparing
hs_try_putmvar() with using a standard foreign export:
./hs_try_putmvar003 1 64 16 100 +RTS -s -N4 0.49s
./hs_try_putmvar003 2 64 16 100 +RTS -s -N4 2.30s
hs_try_putmvar() is 4x faster for this workload (see the source for
hs_try_putmvar003.hs for details of the workload).
An alternative solution is to use the IO Manager for this. We've tried
it, but there are problems with that approach:
* Need to create a new file descriptor for each callback
* The IO Manger thread(s) become a bottleneck
* More potential for things to go wrong, e.g. throwing an exception in
an IO Manager callback kills the IO Manager thread.
Test Plan: validate; new unit tests
Reviewers: niteria, erikd, ezyang, bgamari, austin, hvr
Subscribers: thomie
Differential Revision: https://phabricator.haskell.org/D2501
Diffstat (limited to 'docs')
-rw-r--r-- | docs/users_guide/ffi-chap.rst | 119 |
1 files changed, 119 insertions, 0 deletions
diff --git a/docs/users_guide/ffi-chap.rst b/docs/users_guide/ffi-chap.rst index 63324d1ff3..f46d902b3d 100644 --- a/docs/users_guide/ffi-chap.rst +++ b/docs/users_guide/ffi-chap.rst @@ -616,6 +616,125 @@ the threads have exited first. (Unofficially, if you want to use this fast and loose version of ``hs_exit()``, then call ``shutdownHaskellAndExit()`` instead). +.. _hs_try_putmvar: + +Waking up Haskell threads from C +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Sometimes we want to be able to wake up a Haskell thread from some C +code. For example, when using a callback-based C API, we register a C +callback and then we need to wait for the callback to run. + +One way to do this is to create a ``foreign export`` that will do +whatever needs to be done to wake up the Haskell thread - perhaps +``putMVar`` - and then call this from our C callback. There are a +couple of problems with this: + +1. Calling a foreign export has a lot of overhead: it creates a + complete new Haskell thread, for example. +2. The call may block for a long time if a GC is in progress. We + can't use this method if the C API we're calling doesn't allow + blocking in the callback. + +For these reasons GHC provides an external API to ``tryPutMVar``, +``hs_try_putmvar``, which you can use to cheaply and asynchronously +wake up a Haskell thread from C/C++. + +.. code-block:: c + + void hs_try_putmvar (int capability, HsStablePtr sp); + +The C call ``hs_try_putmvar(cap, mvar)`` is equivalent to the Haskell +call ``tryPutMVar mvar ()``, except that it is + +* non-blocking: takes a bounded, short, amount of time + +* asynchronous: the actual putMVar may be performed after the call + returns (for example, if the RTS is currently garbage collecting). + That's why ``hs_try_putmvar()`` doesn't return a result to say + whether the put succeeded. It is your responsibility to ensure that + the ``MVar`` is empty; if it is full, ``hs_try_putmvar()`` will have + no effect. + +**Example**. Suppose we have a C/C++ function to call that will return and then +invoke a callback at some point in the future, passing us some data. +We want to wait in Haskell for the callback to be called, and retrieve +the data. We can do it like this: + +.. code-block:: haskell + + import GHC.Conc (newStablePtrPrimMVar, PrimMVar) + + makeExternalCall = mask_ $ do + mvar <- newEmptyMVar + sp <- newStablePtrPrimMVar mvar + fp <- mallocForeignPtr + withForeignPtr fp $ \presult -> do + cap <- threadCapability =<< myThreadId + scheduleCallback sp cap presult + takeMVar mvar `onException` + forkIO (do takeMVar mvar; touchForeignPtr fp) + peek presult + + foreign import ccall "scheduleCallback" + scheduleCallback :: StablePtr PrimMVar + -> Int + -> Ptr Result + -> IO () + +And inside ``scheduleCallback``, we create a callback that will in due +course store the result data in the ``Ptr Result``, and then call +``hs_try_putmvar()``. + +There are a few things to note here. + +* There's a special function to create the ``StablePtr``: + ``newStablePtrPrimMVar``, because the RTS needs a ``StablePtr`` to + the primitive ``MVar#`` object, and we can't create that directly. + Do *not* just use ``newStablePtr`` on the ``MVar``: your program + will crash. + +* The ``StablePtr`` is freed by ``hs_try_putmvar()``. This is because + it would otherwise be difficult to arrange to free the ``StablePtr`` + reliably: we can't free it in Haskell, because if the ``takeMVar`` + is interrupted by an asynchronous exception, then the callback will + fire at a later time. We can't free it in C, because we don't know + when to free it (not when ``hs_try_putmvar()`` returns, because that + is an async call that uses the ``StablePtr`` at some time in the + future). + +* The ``mask_`` is to avoid asynchronous exceptions before the + ``scheduleCallback`` call, which would leak the ``StablePtr``. + +* We find out the current capability number and pass it to C. This is + passed back to ``hs_try_putmvar``, and helps the RTS to know which + capability it should try to perform the ``tryPutMVar`` on. If you + don't care, you can pass ``-1`` for the capability to + ``hs_try_putmvar``, and it will pick an arbitrary one. + + Picking the right capability will help avoid unnecessary context + switches. Ideally you should pass the capability that the thread + that will be woken up last ran on, which you can find by calling + ``threadCapability`` in Haskell. + +* If you want to also pass some data back from the C callback to + Haskell, this is best done by first allocating some memory in + Haskell to receive the data, and passing the address to C, as we did + in the above example. + +* ``takeMVar`` can be interrupted by an asynchronous exception. If + this happens, the callback in C will still run at some point in the + future, will still write the result, and will still call + ``hs_try_putmvar()``. Therefore we have to arrange that the memory + for the result stays alive until the callback has run, so if an + exception is thrown during ``takeMVar`` we fork another thread to + wait for the callback and hold the memory alive using + ``touchForeignPtr``. + +For a fully working example, see +``testsuite/tests/concurrent/should_run/hs_try_putmvar001.hs`` in the +GHC source tree. + .. _ffi-floating-point: Floating point and the FFI |