git.iko.soy/distributed-process
on master help sign in
Introduce unsafe variants of basic messaging primitives

The send/nsend/sendChan family of primitives force serialisation, even for local inter-process communication, to ensure full evaluation of the message takes place prior to delivery. These new primitives skip that step altogether, offering potentially better performance at the cost of a less obvious evaluation strategy.

commit061a8c468ca4132ed3ac44a08af20e0a56ea26f8 authorTim Watson <tim@rabbitmq.com> dateFri 26 Apr 2013, 09:32 (13 years ago) parent13377cf
7 files changed+253−25
distributed-process.cabal+6−4 src/Control/Distributed/Process.hs+7 src/Control/Distributed/Process/Internal/Messaging.hs+31−2 src/Control/Distributed/Process/Internal/Primitives.hs+29−19 src/Control/Distributed/Process/Internal/Types.hs+6 src/Control/Distributed/Process/UnsafePrimitives.hs+114 tests/TestCH.hs+60
distributed-process.cabal+6−4
@@ -59,13 +59,15 @@ Library
5959 rank1dynamic >= 0.1 && < 0.2,
6060 syb >= 0.3 && < 0.4
6161 Exposed-modules: Control.Distributed.Process,
62− Control.Distributed.Process.Internal.Types,
63− Control.Distributed.Process.Serializable,
6462 Control.Distributed.Process.Closure,
6563 Control.Distributed.Process.Debug,
66− Control.Distributed.Process.Node
64+ Control.Distributed.Process.Internal.Types,
65+ Control.Distributed.Process.Node,
66+ Control.Distributed.Process.Serializable,
67+ Control.Distributed.Process.UnsafePrimitives
6768 Other-modules: Control.Distributed.Process.Internal.Primitives,
6869 Control.Distributed.Process.Internal.Spawn,
70+ Control.Distributed.Process.Internal.Primitives,
6971 Control.Distributed.Process.Internal.CQueue,
7072 Control.Distributed.Process.Internal.Closure.BuiltIn,
7173 Control.Distributed.Process.Internal.Messaging,
@@ -75,7 +77,7 @@ Library
7577 Control.Distributed.Process.Internal.StrictContainerAccessors,
7678 Control.Distributed.Process.Internal.Trace.Types,
7779 Control.Distributed.Process.Internal.Trace.Primitives,
78− Control.Distributed.Process.Internal.Trace.Tracer
80+ Control.Distributed.Process.Internal.Trace.Tracer,
7981 Control.Distributed.Process.Internal.Trace.Remote
8082 Extensions: RankNTypes,
8183 ScopedTypeVariables,
src/Control/Distributed/Process.hs+7
@@ -36,6 +36,10 @@ module Control.Distributed.Process
3636 , receiveChanTimeout
3737 , mergePortsBiased
3838 , mergePortsRR
39+ -- * Unsafe messaging variants
40+ , unsafeSend
41+ , unsafeSendChan
42+ , unsafeNSend
3943 -- * Advanced messaging
4044 , Match
4145 , receiveWait
@@ -187,6 +191,9 @@ import Control.Distributed.Process.Internal.Primitives
187191 , receiveChan
188192 , mergePortsBiased
189193 , mergePortsRR
194+ , unsafeSend
195+ , unsafeSendChan
196+ , unsafeNSend
190197 -- Advanced messaging
191198 , Match
192199 , receiveWait
src/Control/Distributed/Process/Internal/Messaging.hs+31−2
@@ -5,6 +5,7 @@ module Control.Distributed.Process.Internal.Messaging
55 , disconnect
66 , closeImplicitReconnections
77 , impliesDeathOf
8+ , sendCtrlMsg
89 ) where
910
1011import Data.Accessor ((^.), (^=))
@@ -13,8 +14,12 @@ import qualified Data.Map as Map (partitionWithKey, elems)
1314import qualified Data.ByteString.Lazy as BSL (toChunks)
1415import qualified Data.ByteString as BSS (ByteString)
1516import Control.Distributed.Process.Internal.StrictMVar (withMVar, modifyMVar_)
17+import Control.Distributed.Process.Serializable ()
18+
1619import Control.Concurrent.Chan (writeChan)
1720import Control.Monad (unless)
21+import Control.Monad.IO.Class (MonadIO, liftIO)
22+import Control.Monad.Reader (ask)
1823import qualified Network.Transport as NT
1924 ( Connection
2025 , send
@@ -36,8 +41,11 @@ import Control.Distributed.Process.Internal.Types
3641 , ProcessSignal(Died)
3742 , DiedReason(DiedDisconnect)
3843 , ImplicitReconnect(WithImplicitReconnect)
39− , NodeId
40− , ProcessId(processNodeId)
44+ , NodeId(..)
45+ , ProcessId(..)
46+ , LocalNode(..)
47+ , LocalProcess(..)
48+ , Process(..)
4149 , SendPortId(sendPortProcessId)
4250 , Identifier(NodeIdentifier, ProcessIdentifier, SendPortIdentifier)
4351 )
@@ -167,3 +175,24 @@ SendPortIdentifier cid `impliesDeathOf` SendPortIdentifier cid' =
167175 cid' == cid
168176_ `impliesDeathOf` _ =
169177 False
178+
179+
180+-- Send a control message
181+sendCtrlMsg :: Maybe NodeId -- ^ Nothing for the local node
182+ -> ProcessSignal -- ^ Message to send
183+ -> Process ()
184+sendCtrlMsg mNid signal = do
185+ proc <- ask
186+ let msg = NCMsg { ctrlMsgSender = ProcessIdentifier (processId proc)
187+ , ctrlMsgSignal = signal
188+ }
189+ case mNid of
190+ Nothing -> do
191+ liftIO $ writeChan (localCtrlChan (processNode proc)) msg
192+ Just nid ->
193+ liftIO $ sendBinary (processNode proc)
194+ (ProcessIdentifier (processId proc))
195+ (NodeIdentifier nid)
196+ WithImplicitReconnect
197+ msg
198+
src/Control/Distributed/Process/Internal/Primitives.hs+29−19
@@ -14,6 +14,10 @@ module Control.Distributed.Process.Internal.Primitives
1414 , receiveChan
1515 , mergePortsBiased
1616 , mergePortsRR
17+ -- * Unsafe messaging variants
18+ , unsafeSend
19+ , unsafeSendChan
20+ , unsafeNSend
1721 -- * Advanced messaging
1822 , Match
1923 , receiveWait
@@ -93,6 +97,8 @@ module Control.Distributed.Process.Internal.Primitives
9397 -- * Reconnecting
9498 , reconnect
9599 , reconnectPort
100+ -- * Internal Exports
101+ , sendCtrlMsg
96102 ) where
97103
98104#if ! MIN_VERSION_base(4,6,0)
@@ -138,6 +144,7 @@ import Control.Distributed.Static
138144 )
139145import Data.Rank1Typeable (Typeable)
140146import qualified Control.Distributed.Static as Static (unstatic, unclosure)
147+import qualified Control.Distributed.Process.UnsafePrimitives as Unsafe
141148import Control.Distributed.Process.Internal.Types
142149 ( NodeId(..)
143150 , ProcessId(..)
@@ -181,6 +188,7 @@ import Control.Distributed.Process.Internal.Messaging
181188 , sendBinary
182189 , sendPayload
183190 , disconnect
191+ , sendCtrlMsg
184192 )
185193import Control.Distributed.Process.Internal.Trace.Types
186194 ( traceEvent
@@ -220,6 +228,13 @@ send them msg = do
220228 liftIO $ traceEvent (localTracer node)
221229 (TraceEvSent them us (createUnencodedMessage msg))
222230
231+-- | /Unsafe/ variant of 'send'. This function makes /no/ attempt to serialize
232+-- and (in the case when the destination process resides on the same local
233+-- node) therefore ensure that the payload is fully evaluated before it is
234+-- delivered.
235+unsafeSend :: Serializable a => ProcessId -> a -> Process ()
236+unsafeSend = Unsafe.send
237+
223238-- | Wait for a message of a specific type
224239expect :: forall a. Serializable a => Process a
225240expect = receiveWait [match return]
@@ -267,6 +282,13 @@ sendChan (SendPort cid) msg = do
267282 NoImplicitReconnect
268283 msg
269284
285+-- | Send a message on a typed channel. This function makes /no/ attempt to
286+-- serialize and (in the case when the @ReceivePort@ resides on the same local
287+-- node) therefore ensure that the payload is fully evaluated before it is
288+-- delivered.
289+unsafeSendChan :: Serializable a => SendPort a -> a -> Process ()
290+unsafeSendChan = Unsafe.sendChan
291+
270292-- | Wait for a message on a typed channel
271293receiveChan :: Serializable a => ReceivePort a -> Process a
272294receiveChan = liftIO . atomically . receiveSTM
@@ -986,6 +1008,13 @@ nsend :: Serializable a => String -> a -> Process ()
9861008nsend label msg =
9871009 sendCtrlMsg Nothing (NamedSend label (createUnencodedMessage msg))
9881010
1011+-- | Named send to a process in the local registry (asynchronous).
1012+-- This function makes /no/ attempt to serialize and (in the case when the
1013+-- destination process resides on the same local node) therefore ensure that
1014+-- the payload is fully evaluated before it is delivered.
1015+unsafeNSend :: Serializable a => String -> a -> Process ()
1016+unsafeNSend = Unsafe.nsend
1017+
9891018-- | Named send to a process in a remote registry (asynchronous)
9901019nsendRemote :: Serializable a => NodeId -> String -> a -> Process ()
9911020nsendRemote nid label msg =
@@ -1091,22 +1120,3 @@ monitor' ident = do
10911120-- | Link to a process/node/channel
10921121link' :: Identifier -> Process ()
10931122link' = sendCtrlMsg Nothing . Link
1094−
1095−-- Send a control message
1096−sendCtrlMsg :: Maybe NodeId -- ^ Nothing for the local node
1097− -> ProcessSignal -- ^ Message to send
1098− -> Process ()
1099−sendCtrlMsg mNid signal = do
1100− proc <- ask
1101− let msg = NCMsg { ctrlMsgSender = ProcessIdentifier (processId proc)
1102− , ctrlMsgSignal = signal
1103− }
1104− case mNid of
1105− Nothing -> do
1106− liftIO $ writeChan (localCtrlChan (processNode proc)) msg
1107− Just nid ->
1108− liftIO $ sendBinary (processNode proc)
1109− (ProcessIdentifier (processId proc))
1110− (NodeIdentifier nid)
1111− WithImplicitReconnect
1112− msg
src/Control/Distributed/Process/Internal/Types.hs+6
@@ -31,6 +31,7 @@ module Control.Distributed.Process.Internal.Types
3131 , Message(..)
3232 , createMessage
3333 , createUnencodedMessage
34+ , unsafeCreateUnencodedMessage
3435 , messageToPayload
3536 , payloadToMessage
3637 -- * Node controller user-visible data types
@@ -339,6 +340,11 @@ createUnencodedMessage :: Serializable a => a -> Message
339340createUnencodedMessage a =
340341 let encoded = encode a in BSL.length encoded `seq` UnencodedMessage (fingerprint a) a
341342
343+-- | Turn any serializable term into an unencodede/local message, without
344+-- evalutaing it!
345+unsafeCreateUnencodedMessage :: Serializable a => a -> Message
346+unsafeCreateUnencodedMessage a = UnencodedMessage (fingerprint a) a
347+
342348-- | Serialize a message
343349messageToPayload :: Message -> [BSS.ByteString]
344350messageToPayload (EncodedMessage fp enc) = encodeFingerprint fp : BSL.toChunks enc
src/Control/Distributed/Process/UnsafePrimitives.hsnew file+114
@@ -0,0 +1,114 @@
1+-----------------------------------------------------------------------------
2+-- |
3+-- Module : Control.Distributed.Process.UnsafePrimitives
4+-- Copyright : (c) Well-Typed / Tim Watson
5+-- License : BSD3 (see the file LICENSE)
6+--
7+-- Maintainer : Tim Watson <watson.timothy@gmail.com>
8+-- Stability : experimental
9+-- Portability : non-portable (requires concurrency)
10+--
11+-- [Unsafe Variants of Cloud Haskell's Messaging Primitives]
12+--
13+-- Cloud Haskell's semantics do not mention evaluation/strictness properties
14+-- at all; Only the promise (or lack) of signal/message delivery between
15+-- processes is considered. For practical purposes, Cloud Haskell optimises
16+-- communication between (intra-node) local processes by skipping the
17+-- network-transport layer. In order to ensure the same strictness semantics
18+-- however, messages /still/ undergo binary serialisation before (internal)
19+-- transmission takes place. Thus we ensure that in both (the local and remote)
20+-- cases, message payloads are fully evaluated. Whilst this provides the user
21+-- with /unsurprising/ behaviour, the resulting performance overhead is quite
22+-- severe. Communicating data between Haskell threads /without/ forcing binary
23+-- serialisation reduces (intra-node, inter-process) communication overheads
24+-- by several orders of magnitude.
25+--
26+-- This module provides variants of Cloud Haskell's messaging primitives
27+-- ('send', 'sendChan' and 'nsend') which do /not/ force binary serialisation in
28+-- the local case, thereby offering superior intra-node messaging performance.
29+-- The /catch/ is that any evaluation problems lurking within the passed data
30+-- structure - e.g., fields set to @undefined@ and so on - will show up in the
31+-- receiver rather than in the caller (as they would with the /normal/ strategy).
32+--
33+-- Use of the functions in this module can potentially change the runtime
34+-- behaviour of your application. You have been warned!
35+--
36+-- This module is exported so that you can replace the use of Cloud Haskell's
37+-- /safe/ messaging primitives. If you want to use both variants, then you can
38+-- take advantage of qualified imports, however "Control.Distributed.Process"
39+-- also re-exports these functions under different names, using the @unsafe@
40+-- prefix.
41+--
42+module Control.Distributed.Process.UnsafePrimitives
43+ ( -- * Unsafe Basic Messaging
44+ send
45+ , sendChan
46+ , nsend
47+ ) where
48+
49+import Control.Distributed.Process.Internal.Messaging
50+ ( sendMessage
51+ , sendBinary
52+ , sendCtrlMsg
53+ )
54+
55+import Control.Distributed.Process.Internal.Types
56+ ( ProcessId(..)
57+ , LocalNode(..)
58+ , LocalProcess(..)
59+ , Process(..)
60+ , SendPort(..)
61+ , ProcessSignal(..)
62+ , Identifier(..)
63+ , ImplicitReconnect(..)
64+ , SendPortId(..)
65+ , sendPortProcessId
66+ , unsafeCreateUnencodedMessage
67+ )
68+import Control.Distributed.Process.Serializable (Serializable)
69+
70+import Control.Monad.IO.Class (MonadIO, liftIO)
71+import Control.Monad.Reader (ask)
72+
73+-- | Named send to a process in the local registry (asynchronous)
74+nsend :: Serializable a => String -> a -> Process ()
75+nsend label msg =
76+ sendCtrlMsg Nothing (NamedSend label (unsafeCreateUnencodedMessage msg))
77+
78+-- | Send a message
79+send :: Serializable a => ProcessId -> a -> Process ()
80+send them msg = do
81+ proc <- ask
82+ let node = localNodeId (processNode proc)
83+ destNode = (processNodeId them) in do
84+ case destNode == node of
85+ True -> unsafeSendLocal them msg
86+ False -> liftIO $ sendMessage (processNode proc)
87+ (ProcessIdentifier (processId proc))
88+ (ProcessIdentifier them)
89+ NoImplicitReconnect
90+ msg
91+
92+-- | Send a message on a typed channel
93+sendChan :: Serializable a => SendPort a -> a -> Process ()
94+sendChan (SendPort cid) msg = do
95+ proc <- ask
96+ let node = localNodeId (processNode proc)
97+ destNode = processNodeId (sendPortProcessId cid) in do
98+ case destNode == node of
99+ True -> unsafeSendChanLocal cid msg
100+ False -> do
101+ liftIO $ sendBinary (processNode proc)
102+ (ProcessIdentifier (processId proc))
103+ (SendPortIdentifier cid)
104+ NoImplicitReconnect
105+ msg
106+
107+unsafeSendLocal :: (Serializable a) => ProcessId -> a -> Process ()
108+unsafeSendLocal pid msg =
109+ sendCtrlMsg Nothing $ LocalSend pid (unsafeCreateUnencodedMessage msg)
110+
111+unsafeSendChanLocal :: (Serializable a) => SendPortId -> a -> Process ()
112+unsafeSendChanLocal spId msg =
113+ sendCtrlMsg Nothing $ LocalPortSend spId (unsafeCreateUnencodedMessage msg)
114+
tests/TestCH.hs+60
@@ -1167,6 +1167,63 @@ testExitRemote transport = do
11671167 takeMVar supervisedDone
11681168 takeMVar supervisorDone
11691169
1170+testUnsafeSend :: NT.Transport -> Assertion
1171+testUnsafeSend transport = do
1172+ serverAddr <- newEmptyMVar
1173+ clientDone <- newEmptyMVar
1174+
1175+ localNode <- newLocalNode transport initRemoteTable
1176+ void $ forkProcess localNode $ do
1177+ self <- getSelfPid
1178+ liftIO $ putMVar serverAddr self
1179+ clientAddr <- expect
1180+ unsafeSend clientAddr ()
1181+
1182+ void $ forkProcess localNode $ do
1183+ serverPid <- liftIO $ takeMVar serverAddr
1184+ getSelfPid >>= unsafeSend serverPid
1185+ () <- expect
1186+ liftIO $ putMVar clientDone ()
1187+
1188+ takeMVar clientDone
1189+
1190+testUnsafeNSend :: NT.Transport -> Assertion
1191+testUnsafeNSend transport = do
1192+ clientDone <- newEmptyMVar
1193+
1194+ localNode <- newLocalNode transport initRemoteTable
1195+
1196+ pid <- forkProcess localNode $ do
1197+ () <- expect
1198+ liftIO $ putMVar clientDone ()
1199+
1200+ void $ runProcess localNode $ do
1201+ register "foobar" pid
1202+ unsafeNSend "foobar" ()
1203+
1204+ takeMVar clientDone
1205+
1206+testUnsafeSendChan :: NT.Transport -> Assertion
1207+testUnsafeSendChan transport = do
1208+ serverAddr <- newEmptyMVar
1209+ clientDone <- newEmptyMVar
1210+
1211+ localNode <- newLocalNode transport initRemoteTable
1212+ void $ forkProcess localNode $ do
1213+ self <- getSelfPid
1214+ liftIO $ putMVar serverAddr self
1215+ sp <- expect
1216+ unsafeSendChan sp ()
1217+
1218+ void $ forkProcess localNode $ do
1219+ serverPid <- liftIO $ takeMVar serverAddr
1220+ (sp, rp) <- newChan
1221+ unsafeSend serverPid sp
1222+ () <- receiveChan rp
1223+ liftIO $ putMVar clientDone ()
1224+
1225+ takeMVar clientDone
1226+
11701227tests :: (NT.Transport, TransportInternals) -> [Test]
11711228tests (transport, transportInternals) = [
11721229 testGroup "Basic features" [
@@ -1227,6 +1284,9 @@ tests (transport, transportInternals) = [
12271284 , testCase "MonitorChannel" (testMonitorChannel transport)
12281285 -- Reconnect
12291286 , testCase "Reconnect" (testReconnect transport transportInternals)
1287+ , testCase "TestUnsafeSend" (testUnsafeSend transport)
1288+ , testCase "TestUnsafeNSend" (testUnsafeNSend transport)
1289+ , testCase "TestUnsafeSendChan" (testUnsafeSendChan transport)
12301290 ]
12311291 ]
12321292