module MBox where import Control.Concurrent.Chan import Control.Concurrent.MVar -- MBox is enriched channel -> reading and MBox can give Empty | Value , i.e Maybe => Just value | Nothing -- writing to an MBox should be able to fail i.e Success | Failure, i.e. => Just () | Nothing -- writing to an MBox should fail only if an MBox is closed, i.e it's control thread has finihed -- this opens a can of worms - resource leaks. ideally all MBox-es should be garbage collected -- but if the control thread finishes, releasing the MBox, an unknown number of threads might hold an (indirect) -- reference to it via a writer function. Allowing the writer to return a failure, will allow a client thread -- to gracefully fail, or release the handle. :( -- The bottom line is channels behave like pointers, using channels, reintroduces a bunch of pointer problems... -- below everything has the obvious semantics -- -- the read and write functions return Maybe or Maybe Maybe -> the outer indicates open/close, the inner -- op success/failure data MBox a = MBox (MVar Bool) (Chan a) newMBox :: IO (MBox a) newMBox = do m <- newMVar True c <- newChan return $ MBox m c closeMBox :: MBox t -> IO () closeMBox (MBox handle channel) = do modifyMVar_ handle $ \_ -> do putMVar handle False return False readMBox :: MBox t -> IO (Maybe (Maybe t)) readMBox (MBox handle channel) = do is_open <- takeMVar handle if is_open then readChan channel >>= \x -> return $ Just $ Just x else return $ Just Nothing tryReadMBox :: MBox t -> IO (Maybe (Maybe t)) tryReadMBox (MBox handle channel) = do is_open <- takeMVar handle if is_open then do is_empty <- isEmptyChan channel if is_empty then readChan channel >>= \x -> return $ Just $ Just x else return $ Just Nothing else return Nothing writeMBox :: MBox t -> t -> IO (Maybe ()) writeMBox (MBox handle channel) message = do is_open <- takeMVar handle if is_open then do writeChan channel message return $ Just () else return Nothing -- | put stuff back into the MBox unGetMBox :: MBox t -> t -> IO (Maybe ()) unGetMBox (MBox handle channel) message = do is_open <- takeMVar handle if is_open then do unGetChan channel message return $ Just () else return Nothing -- | return if the MBox is open for use, that is if the opening process still keeps it isOpenMBox :: MBox t -> IO Bool isOpenMBox (MBox handle channel) = takeMVar handle >>= return -- | check if an mbox is empty, return Just empty?, Nothing if the MBox is closed isEmptyMBox :: MBox t -> IO (Maybe Bool) isEmptyMBox (MBox handle channel) = do is_open <- takeMVar handle if is_open then do is <- isEmptyChan channel return $ Just is else return Nothing