git.iko.soy/distributed-process
on master help sign in
merged upstream changes with lazy/strict switch and bug fixes
900dea9Adam Foltzer
README.md+3−3
@@ -25,10 +25,10 @@ The `DemoTransport.hs` file contains a number of different functions named
25To try them out, launch `ghci`, including the appropriate sources:
25To try them out, launch `ghci`, including the appropriate sources:
26
26
27 ghci -idistributed-process/src -inetwork-transport/src examples/DemoTransport.hs
27 ghci -idistributed-process/src -inetwork-transport/src examples/DemoTransport.hs
28+
29+The first demo, `demo0` yields the following output when run with the TCP transport:
28
30
29−The first demo, `demo0` yields the following output:
30−
31− *DemoTransport> demo0
31+ *DemoTransport> tcp >>= demo0
32 logServer: [Chunk "hello 1" Empty]
32 logServer: [Chunk "hello 1" Empty]
33 logServer: [Chunk "hello 2" Empty]
33 logServer: [Chunk "hello 2" Empty]
34 logServer: [Chunk "hello 3" Empty]
34 logServer: [Chunk "hello 3" Empty]
benchmarks/Makefilenew file+27
@@ -0,0 +1,27 @@
1+
2+
3+ifeq ($(GHC),)
4+ GHC=ghc
5+endif
6+
7+all: pipes tcp transport
8+tcp:
9+ $(GHC) -O2 -rtsopts -threaded --make -i../network-transport/src PingTCP.hs -o PingTCP.exe
10+transport:
11+ $(GHC) -O2 -rtsopts -threaded --make -i../network-transport/src PingTransport.hs -o PingTransport.exe
12+pipes:
13+ $(GHC) -O2 -rtsopts -threaded --make -i../network-transport/src PingPipes.hs -o PingPipes.exe
14+
15+run: runBaseline runTransport
16+runBaseline:
17+ ./PingTCP.exe server 8080 &
18+ sleep 1
19+ ./PingTCP.exe client 0.0.0.0 8080 1000
20+
21+runTransport:
22+ ./PingTransport.exe server 0.0.0.0 8080 sourceAddr &
23+ sleep 1
24+ ./PingTransport.exe client 0.0.0.0 8081 sourceAddr 1000
25+
26+clean:
27+ rm -f *.hi *.o *.exe
benchmarks/PingPipes.hsnew file+94
@@ -0,0 +1,94 @@
1+module Main where
2+
3+import Network.Transport
4+import Network.Transport.Pipes (mkTransport)
5+
6+import Control.Monad (forever, replicateM_)
7+import Criterion.Main (Benchmark, bench, defaultMain, nfIO)
8+import Data.Binary
9+import Data.Maybe (fromJust)
10+import Data.Int
11+import System.Environment (getArgs, withArgs)
12+
13+import qualified Data.ByteString.Lazy.Char8 as BS
14+
15+-- | This performs a ping benchmark on the TCP transport. This can be
16+-- compiled using:
17+--
18+-- ghc --make -idistributed-process/src -inetwork-transport/src -O2 benchmarks/PingTransport.hs
19+--
20+-- To use the compiled binary, first set up a server:
21+--
22+-- ./benchmarks/PingPipes server sourceAddr
23+--
24+-- Once this is established, launch a client to perform the benchmark. The
25+-- following command sends 1000 pings per mark.
26+--
27+-- ./benchmarks/PingPipes client sourceAddr 1000
28+--
29+-- The server must be restarted between benchmarks.
30+main :: IO ()
31+main = do
32+ args <- getArgs
33+ case args of
34+ "server" : sourceAddrFilePath : [] -> do
35+ -- establish transport
36+ transport <- mkTransport
37+
38+ -- create ping end
39+ putStrLn "server: creating ping end"
40+ (sourceAddrPing, targetEndPing) <- newConnection transport
41+ BS.writeFile sourceAddrFilePath $ serialize sourceAddrPing
42+
43+ -- create pong end
44+ putStrLn "server: creating pong end"
45+ [sourceAddrPongBS] <- receive targetEndPing
46+ sourceEndPong <- connect . fromJust $ deserialize transport sourceAddrPongBS
47+
48+ -- always respond to a ping with a pong
49+ putStrLn "server: awaiting pings"
50+ forever $ pong targetEndPing sourceEndPong
51+
52+
53+ "client" : sourceAddrFilePath : pingsStr : args' -> do
54+ let pings = read pingsStr
55+ -- establish transport
56+ transport <- mkTransport
57+
58+ -- create ping end
59+ sourceAddrPingBS <- BS.readFile sourceAddrFilePath
60+ sourceEndPing <- connect . fromJust $ deserialize transport sourceAddrPingBS
61+
62+ -- create pong end
63+ (sourceAddrPong, targetEndPong) <- newConnection transport
64+ send sourceEndPing [serialize sourceAddrPong]
65+
66+ -- benchmark the pings
67+-- withArgs args' $ defaultMain [ benchPing sourceEndPing targetEndPong pings]
68+ replicateM_ pings (ping sourceEndPing targetEndPong)
69+ putStrLn "Done with all ping/pongs."
70+
71+-- | This function takes a `TargetEnd` for the pings, and a `SourceEnd` for
72+-- pongs. Whenever a ping is received from the `TargetEnd`, a pong is sent
73+-- in reply down the `SourceEnd`, repeating whatever was sent.
74+pong :: TargetEnd -> SourceEnd -> IO ()
75+pong targetEndPing sourceEndPong = do
76+ bs <- receive targetEndPing
77+ send sourceEndPong bs
78+
79+-- | The effect of `ping sourceEndPing targetEndPong n` is to send the number
80+-- `n` using `sourceEndPing`, and to then receive the a number from
81+-- `targetEndPong`, which is then returned.
82+ping :: SourceEnd -> TargetEnd -> IO Int64
83+ping sourceEndPing targetEndPong = do
84+ send sourceEndPing [BS.empty]
85+ [bs] <- receive targetEndPong
86+ return $ decode bs
87+
88+-- | The effect of `benchPing sourceEndPing targetEndPong n` is to send
89+-- `n` pings down `sourceEndPing` using the `ping` function. The time
90+-- taken is benchmarked.
91+benchPing :: SourceEnd -> TargetEnd -> Int64 -> Benchmark
92+benchPing sourceEndPing targetEndPong n = bench "PingTransport" $
93+ nfIO (replicateM_ (fromIntegral n) (ping sourceEndPing targetEndPong))
94+
benchmarks/PingTCP.hs+3−1
@@ -86,6 +86,7 @@ main = do
86
86
87 -- benchmark the pings
87 -- benchmark the pings
88 withArgs args' $ defaultMain [ benchPing sock pings ]
88 withArgs args' $ defaultMain [ benchPing sock pings ]
89+-- replicateM_ pings (ping sock)
89
90
90-- | Each `ping` sends a single byte, and expects to receive one
91-- | Each `ping` sends a single byte, and expects to receive one
91-- back in return.
92-- back in return.
@@ -102,6 +103,7 @@ pong sock = do
102 return ()
103 return ()
103
104
104benchPing :: Socket -> Int64 -> Benchmark
105benchPing :: Socket -> Int64 -> Benchmark
105−benchPing sock n = bench "PingTCP" $
106+benchPing sock n =
107+ bench "PingTCP" $
106 nfIO (replicateM_ (fromIntegral n) (ping sock))
108 nfIO (replicateM_ (fromIntegral n) (ping sock))
107
109
benchmarks/PingTransport.hs+1
@@ -83,6 +83,7 @@ main = do
83
83
84 -- benchmark the pings
84 -- benchmark the pings
85 withArgs args' $ defaultMain [ benchPing sourceEndPing targetEndPong pings]
85 withArgs args' $ defaultMain [ benchPing sourceEndPing targetEndPong pings]
86+-- replicateM_ pings (ping sourceEndPing targetEndPong)
86
87
87-- | This function takes a `TargetEnd` for the pings, and a `SourceEnd` for
88-- | This function takes a `TargetEnd` for the pings, and a `SourceEnd` for
88-- pongs. Whenever a ping is received from the `TargetEnd`, a pong is sent
89-- pongs. Whenever a ping is received from the `TargetEnd`, a pong is sent
examples/DemoTransport.hs+77−27
@@ -1,7 +1,9 @@
1+{-# LANGUAGE NamedFieldPuns #-}
1−module DemoTransport where
2+module Main where
2
3
3−import Network.Transport
4+import Network.Transport
4−import qualified Network.Transport.MVar
5+import qualified Network.Transport.MVar
6+import qualified Network.Transport.Pipes
5import Network.Transport.TCP (mkTransport, TCPConfig (..))
7import Network.Transport.TCP (mkTransport, TCPConfig (..))
6
8
7import Control.Concurrent
9import Control.Concurrent
@@ -9,16 +11,27 @@ import Control.Monad
9
11
10import qualified Data.ByteString.Lazy.Char8 as BS
12import qualified Data.ByteString.Lazy.Char8 as BS
11
13
14+import Data.IORef
12import Debug.Trace
15import Debug.Trace
13
16
14-------------------------------------------
17-------------------------------------------
18+
19+{- Run a demo above with one of these tranpsort shorthands like this:
20+ tcp >>= demo0
21+ -}
22+tcp = mkTCPOff 8080
23+mvar = return Network.Transport.MVar.mkTransport
24+pipes = return Network.Transport.Pipes.mkTransport
25+
26+
27+-------------------------------------------
15−-- Example program using backend directly
28+-- Example programs using backend directly
16−--
29+
17
30
18-- | Check if multiple messages can be sent on the same connection.
31-- | Check if multiple messages can be sent on the same connection.
19−demo0 :: IO ()
32+demo0 :: IO Transport -> IO ()
20−demo0 = do
33+demo0 mktrans = do
21− trans <- mkTransport $ TCPConfig undefined "127.0.0.1" "8080"
34+ trans <- mktrans
22
35
23 (sourceAddr, targetEnd) <- newConnection trans
36 (sourceAddr, targetEnd) <- newConnection trans
24
37
@@ -32,11 +45,11 @@ demo0 = do
32
45
33 closeTransport trans
46 closeTransport trans
34
47
48+
35-- | Check endpoint serialization and deserialization.
49-- | Check endpoint serialization and deserialization.
36−demo1 :: IO ()
50+demo1 :: IO Transport -> IO ()
37−demo1 = do
51+demo1 mktrans = do
38− -- trans <- mkTransport
52+ trans <- mktrans
39− trans <- mkTransport $ TCPConfig undefined "127.0.0.1" "8080"
40
53
41 (sourceAddr, targetEnd) <- newConnection trans
54 (sourceAddr, targetEnd) <- newConnection trans
42
55
@@ -54,28 +67,28 @@ demo1 = do
54 threadDelay 100000
67 threadDelay 100000
55 closeTransport trans
68 closeTransport trans
56
69
70+
57-- | Check that messages can be sent before receive is set up.
71-- | Check that messages can be sent before receive is set up.
58−demo2 :: IO ()
72+demo2 :: IO Transport -> IO ()
59−demo2 = do
73+demo2 mktrans = do
60− -- trans <- mkTransport
74+ trans <- mktrans
61− trans <- mkTransport $ TCPConfig undefined "127.0.0.1" "8080"
62
75
63 (sourceAddr, targetEnd) <- newConnection trans
76 (sourceAddr, targetEnd) <- newConnection trans
64−
65 sourceEnd <- connect sourceAddr
77 sourceEnd <- connect sourceAddr
78+
66 forkIO $ send sourceEnd [BS.pack "hello 1"]
79 forkIO $ send sourceEnd [BS.pack "hello 1"]
67 threadDelay 100000
80 threadDelay 100000
68
81
69 forkIO $ logServer "logServer" targetEnd
82 forkIO $ logServer "logServer" targetEnd
70 threadDelay 100000
83 threadDelay 100000
71−
72 closeTransport trans
84 closeTransport trans
73
85
86+
74-- | Check that two different transports can be created.
87-- | Check that two different transports can be created.
75−demo3 :: IO ()
88+demo3 :: IO Transport -> IO ()
76−demo3 = do
89+demo3 mktrans = do
77− trans1 <- mkTransport $ TCPConfig undefined "127.0.0.1" "8080"
90+ trans1 <- mktrans
78− trans2 <- mkTransport $ TCPConfig undefined "127.0.0.1" "8081"
91+ trans2 <- mktrans
79
92
80 (sourceAddr1, targetEnd1) <- newConnection trans1
93 (sourceAddr1, targetEnd1) <- newConnection trans1
81 (sourceAddr2, targetEnd2) <- newConnection trans2
94 (sourceAddr2, targetEnd2) <- newConnection trans2
@@ -96,11 +109,11 @@ demo3 = do
96 closeTransport trans1
109 closeTransport trans1
97 closeTransport trans2
110 closeTransport trans2
98
111
112+
99-- | Check that two different connections on the same transport can be created.
113-- | Check that two different connections on the same transport can be created.
100−demo4 :: IO ()
114+demo4 :: IO Transport -> IO ()
101−demo4 = do
115+demo4 mktrans = do
102− trans <- mkTransport $ TCPConfig undefined "127.0.0.1" "8080"
116+ trans <- mktrans
103− -- trans <- Network.Transport.MVar.mkTransport
104
117
105 (sourceAddr1, targetEnd1) <- newConnection trans
118 (sourceAddr1, targetEnd1) <- newConnection trans
106 (sourceAddr2, targetEnd2) <- newConnection trans
119 (sourceAddr2, targetEnd2) <- newConnection trans
@@ -120,7 +133,44 @@ demo4 = do
120 killThread threadId2
133 killThread threadId2
121 closeTransport trans
134 closeTransport trans
122
135
136+--------------------------------------------------------------------------------
137+
123logServer :: String -> TargetEnd -> IO ()
138logServer :: String -> TargetEnd -> IO ()
124logServer name targetEnd = forever $ do
139logServer name targetEnd = forever $ do
125 x <- receive targetEnd
140 x <- receive targetEnd
126− trace (name ++ ": " ++ show x) $ return ()
141+ trace (name ++ " rcvd: " ++ show x) $ return ()
142+
143+mkTCPOff off = do
144+ cntr <- newIORef 0
145+ return$ do cnt <- readIORef cntr
146+ writeIORef cntr (cnt+1)
147+ mkTransport (TCPConfig undefined "127.0.0.1" (show (off + cnt)))
148+
149+--------------------------------------------------------------------------------
150+
151+runWAllTranports :: (IO Transport -> IO ()) -> Int -> IO ()
152+runWAllTranports demo offset = do
153+ putStrLn "------------------------------------------------------------"
154+ putStrLn " MVAR transport:"
155+ mvar >>= demo
156+ putStrLn "\n TCP transport:"
157+ tcp >>= demo
158+ putStrLn "\n PIPES transport:"
159+ pipes >>= demo
160+ putStrLn "\n"
161+
162+
163+main = do
164+ putStrLn "Demo0:"
165+ runWAllTranports demo0 0
166+ putStrLn "Demo1:"
167+ runWAllTranports demo1 10
168+ putStrLn "Demo2:"
169+ runWAllTranports demo2 20
170+ putStrLn "Demo3:"
171+ runWAllTranports demo3 30
172+ putStrLn "Demo4:"
173+ runWAllTranports demo4 40
174+
175+ threadDelay (300 * 1000)
176+ putStrLn "Done with all demos!"
examples/Makefilenew file+13
@@ -0,0 +1,13 @@
1+
2+
3+ifeq ($(GHC),)
4+ GHC=ghc
5+endif
6+
7+all:
8+ $(GHC) -threaded --make -i../network-transport/src DemoTransport.hs -o DemoTransport.exe -fforce-recomp
9+
10+
11+clean:
12+ rm -f *.hi *.o DemoTransport.exe
13+
network-transport-pipes/LICENSEnew file+31
@@ -0,0 +1,31 @@
1+Copyright Ryan Newton, 2011
2+
3+All rights reserved.
4+
5+Redistribution and use in source and binary forms, with or without
6+modification, are permitted provided that the following conditions are met:
7+
8+ * Redistributions of source code must retain the above copyright
9+ notice, this list of conditions and the following disclaimer.
10+
11+ * Redistributions in binary form must reproduce the above
12+ copyright notice, this list of conditions and the following
13+ disclaimer in the documentation and/or other materials provided
14+ with the distribution.
15+
16+ * Neither the name of the owner nor the names of other
17+ contributors may be used to endorse or promote products derived
18+ from this software without specific prior written permission.
19+
20+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+
network-transport-pipes/network-transport-pipes.cabalnew file+28
@@ -0,0 +1,28 @@
1+Name: network-transport-pipes
2+Version: 0.0.1
3+Description: Named Pipes backend for network-transport interface.
4+License: BSD3
5+License-file: LICENSE
6+Author: Ryan Newton
7+Maintainer: rrnewton@gmail.com
8+Homepage: http://github.com/haskell-distributed/
9+Build-Type: Simple
10+Cabal-Version: >=1.2
11+
12+Library
13+ Build-Depends: base >= 3 && < 5,
14+ binary >= 0.5,
15+ cereal,
16+ bytestring >= 0.9,
17+ network-transport,
18+ containers >= 0.4,
19+-- network >= 2.3,
20+-- safe >= 0.3,
21+ unix >= 2.5.0.0,
22+ random,
23+ directory,
24+ unix-bytestring >= 0.3.5.3
25+ Exposed-modules: Network.Transport.Pipes
26+-- Extensions: BangPatterns
27+ ghc-options: -Wall
28+ HS-Source-Dirs: src
network-transport-pipes/src/Network/Transport/Pipes.hsnew file+235
@@ -0,0 +1,235 @@
1+{-# LANGUAGE BangPatterns, CPP, ScopedTypeVariables, PackageImports #-}
2+
3+----------------------------------------------------------------------------------------------------
4+
5+-- WARNING -- This transport is not yet completed. TODO:
6+
7+-- * Add support for messages greater than 4096 bytes.
8+-- * debug ODD problem with CEREAL below
9+-- * switch to unix-bytestring after that package is updated for 7.4.1
10+
11+----------------------------------------------------------------------------------------------------
12+
13+module Network.Transport.Pipes
14+ ( mkTransport
15+ ) where
16+
17+import Control.Monad (when, unless)
18+import Control.Exception (evaluate, throw, handle, fromException,
19+ SomeException, IOException)
20+import Control.Concurrent.MVar
21+import Control.Concurrent (threadDelay, forkOS)
22+import Data.IntMap (IntMap)
23+-- import Data.ByteString.Char8 (ByteString)
24+import Data.Word
25+import Data.Int
26+import Data.IORef
27+import qualified Data.IntMap as IntMap
28+import qualified Data.ByteString.Char8 as BSS
29+import qualified Data.ByteString.Lazy.Char8 as BS
30+
31+-- For some STRANGE reason this is not working with Data.Binary [2012.02.20]:
32+#define CEREAL
33+#ifdef CEREAL
34+import Data.Serialize (encode,decode) -- Uses strict BS
35+#else
36+import Data.Binary (encode,decode) -- Uses lazy BS
37+#endif
38+
39+import Data.List (foldl')
40+import Network.Transport
41+import System.Random (randomIO)
42+import System.IO (IOMode(ReadMode,AppendMode,WriteMode,ReadWriteMode),
43+ openFile, hClose, hPutStrLn, hPutStr, stderr, stdout, hFlush)
44+import System.IO.Error (ioeGetHandle)
45+import System.Posix.Files (createNamedPipe, unionFileModes, ownerReadMode, ownerWriteMode)
46+import System.Posix.Types (Fd, ByteCount)
47+import System.Directory (doesFileExist, removeFile)
48+import System.Mem (performGC)
49+
50+#define USE_UNIX_BYTESTRING
51+#ifdef USE_UNIX_BYTESTRING
52+import qualified "unix-bytestring" System.Posix.IO.ByteString as PIO
53+-- import qualified "unix-bytestring" System.Posix.IO.ByteString.Lazy as PIO
54+import System.Posix.IO as PIO (openFd, closeFd, -- append, exclusive, noctty, nonBlock, trunc,
55+ OpenFileFlags(..), OpenMode(ReadOnly, WriteOnly))
56+-- (fromS,toS) = (BS.pack, BS.unpack)
57+(fromS,toS) = (BSS.pack, BSS.unpack)
58+fromBS = id
59+readit fd n = PIO.fdRead fd n
60+#else
61+import qualified System.Posix.IO as PIO
62+(toS,fromS) = (id,id)
63+fromBS = BSS.unpack
64+readit fd n = do (s,_) <- PIO.fdRead fd n
65+ return (BSS.pack s)
66+#endif
67+-- readit :: Fd -> Int -> IO BS.ByteString
68+readit :: Fd -> ByteCount -> IO BSS.ByteString
69+
70+----------------------------------------------------------------------------------------------------
71+
72+-- The msg header consists of just a length field represented as a Word32
73+sizeof_header = 4
74+
75+fileFlags =
76+ PIO.OpenFileFlags {
77+ PIO.append = False,
78+ PIO.exclusive = False,
79+ PIO.noctty = False,
80+ PIO.nonBlock = True,
81+ -- In nonblocking mode opening for read will always succeed and
82+ -- opening for write must happen second.
83+ PIO.trunc = False
84+ }
85+-- NOTE:
86+-- "The only open file status flags that can be meaningfully applied
87+-- to a pipe or FIFO are O_NONBLOCK and O_ASYNC. "
88+
89+
90+mkTransport :: IO Transport
91+mkTransport = do
92+ uid <- randomIO :: IO Word64
93+ lock <- newMVar ()
94+ let filename = "/tmp/pipe_"++show uid
95+ createNamedPipe filename $ unionFileModes ownerReadMode ownerWriteMode
96+
97+ return Transport
98+ { newConnectionWith = \ _ -> do
99+ -- Here we protect from blocking other threads by running on a separate (OS) thread:
100+ -- Opening the file on the reader side should always succeed:
101+ mv <- onOSThread$ tryUntilNoIOErr $
102+-- spinTillThere filename
103+ PIO.openFd filename PIO.ReadOnly Nothing fileFlags
104+
105+ return (mkSourceAddr filename,
106+ mkTargetEnd mv lock)
107+ , newMulticastWith = error "Pipes.hs: newMulticastWith not implemented yet"
108+ , deserialize = \bs -> return$ mkSourceAddr (BS.unpack bs)
109+ , closeTransport = removeFile filename
110+ }
111+ where
112+ mkSourceAddr :: String -> SourceAddr
113+ mkSourceAddr filename = SourceAddr
114+ { connectWith = \_ -> mkSourceEnd filename
115+ , serialize = BS.pack filename
116+ }
117+
118+ mkSourceEnd :: String -> IO SourceEnd
119+ mkSourceEnd filename = do
120+ -- Initiate but do not block on file opening:
121+ mv <- onOSThread$ tryUntilNoIOErr $
122+ -- The reader must connect first, the writer here spins with backoff.
123+ PIO.openFd filename PIO.WriteOnly Nothing fileFlags
124+
125+ return $
126+ -- Write to the named pipe. If the message is less than
127+ -- PIPE_BUF (4KB on linux) then this should be atomic, otherwise
128+ -- we have to do something more sophisticated.
129+ SourceEnd
130+ { send = \bsls -> do
131+ -- ThreadSafe: This may happen on multiple processes/threads:
132+ let msgsize :: Word32 = fromIntegral$ foldl' (\n s -> n + BS.length s) 0 bsls
133+ when (msgsize > 4096)$ -- TODO, look up PIPE_BUF in foreign code
134+ error "Message larger than blocksize written atomically to a named pipe. Unimplemented."
135+ -- Otherwise it's just a simple write:
136+ -- We append the length as a header. TODO - REMOVE EXTRA COPY HERE:
137+
138+ let hdrbss :: BSS.ByteString
139+#ifdef CEREAL
140+ hdrbss = encode msgsize
141+#else
142+-- hdrbss = BS.fromChunks[encode msgsize]
143+ [hdrbss] = BS.toChunks (encode msgsize)
144+#endif
145+
146+-- let finalmsg = BS.concat (encode msgsize : bss)
147+ let finalmsg = BSS.concat (hdrbss : concatMap BS.toChunks bsls)
148+
149+ fd <- readMVar mv -- Synchronize with file opening.
150+ ----------------------------------------
151+ cnt <- PIO.fdWrite fd (fromBS finalmsg) -- inefficient to use String here!
152+ unless (fromIntegral cnt == BSS.length finalmsg) $
153+ error$ "Failed to write message in one go, length: "++ show (BSS.length finalmsg)
154+ ----------------------------------------
155+ return ()
156+ , closeSourceEnd = do
157+ fd <- readMVar mv
158+ PIO.closeFd fd
159+ }
160+
161+ mkTargetEnd :: MVar Fd -> MVar () -> TargetEnd
162+ mkTargetEnd mv lock = TargetEnd
163+ { receive = do
164+ fd <- readMVar mv -- Make sure Fd is there before
165+
166+ -- This should only happen on a single process. But it may
167+ -- happen on multiple threads so we grab a lock.
168+ takeMVar lock
169+
170+ let spinread :: Int -> IO BSS.ByteString
171+ spinread desired = do
172+
173+ bs <- tryUntilNoIOErr$
174+ readit fd (fromIntegral desired)
175+
176+ case BSS.length bs of
177+ n | n == desired -> return bs
178+ 0 -> do threadDelay (10*1000)
179+ spinread desired
180+ l -> error$ "Inclomplete read expected either 0 bytes or complete msg ("++
181+ show desired ++" bytes) got "++ show l ++ " bytes"
182+
183+ hdr <- spinread sizeof_header
184+ let -- decoded :: BSS.ByteString
185+#ifdef CEREAL
186+-- decoded = decode (BSS.concat$ BS.toChunks hdr)
187+ decoded = decode hdr
188+#else
189+-- decoded = decode hdr
190+ decoded = decode (BS.fromChunks [hdr])
191+#endif
192+ payload <- case decoded of
193+ Left err -> error$ "ERROR: "++ err
194+ Right size -> spinread (fromIntegral (size::Word32))
195+ putMVar lock ()
196+ return [BS.fromChunks [payload]] -- How terribly listy.
197+ , closeTargetEnd = do
198+ fd <- readMVar mv
199+ PIO.closeFd fd
200+ }
201+
202+spinTillThere :: String -> IO ()
203+spinTillThere filename = mkBackoff >>= loop
204+ where
205+ loop bkoff = do b <- doesFileExist filename
206+ unless b $ do bkoff; loop bkoff
207+
208+mkBackoff :: IO (IO ())
209+mkBackoff =
210+ do tref <- newIORef 1
211+ return$ do t <- readIORef tref
212+ writeIORef tref (min maxwait (2 * t))
213+ threadDelay t
214+ where
215+ maxwait = 50 * 1000
216+
217+tryUntilNoIOErr :: IO a -> IO a
218+tryUntilNoIOErr action = mkBackoff >>= loop
219+ where
220+ loop bkoff =
221+ handle (\ (e :: IOException) ->
222+ do bkoff
223+-- BSS.hPutStr stderr$ BSS.pack$ " got IO err: " ++ show e
224+ -- case ioeGetHandle e of
225+ -- Nothing -> BSS.hPutStrLn stderr$ BSS.pack$ " no hndl io err."
226+ -- Just x -> BSS.hPutStrLn stderr$ BSS.pack$ " HNDL on io err!" ++ show x
227+ loop bkoff) $
228+ action
229+
230+-- Execute an action on its own OS thread. Return an MVar to synchronize on.
231+onOSThread :: IO a -> IO (MVar a)
232+onOSThread action = do
233+ mv <- newEmptyMVar
234+ forkOS (action >>= putMVar mv )
235+ return mv
network-transport/network-transport.cabal+2−1
@@ -16,7 +16,8 @@ Library
16 cereal >= 0.3,
16 cereal >= 0.3,
17 containers >= 0.4,
17 containers >= 0.4,
18 network >= 2.3,
18 network >= 2.3,
19− safe >= 0.3
19+ safe >= 0.3,
20+ unix >= 2.5.0.0
20 Exposed-modules: Network.Transport,
21 Exposed-modules: Network.Transport,
21 Network.Transport.MVar,
22 Network.Transport.MVar,
22 Network.Transport.TCP
23 Network.Transport.TCP
network-transport/src/Network/Transport/MVar.hs+1
@@ -48,6 +48,7 @@ mkTransport = do
48 either (error "dummyBackend.deserializeSourceEnd: cannot parse")
48 either (error "dummyBackend.deserializeSourceEnd: cannot parse")
49 (Just . mkSourceAddr channels)
49 (Just . mkSourceAddr channels)
50 (decode bs)
50 (decode bs)
51+ , closeTransport = return ()
51 }
52 }
52 where
53 where
53 mkSourceAddr :: Chans -> Int -> SourceAddr
54 mkSourceAddr :: Chans -> Int -> SourceAddr
network-transport/src/Network/Transport/TCP.hs+37−6
@@ -8,10 +8,11 @@ module Network.Transport.TCP
8import Network.Transport
8import Network.Transport
9
9
10import Control.Applicative
10import Control.Applicative
11−
12−import Control.Concurrent (forkIO, ThreadId, killThread)
11+import Control.Concurrent (forkIO, ThreadId, killThread, myThreadId)
13import Control.Concurrent.Chan
12import Control.Concurrent.Chan
14import Control.Concurrent.MVar
13import Control.Concurrent.MVar
14+import Control.Exception (SomeException, IOException, AsyncException(ThreadKilled),
15+ fromException, throwTo, throw, catch, handle)
15import Control.Monad (forever, forM_)
16import Control.Monad (forever, forM_)
16
17
17import qualified Data.ByteString.Internal as BSI
18import qualified Data.ByteString.Internal as BSI
@@ -37,6 +38,7 @@ import qualified Network.Socket as N
37import Text.Printf
38import Text.Printf
38
39
39import Safe
40import Safe
41+import System.IO (stderr, hPutStrLn)
40
42
41#ifndef LAZY
43#ifndef LAZY
42import Data.ByteString.Char8 (ByteString)
44import Data.ByteString.Char8 (ByteString)
@@ -87,7 +89,8 @@ mkTransport (TCPConfig _hints host service) = withSocketsDo $ do
87 setSocketOption sock ReuseAddr 1
89 setSocketOption sock ReuseAddr 1
88 bindSocket sock (addrAddress serverAddr)
90 bindSocket sock (addrAddress serverAddr)
89 listen sock 5
91 listen sock 5
92+ threadId <- forkWithExceptions forkIO "Connection Listener" $
90− threadId <- forkIO $ procConnections chans sock
93+ procConnections chans sock
91
94
92 return Transport
95 return Transport
93 { newConnectionWith = {-# SCC "newConnectionWith" #-}\_ -> do
96 { newConnectionWith = {-# SCC "newConnectionWith" #-}\_ -> do
@@ -173,7 +176,8 @@ procConnections chans sock = forever $ do
173 putMVar chans (chanId', chanMap)
176 putMVar chans (chanId', chanMap)
174 err "cannot find chanId"
177 err "cannot find chanId"
175 Just (chan, socks) -> do
178 Just (chan, socks) -> do
179+ threadId <- forkWithExceptions forkIO "Message Listener" $
176− threadId <- forkIO $ procMessages chans chanId chan clientSock
180+ procMessages chans chanId chan clientSock
177 let chanMap' = IntMap.insert chanId (chan, (threadId, clientSock):socks) chanMap
181 let chanMap' = IntMap.insert chanId (chan, (threadId, clientSock):socks) chanMap
178 putMVar chans (chanId', chanMap')
182 putMVar chans (chanId', chanMap')
179
183
@@ -196,7 +200,8 @@ procMessages chans chanId chan sock = do
196 case esizeBS' of
200 case esizeBS' of
197 Left _ -> closeSocket
201 Left _ -> closeSocket
198 Right sizeBS' -> procMessage $ decode sizeBS'
202 Right sizeBS' -> procMessage $ decode sizeBS'
199− esize -> procMessage (fromIntegral <$> esize)
203+ esize@(Right _) -> procMessage (fromIntegral <$> esize)
204+ Left msg -> error msg
200 where
205 where
201 err m = error $ printf "procMessages: %s" m
206 err m = error $ printf "procMessages: %s" m
202 closeSocket :: IO ()
207 closeSocket :: IO ()
@@ -230,7 +235,7 @@ procMessages chans chanId chan sock = do
230-- input depending on the socket type.
235-- input depending on the socket type.
231#ifndef LAZY
236#ifndef LAZY
232recvExact :: Socket -> Int32 -> IO (Either ByteString ByteString)
237recvExact :: Socket -> Int32 -> IO (Either ByteString ByteString)
233−recvExact sock l = do
238+recvExact sock l = interceptAllExn "recvExact" $ do
234 res <- BSI.createAndTrim (fromIntegral l) (go 0)
239 res <- BSI.createAndTrim (fromIntegral l) (go 0)
235 case BS.length res of
240 case BS.length res of
236 n | n == (fromIntegral l) -> return $ Right res
241 n | n == (fromIntegral l) -> return $ Right res
@@ -248,6 +253,7 @@ recvExact sock l = do
248#else
253#else
249recvExact :: Socket -> Int32 -> IO (Either ByteString ByteString)
254recvExact :: Socket -> Int32 -> IO (Either ByteString ByteString)
250recvExact sock n =
255recvExact sock n =
256+ interceptAllExn "recvExact" $
251 go [] sock n
257 go [] sock n
252 where
258 where
253 go :: [ByteString] -> Socket -> Int32 -> IO (Either ByteString ByteString)
259 go :: [ByteString] -> Socket -> Int32 -> IO (Either ByteString ByteString)
@@ -258,3 +264,28 @@ recvExact sock n =
258 then return (Left $ BS.concat (reverse bss))
264 then return (Left $ BS.concat (reverse bss))
259 else go (bs:bss) sock (n - (fromIntegral $ BS.length bs))
265 else go (bs:bss) sock (n - (fromIntegral $ BS.length bs))
260#endif
266#endif
267+
268+interceptAllExn msg =
269+ Control.Exception.handle $ \ e ->
270+ case fromException e of
271+ Just ThreadKilled -> throw e
272+ Nothing -> do
273+ BS.hPutStrLn stderr $ BS.pack$ "Exception inside "++msg++": "++show e
274+ throw e
275+-- throw (e :: SomeException)
276+
277+forkWithExceptions :: (IO () -> IO ThreadId) -> String -> IO () -> IO ThreadId
278+forkWithExceptions forkit descr action = do
279+ parent <- myThreadId
280+ forkit $
281+ Control.Exception.catch action
282+ (\ e -> do
283+ case fromException e of
284+ Just ThreadKilled ->
285+-- BSS.hPutStrLn stderr $ BSS.pack$ "Note: Child thread killed: "++descr
286+ return ()
287+ _ -> do
288+ BS.hPutStrLn stderr $ BS.pack$ "Exception inside child thread "++descr++": "++show e
289+ throwTo parent (e::SomeException)
290+ )
291+