{-# OPTIONS_GHC -funbox-strict-fields #-} -- | Simple interprocess quantity semaphores -- -- Based on POSIX or Win32 C semaphores module Control.Concurrent.Process.QSem ( QSem, newQSem, lookupQSem, waitQSem, tryWaitQSem, signalQSem, qSemName ) where import Control.Monad (when) import Data.Data (Typeable) import Foreign.C.Error import Foreign.C.String import Foreign.C.Types import Foreign.ForeignPtr import Foreign.Ptr import Foreign.SharedObjectName.Internal -- | Opaque implementation-dependent semaphore data QSemT -- | 'QSem' is a quantity semaphore in which the resource is aqcuired -- and released in units of one. data QSem = QSem !(SOName QSem) !(ForeignPtr QSemT) deriving (Eq, Typeable) -- | Build a new 'QSem' with a supplied initial quantity. -- The initial quantity must be at least 0. -- -- This function throws an exception -- if an underlying platform-dependent function fails. newQSem :: Int -> IO QSem newQSem initial | initial < 0 = fail "newQSem: Initial quantity must be non-negative" | otherwise = do qsem <- checkNullPointer "newQSem" $ c'qsem_new initial n <- newEmptySOName unsafeWithSOName n $ c'qsem_name qsem QSem n <$> newForeignPtr p'qsem_close qsem -- | Lookup QSem by its name in the global namespace. -- Use this function to init several entangled semaphores in different processes. -- -- This function throws an exception if no `QSem` with this name exist, -- or if an underlying platform-dependent function fails. lookupQSem :: SOName QSem -> IO QSem lookupQSem n = do qsem <- unsafeWithSOName n $ checkNullPointer "lookupQSem" . c'qsem_lookup QSem n <$> newForeignPtr p'qsem_close qsem -- | Get a global reference to the semaphore. -- Send this reference to another process to lookup this semaphore and -- start interprocess communication. qSemName :: QSem -> SOName QSem qSemName (QSem r _) = r -- | Wait for a unit to become available -- -- This function throws an exception -- if an underlying platform-dependent function fails. waitQSem :: QSem -> IO () waitQSem (QSem _ p) = withForeignPtr p $ checkZeroReturn "waitQSem" . c'qsem_wait -- | Try to take a unit of the `QSem`. -- -- This function does not wait, in fact. Sorry for naming. -- -- Returns: -- -- * @True@ if successfully took a unit of `QSem` (it is decremented) -- * @False@ if number of available units is less than @1@ (it is not decremented) -- -- This function does not throw an exception. tryWaitQSem :: QSem -> IO Bool tryWaitQSem (QSem _ p) = withForeignPtr p $ fmap (0 ==) . c'qsem_trywait -- | Signal that a unit of the 'QSem' is available -- -- This function throws an exception -- if an underlying platform-dependent function fails. signalQSem :: QSem -> IO () signalQSem (QSem _ p) = withForeignPtr p $ checkZeroReturn "signalQSem" . c'qsem_signal checkNullPointer :: String -> IO (Ptr a) -> IO (Ptr a) checkNullPointer s k = do p <- k if p == nullPtr then throwErrno (s ++ " returned NULL pointer.") else return p checkZeroReturn :: String -> IO CInt -> IO () checkZeroReturn s k = do p <- k when (p /= 0) $ throwErrno (s ++ " returned non-zero result.") foreign import ccall unsafe "qsem_new" c'qsem_new :: Int -> IO (Ptr QSemT) foreign import ccall unsafe "qsem_lookup" c'qsem_lookup :: CString -> IO (Ptr QSemT) foreign import ccall unsafe "&qsem_close" p'qsem_close :: FunPtr (Ptr QSemT -> IO ()) foreign import ccall unsafe "qsem_signal" c'qsem_signal :: Ptr QSemT -> IO CInt foreign import ccall unsafe "qsem_wait" c'qsem_wait :: Ptr QSemT -> IO CInt foreign import ccall unsafe "qsem_trywait" c'qsem_trywait :: Ptr QSemT -> IO CInt foreign import ccall unsafe "qsem_name" c'qsem_name :: Ptr QSemT -> CString -> IO ()