packages feed

sarsi 0.0.1.0 → 0.0.2.0

raw patch · 15 files changed

+202/−118 lines, 15 filesdep +asyncdep +stmdep ~sarsiPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: async, stm

Dependency ranges changed: sarsi

API changes (from Hackage documentation)

- Sarsi: getSockPath :: FilePath -> IO FilePath
- Sarsi: mkSockAddr :: FilePath -> IO SockAddr
- Sarsi: mkSockAddr' :: FilePath -> IO SockAddr
- Sarsi: mkSocket :: IO Socket
- Sarsi.Consumer: serve :: Socket -> IOSink Event -> IO ()
+ Sarsi: Broker :: FilePath -> Broker
+ Sarsi: Topic :: Broker -> FilePath -> Topic
+ Sarsi: createSockAddr :: Topic -> IO SockAddr
+ Sarsi: createSocket :: IO Socket
+ Sarsi: data Topic
+ Sarsi: getBroker :: IO Broker
+ Sarsi: getSockAddr :: Topic -> SockAddr
+ Sarsi: getTopic :: Broker -> FilePath -> IO Topic
+ Sarsi: newtype Broker
+ Sarsi: removeTopic :: Topic -> IO ()
+ Sarsi.Consumer: consume' :: Topic -> (Maybe s -> IOSource Event -> IO (Either s a)) -> IO a
+ Sarsi.Consumer: consumeOrWait :: Topic -> (Maybe s -> IOSource Event -> IO (Either s a)) -> IO a
+ Sarsi.Consumer: consumeOrWait_ :: Topic -> IOSink Event -> IO a
+ Sarsi.Producer: serve :: (Handle -> IO (Maybe a)) -> Socket -> IO a
- Codec.Sarsi.SBT: end :: Parser Text ()
+ Codec.Sarsi.SBT: end :: Parser ()
- Sarsi.Consumer: consume :: FilePath -> IOSink Event -> IO ()
+ Sarsi.Consumer: consume :: Topic -> (Maybe s -> IOSource Event -> IO (Either s a)) -> IO (Either IOException a)
- Sarsi.Producer: produce :: FilePath -> (ProcessT IO Event Event -> IO a) -> IO a
+ Sarsi.Producer: produce :: Topic -> (ProcessT IO Event Event -> IO a) -> IO a

Files

sarsi-hs/Main.hs view
@@ -5,14 +5,14 @@ import Codec.Sarsi (Event(..), Level(..), Message(..)) import Codec.Sarsi.GHC (fromGHCLog) import Data.Attoparsec.Text.Machine (streamParser)-import Data.Machine (ProcessT, (<~), asParts, auto, autoM, before, echo, prepended, runT_, source)-import Data.Machine.Plan (yield)+import Data.Machine (ProcessT, (<~), asParts, auto, autoM, prepended, runT_, source) import Data.List (foldl')+import Sarsi (getBroker, getTopic) import Sarsi.Producer (produce) import System.Environment (getArgs)-import System.IO (stdin, stdout, stderr)-import System.IO.Machine (byLine, printer)-import System.Process (CreateProcess, StdStream(..), shell, std_in, std_out, std_err)+import System.IO (stderr)+import System.IO.Machine (byLine)+import System.Process (StdStream(..), shell, std_in, std_err) import System.Process.Machine (callProcessMachines, mStdErr) import System.Exit (ExitCode, exitWith) @@ -54,5 +54,7 @@ main :: IO () main = do   args  <- getArgs-  ec    <- produce "." $ producer (concat $ List.intersperse " " args)+  b     <- getBroker+  t     <- getTopic b "."+  ec    <- produce t $ producer (concat $ List.intersperse " " args)   exitWith ec
sarsi-nvim/Data/MessagePack/RPC.hs view
@@ -36,12 +36,13 @@   tpe <- MP.getInt   case tpe of     1 -> do-      id <- MP.getInt+      id' <- MP.getInt       err <- get       res <- get-      return $ Response id $ f err res where+      return $ Response id' $ f err res where         f ObjectNil res = Success res         f err ObjectNil = Error err+        f _ _           = Error ObjectNil     2 -> do       method <- MP.getStr       (ObjectArray params) <- get
sarsi-nvim/Main.hs view
@@ -1,12 +1,12 @@ module Main where -import Codec.Sarsi (Event(..), Level(..), Location(..), Message(..), getEvent)-import Data.Machine ((<~), asParts, auto, scan)+import Codec.Sarsi (Event(..), Level(..), Location(..), Message(..))+import Data.Machine (ProcessT, (<~), asParts, auto, final, scan, sinkPart_, runT) import Data.MessagePack.Object (Object(..), toObject) import NVIM.Client (Command(..), runCommand)-import Sarsi (title)-import Sarsi.Consumer (consume)-import System.IO (BufferMode(NoBuffering), hSetBinaryMode, hSetBuffering, stdin, stdout)+import Sarsi (getBroker, getTopic, title)+import Sarsi.Consumer (consumeOrWait)+import System.IO (BufferMode(NoBuffering), hSetBuffering, stdin, stdout) import System.IO.Machine (sinkIO)  import qualified Data.Text as Text@@ -46,11 +46,16 @@ main = do   hSetBuffering stdin NoBuffering   hSetBuffering stdout NoBuffering-  consume "." $ sinkIO publish <~ asParts <~ auto unpack <~ scan f (True, [])+  b     <- getBroker+  t     <- getTopic b "."+  consumeOrWait t consumer     where-      f (first, _) event = convert first event-      unpack (_, xs) = xs+      consumer Nothing  src = consumer (Just True) src+      consumer (Just b) src = do+        b' <- runT $ final <~ sinkPart_ id (sinkIO publish <~ asParts) <~ converter b <~ src+        return (Left $ head b')+      converter :: Bool -> ProcessT IO Event (Bool, [Command])+      converter b = scan f (b, []) where f (first, _) event = convert first event       publish cmd = do-        -- TODO Log failed command in a file (or print to stderr?).         _ <- runCommand cmd         return ()
sarsi-nvim/NVIM/Client.hs view
@@ -2,17 +2,15 @@  -- | A primitive NVIM RPC synchronous client. -import Data.Binary.Get (runGet) import Data.Binary.Machine (streamGet) import Data.Binary.Put (runPut) import Data.ByteString (hGetSome, hPut)-import Data.ByteString.Lazy (fromStrict, toStrict)+import Data.ByteString.Lazy (toStrict) import Data.Machine ((<~), asParts, auto, final, run, source) import Data.MessagePack.Object (Object(..), toObject) import Data.MessagePack.RPC (Answer(..), Request(..), Message(..), getMessage, putRequest) import Data.Text (Text) import System.IO (Handle, stdin, stdout)- import qualified Data.Text as Text import qualified Data.Vector as Vector 
sarsi-sbt/Main.hs view
@@ -1,41 +1,45 @@+{-# LANGUAGE Rank2Types #-} module Main where  import Codec.Sarsi (Event) import Codec.Sarsi.SBT.Machine (eventProcess)-import Data.Machine (ProcessT, (<~), asParts, auto, autoM, runT_)+import Data.Machine (ProcessT, (<~), autoM, runT_)+import Sarsi (getBroker, getTopic) import Sarsi.Producer (produce) import System.Environment (getArgs) import System.Exit (ExitCode, exitWith)-import System.Process (CreateProcess, StdStream(..), shell, std_in, std_out, std_err)-import System.Process.Machine (callProcessMachines, mStdOut)+import System.Process (StdStream(..), shell, std_in, std_out)+import System.Process.Machine (ProcessMachines, callProcessMachines) import System.IO (BufferMode(NoBuffering), hSetBuffering, stdin, stdout)-import System.IO.Machine (byChunk, printer)+import System.IO.Machine (IOSink, byChunk) -import qualified Data.Char as Char import qualified Data.List as List-import qualified Data.Text as Text import qualified Data.Text.IO as TextIO import qualified Sarsi as Sarsi  title :: String title = concat [Sarsi.title, "-sbt"] -callShell :: String -> ProcessT IO Event a -> IO (ExitCode, [a])-callShell cmd sink = callProcessMachines byChunk createProc (mStdOut pipeline)-  where-    pipeline = sink <~ eventProcess <~ echoText stdout-    echoText h = autoM $ (\txt -> TextIO.hPutStr h txt >> return txt)-    createProc  = (shell cmd) { std_in = Inherit, std_out = CreatePipe }+-- TODO Contribute to machines-process+mStdOut_ :: ProcessT IO a b -> ProcessMachines a a0 k0 -> IO ()+mStdOut_ mp (_, Just stdOut, _)  = runT_ $ mp <~ stdOut+mStdOut_ _  _                    = return () -producer :: String -> ProcessT IO Event Event -> IO ExitCode+producer :: String -> ProcessT IO Event Event -> IO (ExitCode) producer cmd sink = do-  (ec, _)   <- callShell cmd sink+  (ec, _) <- callProcessMachines byChunk createProc (mStdOut_ pipeline)   return ec+    where+      pipeline = sink <~ eventProcess <~ echoText stdout+      echoText h = autoM $ (\txt -> TextIO.hPutStr h txt >> return txt)+      createProc  = (shell cmd) { std_in = Inherit, std_out = CreatePipe }  main :: IO () main = do   hSetBuffering stdin NoBuffering   hSetBuffering stdout NoBuffering   args  <- getArgs-  ec    <- produce "." $ producer $ concat $ List.intersperse " " ("sbt":args)+  b     <- getBroker+  t     <- getTopic b "."+  ec    <- produce t $ producer $ concat $ List.intersperse " " ("sbt":args)   exitWith ec
sarsi.cabal view
@@ -1,5 +1,5 @@ name:                sarsi-version:             0.0.1.0+version:             0.0.2.0 synopsis:            A universal quickfix toolkit and his protocol.   description:@@ -34,6 +34,7 @@     Data.Attoparsec.Text.Machine   build-depends:       base                  >= 4.6.0.1    && < 5+    , async                 >= 2.1        && < 2.2     , attoparsec            >= 0.12       && < 0.14     , binary                >= 0.7        && < 0.9     , bytestring            >= 0.10       && < 0.11@@ -41,6 +42,7 @@     , cryptonite            >= 0.10       && < 0.14     , directory             >= 1.2        && < 1.3     , filepath              >= 1.4        && < 1.5+    , fsnotify              >= 0.2        && < 0.3     , machines              >= 0.6        && < 0.7     , machines-binary       >= 0.3.0.2    && < 0.4     , machines-process      >= 0.2.0.6    && < 0.3@@ -48,6 +50,7 @@     , msgpack               >= 1.0        && < 1.1     , network               >= 2.6        && < 2.7     , process               >= 1.1        && < 1.5+    , stm                   >= 2.4        && < 2.5     , text                  >= 1.2        && < 1.3     , vector                >= 0.10       && < 0.12 @@ -58,9 +61,9 @@   build-depends:              base     , Cabal-    , sarsi+    , sarsi                 == 0.0.2.0   hs-source-dirs:       sarsi-  ghc-options:          -Wall+  ghc-options:          -Wall -threaded   default-language:     Haskell2010  @@ -68,7 +71,7 @@   main-is:              Main.hs   build-depends:              base-    , sarsi                 == 0.0.1.0+    , sarsi                 == 0.0.2.0     , machines     , machines-io     , machines-process@@ -76,7 +79,7 @@     , text     , fsnotify              >= 0.2        && < 0.3   hs-source-dirs:       sarsi-hs-  ghc-options:          -Wall -dynamic+  ghc-options:          -Wall -dynamic -threaded   default-language:     Haskell2010  executable sarsi-nvim@@ -90,7 +93,7 @@     -- NVIM.Info   build-depends:              base-    , sarsi                 == 0.0.1.0+    , sarsi                 == 0.0.2.0     , machines     , binary     , bytestring@@ -105,7 +108,7 @@     , vector     , unordered-containers  >= 0.2        && < 0.3   hs-source-dirs:       sarsi-nvim-  ghc-options:          -Wall -dynamic+  ghc-options:          -Wall -dynamic -threaded   default-language:     Haskell2010  executable sarsi-sbt@@ -113,12 +116,12 @@   other-modules:   build-depends:              base-    , sarsi                 == 0.0.1.0+    , sarsi                 == 0.0.2.0     , machines     , machines-io     , machines-process     , process     , text   hs-source-dirs:       sarsi-sbt-  ghc-options:          -Wall -dynamic+  ghc-options:          -Wall -dynamic -threaded   default-language:     Haskell2010
sarsi/Main.hs view
@@ -1,7 +1,7 @@ module Main where  import Distribution.Text-import Sarsi (mkSockAddr, title)+import Sarsi (getBroker, getSockAddr, getTopic, title) import Sarsi.Trace (traceHS, traceSBT) import System.IO (stdin) import System.Environment (getArgs)@@ -14,5 +14,6 @@   run ["--trace-sbt"] = traceSBT stdin   run ["--version"]   = putStrLn $ concat [title, "-", display version]   run _ = do-    sock <- mkSockAddr "."-    print sock+    b     <- getBroker+    t     <- getTopic b "."+    print $ getSockAddr t
src/Codec/Sarsi.hs view
@@ -15,7 +15,7 @@   | Notify { message :: Message }  instance Show Event where-  show (Start label) = concat ["starting ", unpack label, " build"]+  show (Start lbl) = concat ["starting ", unpack lbl, " build"]   show (Finish 0 0) = "build success"   show (Finish 0 w) = concat ["build success with ", show w, " warning(s)"]   show (Finish e 0) = concat ["build fail with ", show e, " error(s)"]
src/Codec/Sarsi/GHC.hs view
@@ -1,7 +1,7 @@ module Codec.Sarsi.GHC where  import Codec.Sarsi-import Data.Text (Text, strip)+import Data.Text (strip)  import qualified Codec.GHC.Log as Log import qualified Data.Vector as Vector
src/Codec/Sarsi/SBT.hs view
@@ -33,18 +33,17 @@   fp  <- takeWhile1 (\c -> c /= sepChar && c /= '\n' && c /= '\r') <* char sepChar   ln  <- decimal <* char sepChar   t   <- space *> (untilLineBreak <* "\n")-  ts  <- manyTill' (lineStart *> (untilLineBreak <* "\n")) (lookAhead $ column)-  col <- column+  ts  <- manyTill' (lineStart *> (untilLineBreak <* "\n")) (lookAhead $ column')+  col <- column'   _   <- end   return $ Message (Location fp (col) ln) lvl $ formatTxts t ts     where-      takeLineBreak = takeWhile1 $ \w -> w == '\n' || w == '\r'       level = choice [string "[error]" *> return Error, string "[warn]" *> return Warning]       lineStart = level <* space       sepChar = ':'       formatTxts t [] = Vector.singleton t       formatTxts t ts = Vector.fromList $ t : init ts-      column = level *> ((length <$> many1 space) <* "^\n")+      column' = level *> ((length <$> many1 space) <* "^\n")  cleanEC :: Parser Text cleanEC = choice [noEC, withEC]@@ -62,6 +61,9 @@     isEscEnd 109 = True     isEscEnd _ = False +untilLineBreak :: Parser Text untilLineBreak = takeWhile1 $ \w -> w /= '\n' && w /= '\r'++end :: Parser () end = choice [const () <$> "\n", endOfInput, return ()] 
src/Codec/Sarsi/SBT/Machine.hs view
@@ -3,7 +3,7 @@ import Codec.Sarsi (Event(..), Message(..), Level(..)) import Codec.Sarsi.SBT (SBTEvent(..), eventParser, cleanEC, untilLineBreak, end) import Data.Attoparsec.Text.Machine (processParser, streamParser)-import Data.Machine (ProcessT, (<~), asParts, auto, autoM, scan, runT_)+import Data.Machine (ProcessT, (<~), asParts, auto, scan) import Data.Text (Text)  import qualified Data.Text as Text
src/Data/Attoparsec/Machine.hs view
@@ -1,8 +1,8 @@ {-# LANGUAGE Rank2Types #-} module Data.Attoparsec.Machine where -import Data.Attoparsec.Internal.Types (IResult(..), Parser)-import Data.Machine (MachineT(..), ProcessT, Step(Await, Yield), Is(Refl), source, stopped)+import Data.Attoparsec.Internal.Types (IResult(..))+import Data.Machine (MachineT(..), ProcessT, Step(Await, Yield), Is(Refl), stopped)  streamParserWith :: (Monoid i, Monad m) => (i -> IResult i a) -> ProcessT m i (Either String a) streamParserWith runParser = start where
src/Sarsi.hs view
@@ -3,7 +3,7 @@ import Crypto.Hash (Digest, hash) import Crypto.Hash.Algorithms (MD5) import Network.Socket (Family(AF_UNIX), SockAddr(SockAddrUnix), Socket, SocketType(Stream), defaultProtocol, socket)-import System.Directory (getTemporaryDirectory, doesFileExist, makeAbsolute, removeFile)+import System.Directory (getTemporaryDirectory, createDirectory, doesDirectoryExist, doesFileExist, makeAbsolute, removeFile) import System.FilePath ((</>))  import qualified Data.ByteString.Char8 as BSC8@@ -11,26 +11,35 @@ title :: String title = "sarsi" --- TODO Windows compat: create a TCP socket.+newtype Broker = Broker FilePath -mkSocket :: IO Socket-mkSocket = do-  socket AF_UNIX Stream defaultProtocol+data Topic = Topic Broker FilePath -mkSockAddr :: FilePath -> IO SockAddr-mkSockAddr fp = do-  path  <- getSockPath fp-  return . SockAddrUnix $ path+getBroker :: IO Broker+getBroker = do+  tmp <- getTemporaryDirectory+  let bp = tmp </> title+  let broker = Broker bp+  exists <- doesDirectoryExist bp+  if exists then return broker else createDirectory bp >> return broker -mkSockAddr' :: FilePath -> IO SockAddr-mkSockAddr' fp = do-  path  <- getSockPath fp+getTopic :: Broker -> FilePath -> IO Topic+getTopic b@(Broker bp) fp' = do+  fp    <- makeAbsolute fp'+  return $ Topic b $ bp </> (show $ (hash $ BSC8.pack fp :: Digest MD5))++removeTopic :: Topic -> IO ()+removeTopic (Topic _ fp) = removeFile fp++createSocket :: IO Socket+createSocket = do+  socket AF_UNIX Stream defaultProtocol++createSockAddr :: Topic -> IO SockAddr+createSockAddr t@(Topic _ path) = do   exists <- doesFileExist path   if (exists) then removeFile path else return ()-  mkSockAddr fp+  return $ getSockAddr t -getSockPath :: FilePath -> IO FilePath-getSockPath fp' = do-  fp    <- makeAbsolute fp'-  tmp <- getTemporaryDirectory-  return $ tmp </> concat [title, "-", show $ (hash $ BSC8.pack fp :: Digest MD5)]+getSockAddr :: Topic -> SockAddr+getSockAddr (Topic _ path) = SockAddrUnix path
src/Sarsi/Consumer.hs view
@@ -2,29 +2,47 @@ module Sarsi.Consumer where  import Codec.Sarsi (Event, getEvent)-import Data.Binary.Machine (streamGet)-import Data.Machine ((<~), asParts, auto, runT_)-import Network.Socket (Socket, accept, bind, listen, close, socketToHandle)-import Sarsi (mkSocket, mkSockAddr')-import System.IO (IOMode(ReadMode), hClose)-import System.IO.Machine (IOSink, byChunk, sourceHandle)--consume :: FilePath -> IOSink Event -> IO ()-consume fp sink = do-  sock  <- mkSocket-  addr  <- mkSockAddr' fp-  bind sock addr-  listen sock 1-  serve sock sink-  close sock+import Control.Concurrent.MVar (newEmptyMVar, takeMVar, putMVar)+import Control.Exception (IOException, bracket, try)+import Data.Binary.Machine (processGet)+import Data.Machine ((<~), auto, asParts, runT_)+import Network.Socket (connect, socketToHandle)+import Sarsi (Broker(..), Topic(..), createSocket, getSockAddr)+import System.FSNotify (eventPath, watchDir, withManager)+import System.IO (IOMode(ReadMode), hClose, hWaitForInput)+import System.IO.Machine (IOSink, IOSource, byChunkOf, sourceHandle) -serve :: Socket -> IOSink Event -> IO ()-serve sock sink = do-  (conn, _) <- accept sock-  h     <- socketToHandle conn ReadMode-  runT_ $ sink <~ asParts <~ auto unpack <~ streamGet getEvent <~ sourceHandle (byChunk) h-  hClose h-  serve sock sink+consumeOrWait :: Topic -> (Maybe s -> IOSource Event -> IO (Either s a)) -> IO a+consumeOrWait topic@(Topic (Broker bp) tp) f = do+  res <- consume topic f+  either (const $ withManager waitAndRetry) return res     where-      unpack (Right e) = [e]-      unpack (Left _) = []+      waitAndRetry mng = do+        lck <- newEmptyMVar+        stop <- watchDir mng bp pred' $ const $ putMVar lck ()+        takeMVar lck+        stop+        consumeOrWait topic f+      pred' e = eventPath e == tp++consumeOrWait_ :: Topic -> IOSink Event  -> IO a+consumeOrWait_ topic@(Topic _ _) sink =+    consumeOrWait topic f where f _ src = fmap (const $ Left ()) $ runT_ $ sink <~ src++consume :: Topic -> (Maybe s -> IOSource Event -> IO (Either s a)) -> IO (Either IOException a)+consume topic f = try $ consume' topic f++consume' :: Topic -> (Maybe s -> IOSource Event -> IO (Either s a)) -> IO a+consume' topic f = bracket createHandle hClose (process Nothing)+  where+    createHandle = do+      sock  <- createSocket+      connect sock $ getSockAddr topic+      socketToHandle sock ReadMode+    process s h = do+      sa  <- f s $ asParts <~ auto unpack <~ processGet getEvent <~ sourceHandle (byChunkOf 1) h+      _   <- hWaitForInput h (-1)+      either (continue h) return sa+        where continue h' s' = process (Just s') h'+    unpack (Right e) = [e]+    unpack (Left _) = []
src/Sarsi/Producer.hs view
@@ -1,26 +1,67 @@ {-# LANGUAGE Rank2Types #-} module Sarsi.Producer where -import Codec.Sarsi (Event(..), putEvent)+import Codec.Sarsi (Event(Start), putEvent)+import Control.Exception (IOException, bracket, tryJust)+import Control.Concurrent.Async (async, cancel, wait)+import Control.Concurrent.Chan (dupChan, newChan, readChan, writeChan)+import Control.Concurrent.MVar (modifyMVar_, newMVar, readMVar)+import Control.Concurrent.STM (atomically)+import Control.Concurrent.STM.TQueue (newTQueue, tryReadTQueue, writeTQueue) import Data.Binary.Machine (processPut)-import Data.Machine (ProcessT, (<~), auto, asParts, runT_)-import Data.Machine.Fanout (fanout)-import Network.Socket (connect, socketToHandle)-import System.IO (IOMode(AppendMode), hClose)-import System.IO.Machine (byChunk, sinkHandle)--import qualified Data.Text as T--import Sarsi (mkSocket, mkSockAddr)+import Data.Machine (ProcessT, (<~), runT_, sinkPart_, prepended)+import Network.Socket (Socket, accept, bind, close, listen, socketToHandle)+import Sarsi (Topic, createSocket, createSockAddr, removeTopic)+import System.IO (IOMode(WriteMode), Handle, hClose)+import System.IO.Machine (byChunk, sinkIO, sinkHandle, sourceIO) -produce :: FilePath -> (ProcessT IO Event Event -> IO a) -> IO a-produce fp f = do-  sock  <- mkSocket-  addr  <- mkSockAddr fp-  connect sock addr-  h     <- socketToHandle sock AppendMode-  -- TODO use sinkPart_-  res   <- f $ asParts <~ fanout [auto (:[]), auto (const []) <~ sinkHandle byChunk h <~ processPut putEvent]-  hClose h-  return res+produce :: Topic -> (ProcessT IO Event Event -> IO a) -> IO a+produce t f = do+  conns   <- atomically $ newTQueue+  chan    <- newChan+  state   <- newMVar []+  server  <- async $ bracket bindSock close (serve (process conns chan state))+  feeder  <- async $ f $ sinkPart_ (\x -> (x, x)) (sinkIO $ feed chan state)+  a       <- wait feeder+  waitFinish conns+  cancel server+  removeTopic t+  return a+    where+      bindSock = do+        sock  <- createSocket+        addr  <- createSockAddr t+        bind sock addr+        listen sock 1+        return sock+      process conns chan' state h = do+        chan <- dupChan chan'+        es   <- readMVar state+        conn <- async $ do+          runT_ $ sinkHandle byChunk h <~ processPut putEvent <~ (prepended $ reverse es) <~ (sourceIO $ readChan chan)+          hClose h+        atomically $ writeTQueue conns conn+        return Nothing+      feed chan state e = do+        modifyMVar_ state $ case e of+          (Start _) -> const $ return [e]+          _         -> return . (:) e+        writeChan chan e+      waitFinish conns = do+        conn <- atomically $ tryReadTQueue conns+        _    <- tryJust io $ maybe (return ()) wait conn+        return ()+          where+            io :: IOException -> Maybe ()+            io _ = Just () +serve :: (Handle -> IO (Maybe a)) -> Socket -> IO a+serve f sock = bracket acceptHandle hClose process+  where+    acceptHandle = do+      (conn, _) <- accept sock+      h         <- socketToHandle conn WriteMode+      return h+    process h = do+      a         <- f h+      maybe (serve f sock) return a