packages feed

synchronous-channels (empty) → 0.1

raw patch · 4 files changed

+724/−0 lines, 4 filesdep +basesetup-changed

Dependencies added: base

Files

+ Control/Concurrent/Chan/Synchronous.hs view
@@ -0,0 +1,671 @@+-----------------------------------------------------------------------------+-- |+-- Module      :  Control.Concurrent.Chan.Synchronous+-- Copyright   :  (c) 2009 Jesse A. Tov+-- License     :  BSD (see the file LICENSE)+-- +-- Maintainer  :  tov@ccs.neu.edu+-- Stability   :  experimental+-- Portability :  somewhat portable?+--+-- This module provides synchronous channels.  Unlike the channels in+-- 'Control.Concurrent.Chan', which are unbounded queues on which+-- writers never block, these channels allow each writer to block until+-- it synchronizes with the particular reader that receives its message.+--+-- We actually provide three classes of channel operations:+--+--   [Synchronous, blocking] These operations block until they+--     synchronize their communication with another thread.+--+--   [Synchronous, non-blocking] These operations complete immediately+--     if another thread is ready to synchronize, and otherwise return+--     a failure code immediate.+--+--   [Asynchronous] These operations complete immediately and always+--     succeed, though the value they send may not be received until+--     another thread tries to receive it.+--+-----------------------------------------------------------------------------+module Control.Concurrent.Chan.Synchronous (+  -- * The channel datatype+  Chan,               -- abstract: * -> *+  -- ** Construction and observation+    newChan,            -- IO (Chan a)+    isEmptyChan,        -- Chan a -> IO Bool++  -- * Synchronous, blocking operations+  -- | The synchronous, blocking channel operations are designed+  --   to complete only when a writing thread synchronizes with a+  --   reading thread.+  --+  --   They are exception safe, in the sense that if+  --   an asynchronous exception is delivered to a blocked thread,+  --   that thread is removed from the pool and cannot synchronize+  --   with another.  In particular, we can write code like this:+  --+  --   @+  --   'Control.Exception.block' $ do+  --     msg <- 'readChan' c+  --     'writeIORef' r msg+  --   @+  --+  --   In this case, the call to 'readChan' may be interrupted, but+  --   if the message is delivered, the 'writeIORef' will happen.  There+  --   is no case where the writing thread synchronizes and unblocks+  --   and the message is dropped on the floor.  This make it possible+  --   to safely implement features such as timeouts on blocking+  --   operations.++  -- ** Basic operations+    writeChan,          -- Chan a -> a -> IO ()+    readChan,           -- Chan a -> IO a+  -- ** Questionable operations+    unGetChan,          -- Chan a -> a -> IO ()+    swapChan,           -- Chan a -> a -> IO a+  -- ** List operations+    getChanContents,    -- Chan a -> IO [a]+    getChanN,           -- Chan a -> Integer -> IO [a]+    writeList2Chan,     -- Chan a -> [a] -> IO ()++  -- * Synchronous, non-blocking operations+  -- | These operations are similar to the blocking operations in that+  --   they only succeed by synchronizing with another thread, but+  --   they return a failure code rather than block if no other thread+  --   is ready or if they cannot acquire a lock on the channel.+  --+  --   Generally, a non-blocking operation from this section+  --   cannot synchronize with another non-blocking operation.  The+  --   other operation that pairs up with one of these operations will+  --   always be blocking or asynchronous.+  --+  --   These operations are designed to operate in constant time+  --   (or linear time for the list operations).+  --   In particular, it may be possible to attempt to synchronize with+  --   a waiting thread that gives up before the operation is complete.+  --   Rather than look for another opportunity, which could lead to+  --   an arbitrary number of attempts, the operation fails with+  --   'TryAgain'.++  -- ** The non-blocking result datatype+  TryResult(..),      -- concrete: * -> *+    maybeTry,           -- IO (TryResult a) -> IO (Maybe a)++  -- ** Basic operations+    tryWriteChan,       -- Chan a -> a -> IO (TryResult ()+    tryReadChan,        -- Chan a -> IO (TryResult a)+    tryPeekChan,        -- Chan a -> IO (TryResult a)+  -- ** List operations+    tryGetChanContents, -- Chan a -> IO (TryResult [a])+    tryGetChanN,        -- Chan a -> Integer -> IO (TryResult [a])+    tryWriteList2Chan,  -- Chan a -> Integer -> IO (TryResult (), [a])++  -- * Asynchronous operations+  -- | The asynchronous operations always succeed immediately.+  --   They should be semantically equivalent to forking another thread+  --   and performing the equivalent blocking operation (though they do+  --   not actually require a separate thread).++    asyncWriteChan,     -- Chan a -> a -> IO ()+    asyncUnGetChan,     -- Chan a -> a -> IO ()+    tryAsyncSwapChan,   -- Chan a -> a -> IO a+    asyncWriteList2Chan -- Chan a -> [a] -> IO ()+) where++import Control.Concurrent.MVar hiding ( modifyMVar )+import Control.Exception+import Control.Monad+import Data.IORef+import System.IO.Unsafe ( unsafeInterleaveIO )++---+--- Amortized O(1) queues+---++data Q a = Q { readEnd :: ![a], writeEnd :: ![a] }++empty :: Q a+empty  = Q [] []++(|>) :: Q a -> a -> Q a+(|>) q a = q { writeEnd = a : writeEnd q }++(<|) :: a -> Q a -> Q a+(<|) a q = q { readEnd = a : readEnd q }++(|>>>) :: Q a -> [a] -> Q a+(|>>>) q as = Q { readEnd  = concat [ readEnd q,+                                      reverse (writeEnd q),+                                      as ],+                  writeEnd = [] }++data QView a b = !a :< !(Q a)+               | NoQ !b++dequeue :: b -> Q a -> QView a b+dequeue b (Q []     []) = NoQ b+dequeue _ (Q (r:rs) ws) = r :< Q rs ws+dequeue b (Q []     ws) = dequeue b (Q (reverse ws) [])++---+--- Chan representation+---++-- Both readers and writers include IORef (Maybe ...) in their+-- representations.  This allows \"revoking\" an operation in case the+-- blocked thread is interrupted.+--+-- A reader contains an MVar in which to write a message to it, whereas+-- a writer contains the value it has sent and an MVar on which to+-- confirm receipt of the message.  A channel at any point in time is+-- represented as either a queue of waiting writers or a queue of+-- waiting readers.+type Reader a  = (IORef (Maybe (MVar a)))+type Writer a  = (IORef (Maybe a), MVar ())+data Rep a     = RQ !(Q (Reader a))+               | WQ !(Q (Writer a))++-- | The abstract channel type for sending values of type @a@.+newtype Chan a = Chan (MVar (Rep a))++-- | The synchronous, non-blocking operations may succeed immediately,+--   or they may give up for a variety of reasons:+data TryResult a =+    -- | The operation succeeded.+    Success { getSuccess :: a }+    -- | No other thread is currently ready to synchronize for the+    --   requested operation.+  | NotReady+    -- | An attempt was made to synchronize with another thread, but+    --   the other thread bailed out before it could complete.  Another+    --   thread may be available, so it may be worth trying again+    --   immediately.+  | TryAgain+    -- | Another thread is currently operating on the channel.  It may+    --   be worth trying again very soon.+  | WouldBlock+  deriving (Eq, Show)++getReaders :: Rep a -> QView (Reader a) (Q (Writer a))+getReaders (RQ q) = dequeue empty q+getReaders (WQ q) = dequeue q empty++getWriters :: Rep a -> QView (Writer a) (Q (Reader a))+getWriters (RQ q) = dequeue q empty+getWriters (WQ q) = dequeue empty q++clear :: IO a -> IORef (Maybe b) -> IO a+clear io r = block $ io `finally` writeIORef r Nothing++-- | Make a new channel.+newChan :: IO (Chan a)+newChan = fmap Chan (newMVar (RQ empty))++genericWriteChan :: (Q (Writer a) -> Writer a -> Q (Writer a)) ->+                    Bool ->+                    Chan a -> a -> IO ()+genericWriteChan enq wait (Chan m) a = join $ modifyMVar m modify+  where+    modify e = case getReaders e of+      r :< readers -> do+        maybereader <- readIORef r+        case maybereader of+          Just reader -> do+            putMVar reader a+            return (RQ readers, return ())+          Nothing     ->+            modify e+      NoQ writers -> do+        r       <- newIORef (Just a)+        confirm <- newEmptyMVar+        return (WQ (writers `enq` (r, confirm)),+                if wait+                  then takeMVar confirm `clear` r+                  else return ())++-- |+-- Write a value to a channel, possibly blocking until synchronizing+-- with a reader.+writeChan :: Chan a -> a -> IO ()+writeChan = genericWriteChan (|>) True++-- |+-- Write to the \"read end\" of a channel.  If several writers are+-- waiting, this jumps ahead of them to the front of the line.  Blocks+-- until matched up with a reader.+unGetChan :: Chan a -> a -> IO ()+unGetChan  = genericWriteChan (flip (<|)) True++-- |+-- Write a value to a channel, returning immediately rather than+-- waiting for the reader to arrive.+asyncWriteChan :: Chan a -> a -> IO ()+asyncWriteChan = genericWriteChan (|>) False++-- |+-- Write a value to the \"read end\" of a channel, succeeding immediately+-- rather than waiting for a reader.+asyncUnGetChan :: Chan a -> a -> IO ()+asyncUnGetChan  = genericWriteChan (flip (<|)) False++-- |+-- Attempts to write a value to a channel, succeeding immediately+-- if a reader is already available to synchronize.  Will fail+-- if the reader is interrupted before the operation completes, if there+-- is no reader available, or if another thread is currently starting+-- an operation on the channel.+tryWriteChan :: Chan a -> a -> IO (TryResult ())+tryWriteChan (Chan m) a = tryModifyMVar m modify+  where+    modify e = case getReaders e of+      r :< readers -> do+        maybereader <- readIORef r+        case maybereader of+          Just reader -> do+            putMVar reader a+            return (RQ readers, Success ())+          Nothing ->+            return (e, TryAgain)+      NoQ _ ->+        return (e, NotReady)++-- |+-- Reads a value from a channel, potentially blocking until a writer+-- is ready to synchronize.+readChan :: Chan a -> IO a+readChan (Chan m) = join $ modifyMVar m modify+  where+    modify e = case getWriters e of+      NoQ readers -> do+        message <- newEmptyMVar+        r       <- newIORef (Just message)+        return (RQ (readers |> r),+                takeMVar message `clear` r)+      (r, confirm) :< writers -> do+        maybea <- readIORef r+        case maybea of+          Just a  -> do+            putMVar confirm ()+            return (WQ writers, return a)+          Nothing ->+            modify (WQ writers)++-- |+-- Attempts to read a value from a channel, succeeding immediately+-- if a writer is already available to synchronize.+tryReadChan :: Chan a -> IO (TryResult a)+tryReadChan (Chan m) = tryModifyMVar m modify+  where+    modify e = case getWriters e of+      NoQ _ ->+        return (e, NotReady)+      (r, confirm) :< writers -> do+        maybea <- readIORef r+        case maybea of+          Just a  -> do+            putMVar confirm ()+            return (WQ writers, Success a)+          Nothing -> do+            return (WQ writers, TryAgain)++-- |+-- Attempts to read a value from a channel, but does not allow a writer+-- to synchronize, and does not remove the observed value from the+-- channel.  Fails if no writer is currently available, if the first+-- writer has bailed, or if it cannot immediately get a lock on the+-- channel.+tryPeekChan :: Chan a -> IO (TryResult a)+tryPeekChan (Chan m) = tryModifyMVar m modify+  where+    modify e =+      case getWriters e of+        NoQ _              -> return (e, NotReady)+        (r, _) :< writers -> do+          maybea <- readIORef r+          case maybea of+            Just a  -> return (e, Success a)+            Nothing -> return (WQ writers, TryAgain)++-- |+-- Reads a value from a channel, replacing it with a different value.+-- Blocks until the replacement value is read, and then returns the old+-- value.+--+-- /CAUTION:/ This operation does not guarantee that the read and+-- subsequent write are atomic.  It is somewhat likely to be better+-- in that respect than 'readChan' followed by 'unGetChan', however.+swapChan :: Chan a -> a -> IO a+swapChan (Chan m) a' = join $ transactMVar m modify+  where+    modify e commit = case getWriters e of+      NoQ readers -> do+        message <- newEmptyMVar+        r       <- newIORef (Just message)+        commit (RQ (readers |> r))+        return $ do+          a <- takeMVar message `clear` r+          -- Race condition here!  I think we'd need a different+          -- representation to do this one right.+          writeChan (Chan m) a'+          return a+      (r, confirm) :< writers -> do+        maybea <- readIORef r+        case maybea of+          Just a  -> do+            r'       <- newIORef (Just a')+            confirm' <- newEmptyMVar+            block $ do+              putMVar confirm ()+              commit (WQ ((r', confirm') <| writers))+            return $ do+              takeMVar confirm' `clear` r'+              return a+          Nothing  -> do+            modify (WQ writers) commit++-- |+-- If a writer is available to synchronize with, synchronizes with the+-- writer, allowing its operation to complete, writes the replacement+-- value ahead of any other writers, and then returns immediately.+-- Unlike 'swapChan', guarantees that no other write can intervene.+tryAsyncSwapChan :: Chan a -> a -> IO (TryResult a)+tryAsyncSwapChan (Chan m) a' = tryModifyMVar m modify+  where+    modify e = case getWriters e of+      NoQ _ -> return (e, NotReady)+      (r, confirm) :< writers -> do+        maybea <- readIORef r+        case maybea of+          Just a  -> do+            r'       <- newIORef (Just a')+            confirm' <- newEmptyMVar+            putMVar confirm ()+            return (WQ ((r', confirm') <| writers), Success a)+          Nothing -> return (WQ writers, TryAgain)++-- | Is the channel currently empty?  Note that the answer may become+--   false arbitrarily soon.  Don't rely on this operation.+isEmptyChan :: Chan a -> IO Bool+isEmptyChan (Chan m) = do+  e <- readMVar m+  case getWriters e of+    NoQ _ -> return True+    _     -> return False++-- Helper for pulling getting all the waiting data in+-- a channel while discharging the writers.  Returns a (possibly+-- empty) queue of readers.+--+-- Rather complicated interface!  It takes a channel representation,+-- and maybe an integer bound on how much stuff to read.  It then+-- returns:+--  * The list of results,+--  * A queue of readers, possibly empty, and+--  * one of:+--     * The remaining list of writers, if the counter ran out, or+--     * The remaining counter, if the writers ran out.+getImmediateChanContents ::+  Rep a -> Maybe Integer ->+  IO ([a], Q (Reader a), Either (Q (Writer a)) (Maybe Integer))+getImmediateChanContents e0 n0 = case getWriters e0 of+  NoQ readers               ->+    return ([], readers, Right n0)+  ((r, confirm) :< writers) -> do+    loop n0 ((r, confirm) :< writers)+  where+    loop n        (NoQ ()) =+      return ([], empty, Right n)+    loop (Just 0) (writer :< writers) =+      return ([], empty, Left (writer <| writers))+    loop n        ((r, confirm) :< writers) = unsafeInterleaveIO $ do+      maybea <- readIORef r+      case maybea of+        Just a  -> do+          putMVar confirm ()+          (as, rs, rest) <- loop (fmap pred n) (dequeue () writers)+          return (a:as, rs, rest)+        Nothing ->+          loop n (dequeue () writers)++getChanMaybeN :: Chan a -> Maybe Integer -> IO [a]+getChanMaybeN (Chan m) n = modifyMVar m modify+  where+    modify e = do+      stopr               <- newIORef False+      (as, readers, rest) <- getImmediateChanContents e n+      case rest of+        Left writers -> return (WQ writers, as)+        Right n'     -> do+          readers'  <- makereaders n' stopr+          as'       <- loop stopr readers'+          return (RQ $ readers |>>> readers', as ++ as')+    loop _     []     = return []+    loop stopr (r:rs) = unsafeInterleaveIO $ do+      maybereader <- readIORef r+      case maybereader of+        Just reader -> do+          a  <- (readMVar reader `clear` r)+            `onException` writeIORef stopr True+          as <- loop stopr rs+          return (a:as)+        Nothing ->+          loop stopr rs+    makereaders (Just 0) _     = return []+    makereaders n'       stopr = unsafeInterleaveIO $ do+      stop    <- readIORef stopr+      if stop+        then return []+        else do+          message <- newEmptyMVar+          r       <- newIORef (Just message)+          rest    <- makereaders (fmap pred n') stopr+          return (r:rest)+-- |+-- Read the contents of the channel as a lazy list.  While this+-- operation returns immediately, forcing evaluation of the list will+-- block, which is why this is included among the blocking operations.+-- Writers will block until each link in the list is forced as well.+--+-- Any subsequent attempts to read from the channel will fail, unless+-- a thread is interrupted while blocking on forcing the list.  Don't+-- rely on this behavior.+getChanContents  :: Chan a -> IO [a]+getChanContents c = getChanMaybeN c Nothing++-- |+-- Read a given number of elements from the channel as a lazy list.+-- Like 'getChanContents', this operation returns immediately, but it+-- will block when the list is forced.  (Taking the length of the list+-- should block until all the matching writes complete.)+getChanN  :: Chan a -> Integer -> IO [a]+getChanN c = getChanMaybeN c . Just++-- |+-- Read the currently available elements from the channel as a lazy+-- list.  The list is lazy because the number of currently available+-- elements may be infinite (see 'writeList2Chan').+tryGetChanContents :: Chan a -> IO (TryResult [a])+tryGetChanContents (Chan m) = tryModifyMVar m modify+  where+    modify e = do+      (as, readers, _) <- getImmediateChanContents e Nothing+      return (RQ readers, Success as)++-- |+-- Read up to the given number of currently available elements+-- from the channel.  The list will be no longer than the given+-- number, but if there are insufficient writers available then+-- it may be shorter.  The writers will block until their portions+-- of the list's spine are forced.+tryGetChanN :: Chan a -> Integer -> IO (TryResult [a])+tryGetChanN (Chan m) n = tryModifyMVar m modify+  where+    modify e = do+      (as, readers, rest) <- getImmediateChanContents e (Just n)+      case rest of+        Left writers -> return (WQ writers, Success as)+        Right _      -> return (RQ readers, Success as)++genericWriteList2Chan :: Bool -> Chan a -> [a] -> IO ()+genericWriteList2Chan wait (Chan m) as0 = join $ modifyMVar m (loop as0)+  where+    loop []     e = return (e, return ())+    loop (a:as) e =+      case getReaders e of+        r :< readers -> do+          maybereader <- readIORef r+          case maybereader of+            Just reader -> do+              putMVar reader a+              loop as (RQ readers)+            Nothing ->+              loop (a:as) (RQ readers)+        NoQ writers -> do+          stopr      <- newIORef False+          writers'   <- makeWriters stopr (a:as)+          -- This seems like overkill, maybe, but it ensures that+          -- if the writer gets killed, the remainder of the list+          -- not yet delivered is dropped.+          let each (r, c) = do+                (takeMVar c `clear` r)+                  `onException` writeIORef stopr True+              action = if wait+                         then mapM_ each writers'+                         else return ()+          return (WQ (writers |>>> writers'), action)+    makeWriters _    []     = return []+    makeWriters stopr (a:as) = unsafeInterleaveIO $ do+      stop <- readIORef stopr+      if stop+        then return []+        else do+          rI       <- newIORef (Just a)+          confirmI <- newEmptyMVar+          rest     <- makeWriters stopr as+          return ((rI, confirmI):rest)++-- |+-- Write a list to a channel, blocking until the read completes.+-- It is guaranteed that no other writes can intervene among the+-- list elements.  (This cannot be implemented in terms of+-- 'writeChan'.)  The list may be infinite, in which case this+-- operation never completes.+--+-- Interrupting this operation before the list is read completely causes+-- the rest of the list not to be written.  (If you want to write the+-- whole list, 'asyncWriteList2Chan' may be suitable.)+writeList2Chan :: Chan a -> [a] -> IO ()+writeList2Chan = genericWriteList2Chan True++-- |+-- Write a list to a channel, succeeding immediately.  The list may+-- be infinite, in which case the operation still completes+-- immediately.  (Actually, it may take time proportional to the number+-- of readers that are ready, so if an infinite list is written to+-- 'getChanContents' on the other side, it may not actually complete.)+asyncWriteList2Chan :: Chan a -> [a] -> IO ()+asyncWriteList2Chan = genericWriteList2Chan False++-- |+-- Attempt to write as much of a list as possible to a channel+-- synchronously, but without blocking; returns the unwritten remainder+-- of the list.  This operation will write additional list elements so+-- long as -- there are readers ready to receive them (and so long as the+-- list doesn't run out).+tryWriteList2Chan :: Chan a -> [a] -> IO (TryResult (), [a])+tryWriteList2Chan (Chan m) as0 = do+  result <- tryModifyMVar m (loop as0)+  case result of+    Success pair -> return pair+    WouldBlock   -> return (WouldBlock, as0)+    TryAgain     -> return (TryAgain, as0)+    NotReady     -> return (NotReady, as0)+  where+    loop []     e = return (e, Success (Success (), []))+    loop (a:as) e = do+      case getReaders e of+        r :< readers -> block $ do+          maybereader <- readIORef r+          case maybereader of+            Just reader -> do+              putMVar reader a+              loop as (RQ readers)+            Nothing ->+              return (RQ readers, Success (TryAgain, a:as))+        NoQ _ -> return (e, Success (NotReady, a:as))++-- |+-- Lift results of the try* operations into 'Maybe'.  'Success' goes+-- to 'Just' and all kinds of failure go to 'Nothing'.+maybeTry :: IO (TryResult a) -> IO (Maybe a)+maybeTry io = do+  tr <- io+  return $ case tr of+    Success r -> Just r+    _         -> Nothing++---+--- Helpful MVar stuff+---++saveBlock :: IO (IO a -> IO a)+saveBlock  = do+  b <- blocked+  case b of+    True  -> return block+    False -> return unblock++modifyMVar :: MVar a -> (a -> IO (a, b)) -> IO b+modifyMVar m io = do+  restore <- saveBlock+  block $ do+    a <- takeMVar m+    (a',b) <- restore (io a)+      `onException` putMVar m a+    putMVar m a'+    return b++-- Control.Concurrent.MVar doesn't have this, but it's pretty useful+-- for implementing non-blocking operations.+tryModifyMVar :: MVar a -> (a -> IO (a, TryResult b)) -> IO (TryResult b)+tryModifyMVar m io = do+  restore <- saveBlock+  block $ do+    maybea <- tryTakeMVar m+    case maybea of+      Just a -> do+        (a',b) <- restore (io a)+          `onException` putMVar m a+        putMVar m a'+        return b+      Nothing ->+        return WouldBlock++transactMVar :: MVar a ->+                (a -> (a -> IO ()) -> IO b) ->+                IO b+transactMVar m io = do+  restore <- saveBlock+  block $ do+    a <- takeMVar m+    r <- newIORef a+    restore (io a (writeIORef r))+      `finally` (readIORef r >>= putMVar m)++{-+tryTransactMVar :: MVar a ->+                   (a -> (a -> IO ()) -> IO (TryResult b)) ->+                   IO (TryResult b)+tryTransactMVar m io = do+  restore <- saveBlock+  block $ do+    maybea <- tryTakeMVar m+    case maybea of+      Just a -> do+        r <- newIORef a+        restore (io a (writeIORef r))+          `finally` (readIORef r >>= putMVar m)+      Nothing ->+        return WouldBlock+-}+
+ LICENSE view
@@ -0,0 +1,26 @@+Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:++Redistributions of source code must retain the above copyright+notice, this list of conditions and the following disclaimer.++Redistributions in binary form must reproduce the above copyright+notice, this list of conditions and the following disclaimer in the+documentation and/or other materials provided with the distribution.++Neither the name of the Northeastern University; nor the names of its+contributors may be used to endorse or promote products derived from+this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A+PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,4 @@+#!/usr/bin/env runhaskell++import Distribution.Simple+main = defaultMain
+ synchronous-channels.cabal view
@@ -0,0 +1,23 @@+Name:           synchronous-channels+Version:        0.1+Cabal-Version:  >= 1.2.3+License:        BSD3+License-File:   LICENSE+Stability:      experimental+Author:         Jesse A. Tov <tov@ccs.neu.edu>+Maintainer:     tov@ccs.neu.edu+Category:       Concurrency, Control+Synopsis:       Synchronous communication channels+Build-type:     Simple+Tested-with:    GHC == 6.10.1+Description:+        Synchronous communication channels.  These are similar to+        'Control.Concurrent.Chan.Chan',+        but a writer blocks until it can synchronize with a reader.++Library+  Build-Depends:        base >= 4+  GHC-Options:          -W -Wall++  Exposed-modules:+    Control.Concurrent.Chan.Synchronous