packages feed

cfuture 1.0 → 2.0

raw patch · 5 files changed

+92/−85 lines, 5 filesdep ~cfuturePVP ok

version bump matches the API change (PVP)

Dependency ranges changed: cfuture

API changes (from Hackage documentation)

- Control.Concurrent.CFuture: safeGet :: Future a -> IO (FutureResult a)
- Control.Concurrent.CFuture: type FutureResult a = Either SomeException a
- Control.Concurrent.CFuture: forkFutureC :: IO a -> CFuturePtr -> IO ()
+ Control.Concurrent.CFuture: forkFutureC :: CFuturePtr -> IO a -> IO ()
- Control.Concurrent.CFuture: get :: Future a -> IO a
+ Control.Concurrent.CFuture: get :: Future a -> IO (Maybe a)
- Control.Concurrent.CFuture: type CFuturePtr = Ptr (StablePtr PrimMVar)
+ Control.Concurrent.CFuture: type CFuturePtr = Ptr StablePtr PrimMVar

Files

CHANGELOG.md view
@@ -3,3 +3,9 @@ ## 1.0 -- 2025-04-21  * First version, with a foreign library and a test suite already.++## 2.0 -- 2026-03-19++* Switched to Maybe results instead of exceptions wrapped into a Left.+* Changed type signature of forkFutureC to be similar to the others.+* Simplified the code and added more explanations to the documentation.
cfuture.cabal view
@@ -1,16 +1,21 @@ cabal-version:      3.0 name:               cfuture-version:            1.0-synopsis: A Future type that is easy to represent and handle in C/C++.+version:            2.0+synopsis: A Future type that is interruptible anytime and exportable to C/C++. description:    A module similar to the "future" package of Chris Kuklewicz,-   but having a Future that is easy to represent and handle-   in C/C++,-   using two MVars.-   Moreover, it uses two new threads:-   one (the "watcher thread") aborts the calculation-   if triggered by filling the first MVar.+   but having a Future that is interruptible anytime,+   and easy to represent and handle+   in C/C++. +   It uses two threads,+   the first of which kills the calculation+   if triggered by filling an MVar,+   and the second of which calculating the result+   and writing it into another MVar.+   However, the library can be used+   without manipulating the MVars directly.+ license:            BSD-3-Clause license-file:       LICENSE author:             Viktor Csimma@@ -22,7 +27,7 @@     ghc-options: -Wall source-repository head   type:     git-  location: git://github.com/viktorcsimma/cfuture.git+  location: https://github.com/viktorcsimma/cfuture.git  library     import:           warnings@@ -42,7 +47,7 @@     other-modules:    Control.Concurrent.CFuture     build-depends:    base >=4.17.2 && < 5,                       base-prelude >= 1.4 && < 1.7,-                      cfuture >= 1.0 && < 1.1+                      cfuture >= 2.0 && < 3      hs-source-dirs:   src     c-sources:        csrc/CFuture.c
src/Control/Concurrent/CFuture.hs view
@@ -1,69 +1,66 @@--- | A Future type that is easy to represent and handle--- in C\/C++,--- using two 'MVar's.--- Moreover, it uses two new threads:--- one (the "watcher thread") aborts the calculation--- if triggered by filling the first 'MVar'.+-- | Defines a type similar to C++ futures,+-- that can be passed to C and used+-- to interrupt asynchronous calls+-- or to get their results.+--+-- From within Haskell, you can use+-- 'forkFuture' to start a calculation,+-- 'get' to wait on it+-- and 'abort' to abort it.+--+-- From C, interruption happens+-- via calling a C-native FFI function,+-- without the cost of a full FFI call.+-- See the C source for more information.+{-# LANGUAGE ScopedTypeVariables #-} module Control.Concurrent.CFuture-  (Future, FutureResult, CFuturePtr,+  (Future, CFuturePtr,    forkFuture, writeFutureC, forkFutureC,-   safeGet, get, getC, waitC, abort)+   get, getC, waitC, abort)   where  import Foreign.Ptr import Foreign.StablePtr import Foreign.Storable import Control.Concurrent-import Control.Exception(throw, toException, SomeException) import BasePrelude (PrimMVar, newStablePtrPrimMVar,                     Int8, Int16, Int32, Int64,                     Word8, Word16, Word32, Word64) --- | An exception is thrown--- if the Future has been aborted.-type FutureResult a = Either SomeException a+                    -- | A C pointer to a C array of two 'StablePtr's.+                    -- freeing the pointers is possible+                    -- via hs_free_stable_ptr --- | A C pointer to a C array of two 'StablePtr's.+-- | Gets translated to HsStablePtr* (i.e. void**) in C. type CFuturePtr = Ptr (StablePtr PrimMVar) --- | A type similar to C++ futures,--- that can be passed to C and used--- to interrupt asynchronous calls--- or to get their results.------ From within Haskell, you can use--- 'forkFuture' to start a calculation,--- 'safeGet' or 'get' to wait on it--- and 'abort' to abort it.------ From C, interruption happens--- via calling hs_try_putmvar--- on the first 'StablePtr';--- freeing the pointers is possible--- via hs_free_stable_ptr ----- all these without the cost of an FFI call.--data Future a = MkFuture -  (MVar ())                -- interruption: fill it to interrupt the calculation-  (MVar (FutureResult a))  -- result:       where the result or the exception will be put+-- | An object representing an asynchronous calculation.+-- Filling the first MVar activates a thread that aborts the calculation+-- and writes Nothing to the other MVar+-- (which would otherwise contain the result).+-- It is recommended not to manipulate the MVars directly,+-- but to use the functions in the library instead.+data Future a = MkFuture+  (MVar ())           -- ^ For interruption: fill it to interrupt the calculation.+  (MVar (Maybe a))    -- ^ For the result: Nothing if it has been aborted, Just otherwise.  -- | Starts an asynchronous calculation -- and returns a 'Future' to it. forkFuture :: IO a -> IO (Future a) forkFuture action = do   intMVar <- (newEmptyMVar :: IO (MVar ()))-  resMVar <- (newEmptyMVar :: IO (MVar (FutureResult a)))+  resMVar <- (newEmptyMVar :: IO (MVar (Maybe a)))    -- The thread doing the actual calculation.   -- It also wakes up the watcher thread.-  calculationThreadId <- forkIO $ (putMVar resMVar =<< (Right <$> action)) >> putMVar intMVar ()+  calculationThreadId <- forkIO $ (putMVar resMVar . Just =<< action) >> putMVar intMVar () -  -- The thread killing the calculation thread if woken up.+  -- The "watcher thread", killing the calculation thread if woken up.   _ <- forkIO $ do     -- this is activated if intMVar gets filled     readMVar intMVar     killThread calculationThreadId-    putMVar resMVar (Left (toException (userError "Calculation aborted")))+    putMVar resMVar Nothing    return $ MkFuture intMVar resMVar @@ -103,56 +100,52 @@ -- -- Note: it is the responsibility of the C side -- to free the 'StablePtr's.-forkFutureC :: IO a -> CFuturePtr -> IO ()-forkFutureC action ptr = writeFutureC ptr =<< forkFuture action+forkFutureC :: CFuturePtr -> IO a -> IO ()+forkFutureC ptr action = writeFutureC ptr =<< forkFuture action --- | Reads the result from the 'Future'--- but does not check whether there has been an exception--- wrapped into a 'Left'.+-- | Reads the result from the 'Future'. -- This is a blocking call,--- waiting for the result until it is ready.-safeGet :: Future a -> IO (FutureResult a)-safeGet (MkFuture _ resMVar) = readMVar resMVar+-- waiting for the result (or the 'Nothing' signalling interruption)+-- until it is ready.+get :: Future a -> IO (Maybe a)+get (MkFuture _ resMVar) = readMVar resMVar --- | Similar to 'safeGet', but checks whether we have an exception--- and rethrows it if yes;--- returning the plain result otherwise.--- This is a blocking call,--- waiting for the result until it is ready.-get :: Future a -> IO a-get future = safeGet future >>= either throw return+-- | A helper function to 'getC' and 'waitC',+-- expecting a function which we are going to do with the result.+-- Not meant to be used directly.+getCHelper :: CFuturePtr -> (a -> IO ()) -> IO Bool+getCHelper futurePtr doOnCompletion = do+  -- we assume both StablePtrs are of the same size,+  -- which should be in a sane world+  resMVarSPtr <- peekElemOff (castPtr futurePtr :: Ptr (StablePtr (MVar (Maybe a)))) 1+  result <- readMVar =<< deRefStablePtr resMVarSPtr+  case result of+        Just a  -> doOnCompletion a >> return True+        _       -> return False  -- we don't run 'doOnCompletion' in this case + -- | A variant of 'get' to call from C -- which writes the result to the memory location -- defined by the pointer.--- If there is an exception instead of a result,+-- If there is 'Nothing' instead of a result, -- it writes nothing to the pointer -- and returns 'False'; -- on success, it returns 'True'.+--+-- Note: do _not_ call this on a freed 'Future'+-- (the abortC function of the C side frees it). getC :: Storable a => CFuturePtr -> Ptr a -> IO Bool-getC futurePtr destPtr = do-  -- we assume both StablePtrs are of the same size-  resMVarSPtr <- peekElemOff (castPtr futurePtr :: Ptr (StablePtr (MVar (FutureResult a)))) 1-  -- we catch the exception-  -- as C/C++ could not handle it-  result <- readMVar =<< deRefStablePtr resMVarSPtr-  case result of-    Right a -> poke destPtr a >> return True-    _       -> return False+getC futurePtr destPtr = getCHelper futurePtr (poke destPtr)  -- | Only waits until the calculation gets finished; -- then returns 'True' if it was successful and 'False' otherwise. -- To be called from C.+-- Differs from 'getC' in that it ignores the result.+--+-- Note: do _not_ call this on a freed 'Future'+-- (the abortC function of the C side frees it). waitC :: CFuturePtr -> IO Bool-waitC ptr = do-  -- we assume both StablePtrs are of the same size-  resMVarSPtr <- peekElemOff (castPtr ptr :: Ptr (StablePtr (MVar (Either String ())))) 1-  -- we catch the exception and return an illegal value-  -- in order to unblock a C++ thread waiting for the result-  result <- readMVar =<< deRefStablePtr resMVarSPtr-  case result of-    Right _ -> return True-    _       -> return False+waitC futurePtr = getCHelper futurePtr $ const $ return ()  -- Finally, the foreign export declarations: -- this is quite ugly, but c'est la vie.
test/HsTestCase.hs view
@@ -5,6 +5,7 @@ import Control.Concurrent (threadDelay) import OurTasks +-- | Called from main.c. hsTestCase :: IO Bool hsTestCase = do   print "Working"@@ -13,11 +14,11 @@   threadDelay 2000000   _ <- abort future   print "Aborted"-  result <- safeGet future+  result <- get future   -- it should be an exception   case result of-    Left _ -> return True-    _      -> return False+    Nothing  -> return True -- the expected result+    _        -> return False  foreign export ccall hsTestCase :: IO Bool 
test/OurTasks.hs view
@@ -2,6 +2,8 @@ import Control.Concurrent.CFuture import Control.Concurrent (threadDelay) +-- |+-- The task used as an example. task :: IO Int task = sum <$> mapM subtask [1..10]   where@@ -12,5 +14,5 @@     return $ 2 * n  cFunction :: CFuturePtr -> IO ()-cFunction = forkFutureC task+cFunction ptr = forkFutureC ptr task foreign export ccall cFunction :: CFuturePtr -> IO ()