git.iko.soy/distributed-process
on master help sign in
This checkin passes a simple demo test, but it still depends on

opening and closing the fifo on every send. This is proving trickier to get right than I thought.

commit12559ab459181480ab1d9d8bdc07a0f4bc7ffa1c authorRyan Newton <rrnewton@gmail.com> dateSun 19 Feb 2012, 23:09 (15 years ago) parent8c57693
2 files changed+206−31
examples/DemoTransport2.hs+142 network-transport/src/Network/Transport/Pipes.hs+64−31
examples/DemoTransport2.hsnew file+142
@@ -0,0 +1,142 @@
1+-- module DemoTransport where
2+module Main where
3+
4+import Network.Transport
5+import qualified Network.Transport.MVar
6+import Network.Transport.TCP (mkTransport, TCPConfig (..))
7+
8+import qualified Network.Transport.Pipes as Pipes
9+
10+import Control.Concurrent
11+import Control.Monad
12+import Control.Exception
13+import System.IO (stderr, hPutStrLn)
14+
15+import qualified Data.ByteString.Char8 as BS
16+
17+import Debug.Trace
18+
19+forkWithExceptions :: (IO () -> IO ThreadId) -> String -> IO () -> IO ThreadId
20+forkWithExceptions forkit descr action = do
21+ parent <- myThreadId
22+ forkit $
23+ Control.Exception.catch
24+ action
25+ (\ e -> do
26+ hPutStrLn stderr $ "Exception inside "++descr++": "++show e
27+ throwTo parent (e::SomeException)
28+ )
29+
30+-------------------------------------------
31+-- Example program using backend directly
32+--
33+
34+-- | Check if multiple messages can be sent on the same connection.
35+-- demo0 :: IO ()
36+demo0 mkTrans = do
37+ trans <- mkTrans
38+
39+ (sourceAddr, targetEnd) <- newConnection trans
40+
41+-- forkWithExceptions forkIO "receiver thread" $ logServer "logServer" targetEnd
42+-- forkWithExceptions forkOS "receiver thread" $ logServer "logServer" targetEnd
43+ forkIO $ logServer "logServer" targetEnd
44+ threadDelay 100000
45+
46+ sourceEnd <- connect sourceAddr
47+
48+ mapM_ (\n -> send sourceEnd [BS.pack ("hello " ++ show n)]) [1 .. 10]
49+ threadDelay 100000
50+
51+
52+main = do
53+ putStrLn "MVAR:"
54+ demo0 Network.Transport.MVar.mkTransport
55+ putStrLn "TCP:"
56+ demo0$ mkTransport $ TCPConfig undefined "127.0.0.1" "8080"
57+ putStrLn "Pipes:"
58+ demo0 Pipes.mkTransport
59+
60+
61+
62+
63+{-
64+-- | Check endpoint serialization and deserialization.
65+demo1 :: IO ()
66+demo1 = do
67+ -- trans <- mkTransport
68+ trans <- mkTransport $ TCPConfig undefined "127.0.0.1" "8080"
69+
70+ (sourceAddr, targetEnd) <- newConnection trans
71+
72+ forkIO $ logServer "logServer" targetEnd
73+ threadDelay 100000
74+
75+ sourceEnd <- connect sourceAddr
76+
77+ -- This use of Just is slightly naughty
78+ let Just sourceAddr' = deserialize trans (serialize sourceAddr)
79+ sourceEnd' <- connect sourceAddr'
80+
81+ send sourceEnd [BS.pack "hello 1"]
82+ send sourceEnd' [BS.pack "hello 2"]
83+ threadDelay 100000
84+
85+-- | Check that messages can be sent before receive is set up.
86+demo2 :: IO ()
87+demo2 = do
88+ -- trans <- mkTransport
89+ trans <- mkTransport $ TCPConfig undefined "127.0.0.1" "8080"
90+
91+ (sourceAddr, targetEnd) <- newConnection trans
92+
93+ sourceEnd <- connect sourceAddr
94+ forkIO $ send sourceEnd [BS.pack "hello 1"]
95+ threadDelay 100000
96+
97+ forkIO $ logServer "logServer" targetEnd
98+ return ()
99+
100+-- | Check that two different transports can be created.
101+demo3 :: IO ()
102+demo3 = do
103+ trans1 <- mkTransport $ TCPConfig undefined "127.0.0.1" "8080"
104+ trans2 <- mkTransport $ TCPConfig undefined "127.0.0.1" "8081"
105+
106+ (sourceAddr1, targetEnd1) <- newConnection trans1
107+ (sourceAddr2, targetEnd2) <- newConnection trans2
108+
109+ forkIO $ logServer "logServer1" targetEnd1
110+ forkIO $ logServer "logServer2" targetEnd2
111+
112+ sourceEnd1 <- connect sourceAddr1
113+ sourceEnd2 <- connect sourceAddr2
114+
115+ send sourceEnd1 [BS.pack "hello1"]
116+ send sourceEnd2 [BS.pack "hello2"]
117+
118+-- | Check that two different connections on the same transport can be created.
119+demo4 :: IO ()
120+demo4 = do
121+ trans <- mkTransport $ TCPConfig undefined "127.0.0.1" "8080"
122+ -- trans <- Network.Transport.MVar.mkTransport
123+
124+ (sourceAddr1, targetEnd1) <- newConnection trans
125+ (sourceAddr2, targetEnd2) <- newConnection trans
126+
127+ forkIO $ logServer "logServer1" targetEnd1
128+ forkIO $ logServer "logServer2" targetEnd2
129+
130+ sourceEnd1 <- connect sourceAddr1
131+ sourceEnd2 <- connect sourceAddr2
132+
133+ send sourceEnd1 [BS.pack "hello1"]
134+ send sourceEnd2 [BS.pack "hello2"]
135+
136+-}
137+
138+logServer :: String -> TargetEnd -> IO ()
139+logServer name targetEnd = forever $ do
140+ x <- receive targetEnd
141+ trace (name ++ " rcvd: " ++ show x) $ return ()
142+
network-transport/src/Network/Transport/Pipes.hs+64−31
@@ -1,4 +1,4 @@
1−{-# LANGUAGE BangPatterns, CPP #-}
1+{-# LANGUAGE BangPatterns, CPP, ScopedTypeVariables #-}
22
33module Network.Transport.Pipes
44 ( mkTransport
@@ -6,7 +6,7 @@ module Network.Transport.Pipes
66
77import Control.Monad (when)
88import Control.Concurrent.MVar
9−import Control.Concurrent (threadDelay)
9+import Control.Concurrent (threadDelay, forkOS)
1010import Data.IntMap (IntMap)
1111-- import Data.ByteString.Char8 (ByteString)
1212import Data.Word
@@ -19,12 +19,14 @@ import Data.Serialize (encode,decode)
1919import Data.List (foldl')
2020import Network.Transport
2121import System.Random (randomIO)
22−import System.IO (IOMode(ReadMode,AppendMode), openFile, hClose)
22+import System.IO (IOMode(ReadMode,AppendMode,WriteMode,ReadWriteMode),
23+ openFile, hClose, hPutStrLn, hPutStr, stderr, stdout)
2324import System.Posix.Files (createNamedPipe, unionFileModes, ownerReadMode, ownerWriteMode)
2425
2526-- Option 1: low level unix routines:
2627-- Option 2: ByteString-provided IO routines
27−#if UNIXIO
28+#define UNIXIO
29+#ifdef UNIXIO
2830-- Added in unix-2.5.1.0. Let's not depend on this yet:
2931-- import qualified System.Posix.IO.ByteString as PIO
3032import qualified System.Posix.IO as PIO
@@ -47,16 +49,14 @@ mkTransport = do
4749 lock <- newMVar ()
4850 let filename = "/tmp/pipe_"++show uid
4951 createNamedPipe filename $ unionFileModes ownerReadMode ownerWriteMode
50−#if UNIXIO
51− pipe <- PIO.openFd filename PIO.ReadOnly Nothing PIO.defaultFileFlags
52−#else
53− pipe <- openFile filename ReadMode
54−#endif
52+
53+ dbgprint1$ " Created pipe at location: "++ filename
5554
5655 return Transport
5756 { newConnectionWith = \ _ -> do
57+ dbgprint1$ " Creating new connection"
5858 return (mkSourceAddr filename,
59− mkTargetEnd pipe lock)
59+ mkTargetEnd filename lock)
6060 , newMulticastWith = error "Pipes.hs: newMulticastWith not implemented yet"
6161 , deserialize = \bs -> return$ mkSourceAddr (BS.unpack bs)
6262 }
@@ -70,52 +70,85 @@ mkTransport = do
7070 mkSourceEnd :: String -> IO SourceEnd
7171 mkSourceEnd filename = do
7272-- fd <- openFile filename AppendMode
73+-- mv <- onOSThread$ openFile filename WriteMode
7374 return $
7475 -- Write to the named pipe. If the message is less than
7576 -- PIPE_BUF (4KB on linux) then this should be atomic, otherwise
7677 -- we have to do something more sophisticated.
7778 SourceEnd
7879 { send = \bss -> do
79− putStrLn$ "SENDING ... "++ show bss
80+ dbgprint1$ "SENDING ... "++ show bss
8081
8182 -- This may happen on multiple processes/threads:
82− let msgsize = foldl' (\n s -> n + BS.length s) 0 bss
83+ let msgsize :: Word32 = fromIntegral$ foldl' (\n s -> n + BS.length s) 0 bss
8384 when (msgsize > 4096)$ -- TODO, look up PIPE_BUF in foreign code
8485 error "Message larger than blocksize written atomically to a named pipe. Unimplemented."
8586 -- Otherwise it's just a simple write:
8687 -- We append the length as a header. TODO - REMOVE EXTRA COPY HERE:
87− fd <- openFile filename AppendMode
88− BS.hPut fd (BS.concat (encode msgsize : bss))
88+
89+-- fd <- readMVar mv
90+ dbgprint1$ " (sending... got file descriptor) "
91+-- fd <- openFile filename AppendMode
92+ fd <- openFile filename WriteMode
93+
94+-- TODO: Consider nonblocking opening of file to make things simpler.
95+
96+ let finalmsg = BS.concat (encode msgsize : bss)
97+ dbgprint1$ " Final send msg: " ++ show finalmsg
98+ BS.hPut fd finalmsg
8999 hClose fd -- TEMP -- opening and closing on each send!
90100 }
91101
92102-- mkTargetEnd :: Fd -> MVar () -> TargetEnd
93− mkTargetEnd fd lock = TargetEnd
103+ mkTargetEnd filename lock = TargetEnd
94104 { receive = do
105+ dbgprint2$ "Begin receive action..."
95106 -- This should only happen on a single process. But it may
96107 -- happen on multiple threads so we grab a lock.
97108 takeMVar lock
98109#ifdef UNIXIO
99− (bytes,cnt) <- PIO.fdRead fd sizeof_header
110+-- fd <- PIO.openFd filename PIO.ReadOnly Nothing PIO.defaultFileFlags
111+ fd <- PIO.openFd filename PIO.ReadWrite Nothing PIO.defaultFileFlags
112+ let oneread n = do (s,cnt) <- PIO.fdRead fd (fromIntegral n)
113+ return (BS.pack s, fromIntegral cnt)
100114#else
101− putStrLn$ " Attempt read header..."
115+ fd <- openFile filename ReadMode
116+ let oneread n = do bs <- BS.hGet fd n;
117+ return (bs, BS.length bs)
118+#endif
119+ dbgprint2$ " Attempt read header..."
102120
103− let rdloop = do
104− putStr "."
105− hdr <- BS.hGet fd sizeof_header
106− case BS.length hdr of
107− n | n == sizeof_header -> return hdr
121+ let spinread :: Int -> IO BS.ByteString
122+ spinread desired = do
123+-- hPutStr stderr "."
124+ (bytes,len) <- oneread desired
125+ case len of
126+ n | n == desired -> return bytes
108127 0 -> do threadDelay (10*1000)
109− rdloop
110− l -> error$ "Inclomplete read of msg header, only "++ show l ++ " bytes"
111− hdr <- rdloop
112− putStrLn$ " Got header "++ show hdr ++ " attempt read payload"
113− payload <- case decode hdr of
114− Left err -> error$ "ERROR: "++ err
115− Right size -> BS.hGet fd size
116− putStrLn$ " Got payload "++ show payload
117−#endif
128+ spinread desired
129+ l -> error$ "Inclomplete read expected either 0 bytes or complete msg ("++
130+ show desired ++" bytes) got "++ show l ++ " bytes"
131+
132+ hdr <- spinread sizeof_header
133+ dbgprint2$ " Got header "++ show hdr ++ " attempt read payload"
134+ payload <- case decode hdr of
135+ Left err -> error$ "ERROR: "++ err
136+ Right size -> spinread (fromIntegral (size::Word32))
137+ dbgprint2$ " Got payload "++ show payload
138+
118139 putMVar lock ()
119140 return [payload]
120141 }
121142
143+-- dbgprint2 = hPutStrLn stdout
144+-- dbgprint1 = hPutStrLn stderr
145+dbgprint1 _ = return ()
146+dbgprint2 _ = return ()
147+
148+
149+-- Execute an action on its own OS thread. Return an MVar to synchronize on.
150+onOSThread :: IO a -> IO (MVar a)
151+onOSThread action = do
152+ mv <- newEmptyMVar
153+ forkOS (action >>= putMVar mv )
154+ return mv