git.iko.soy/distributed-process
on master help sign in
Introduce low level encoding checks; Extend dependencies for DPP

Introduce Hashable and DeepSeq instances for common data types, for use in DPP. Provide low level encoding checking API.

commitcafac53a84fb7b0cc74574ef73d11aaf645606ed authorTim Watson <watson.timothy@gmail.com> dateThu 20 Jun 2013, 19:01 (13 years ago) parent904e598
6 files changed+119−42
distributed-process.cabal+2 src/Control/Distributed/Process.hs+4 src/Control/Distributed/Process/Internal/Primitives.hs+31−17 src/Control/Distributed/Process/Internal/Spawn.hs+1−2 src/Control/Distributed/Process/Internal/Types.hs+50−16 tests/TestCH.hs+31−7
distributed-process.cabal+2
@@ -44,6 +44,8 @@ flag prof
4444Library
4545 Build-Depends: base >= 4.4 && < 5,
4646 binary >= 0.5 && < 0.8,
47+ deepseq >= 1.3.0.1 && < 1.4,
48+ hashable >= 1.2.0.5 && < 1.3,
4749 network-transport >= 0.3 && < 0.4,
4850 stm >= 2.4 && < 2.5,
4951 transformers >= 0.2 && < 0.4,
src/Control/Distributed/Process.hs+4
@@ -53,9 +53,11 @@ module Control.Distributed.Process
5353 , Message
5454 , matchMessage
5555 , matchMessageIf
56+ , isEncoded
5657 , wrapMessage
5758 , unwrapMessage
5859 , handleMessage
60+ , handleMessageIf
5961 , forward
6062 , delegate
6163 , relay
@@ -206,9 +208,11 @@ import Control.Distributed.Process.Internal.Primitives
206208 , matchChan
207209 , matchMessage
208210 , matchMessageIf
211+ , isEncoded
209212 , wrapMessage
210213 , unwrapMessage
211214 , handleMessage
215+ , handleMessageIf
212216 , forward
213217 , delegate
214218 , relay
src/Control/Distributed/Process/Internal/Primitives.hs+31−17
@@ -30,9 +30,11 @@ module Control.Distributed.Process.Internal.Primitives
3030 , matchChan
3131 , matchMessage
3232 , matchMessageIf
33+ , isEncoded
3334 , wrapMessage
3435 , unwrapMessage
3536 , handleMessage
37+ , handleMessageIf
3638 , forward
3739 , delegate
3840 , relay
@@ -173,10 +175,11 @@ import Control.Distributed.Process.Internal.Types
173175 , ProcessRegistrationException(..)
174176 , ProcessInfo(..)
175177 , ProcessInfoNone(..)
178+ , isEncoded
176179 , createMessage
177180 , createUnencodedMessage
178181 , runLocalProcess
179− , ImplicitReconnect(NoImplicitReconnect)
182+ , ImplicitReconnect( NoImplicitReconnect)
180183 , LocalProcessState
181184 , LocalSendPortId
182185 , messageToPayload
@@ -444,14 +447,14 @@ wrapMessage = createUnencodedMessage -- see [note Serializable UnencodedMessage]
444447-- Whereas this expression, will yield @Just "foo"@
445448-- > unwrapMessage (wrapMessage "foo") :: Process (Maybe String)
446449--
447−unwrapMessage :: forall a. Serializable a => Message -> Process (Maybe a)
450+unwrapMessage :: forall m a. (Monad m, Serializable a) => Message -> m (Maybe a)
448451unwrapMessage msg =
449452 case messageFingerprint msg == fingerprint (undefined :: a) of
450− False -> return Nothing
453+ False -> return Nothing :: m (Maybe a)
451454 True -> case msg of
452− (UnencodedMessage _ m) ->
453− let m' = unsafeCoerce m :: a
454− in return (Just m')
455+ (UnencodedMessage _ ms) ->
456+ let ms' = unsafeCoerce ms :: a
457+ in return (Just ms')
455458 (EncodedMessage _ _) ->
456459 return (Just (decoded))
457460 where
@@ -467,17 +470,28 @@ unwrapMessage msg =
467470--
468471-- Intended for use in `catchesExit` and `matchAny` primitives.
469472--
470−handleMessage :: forall a b. (Serializable a)
471− => Message -> (a -> Process b) -> Process (Maybe b)
472−handleMessage msg proc = do
473+handleMessage :: forall m a b. (Monad m, Serializable a)
474+ => Message -> (a -> m b) -> m (Maybe b)
475+handleMessage msg proc = handleMessageIf msg (const True) proc
476+
477+handleMessageIf :: forall m a b . (Monad m, Serializable a)
478+ => Message
479+ -> (a -> Bool)
480+ -> (a -> m b)
481+ -> m (Maybe b)
482+handleMessageIf msg c proc = do
473483 case messageFingerprint msg == fingerprint (undefined :: a) of
474− False -> return Nothing
484+ False -> return Nothing :: m (Maybe b)
475485 True -> case msg of
476− (UnencodedMessage _ m) ->
477− let m' = unsafeCoerce m :: a -- note [decoding]
478− in do { r <- proc m'; return (Just r) }
486+ (UnencodedMessage _ ms) ->
487+ let ms' = unsafeCoerce ms :: a in
488+ case (c ms') of
489+ True -> do { r <- proc ms'; return (Just r) }
490+ False -> return Nothing :: m (Maybe b)
479491 (EncodedMessage _ _) ->
480− do { r <- proc (decoded :: a); return (Just r) }
492+ case (c decoded) of
493+ True -> do { r <- proc (decoded :: a); return (Just r) }
494+ False -> return Nothing :: m (Maybe b)
481495 where
482496 decoded :: a -- note [decoding]
483497 !decoded = decode (messageEncoding msg)
@@ -627,12 +641,12 @@ catchExit act exitHandler = catch act handleExit
627641-- | Lift 'Control.Exception.catches' (almost).
628642--
629643-- As 'ProcessExitException' stores the exit @reason@ as a typed, encoded
630−-- message, a handler must accept an input of the expected type. In order to
644+-- message, a handler must accept inputs of the expected type. In order to
631645-- handle a list of potentially different handlers (and therefore input types),
632−-- a handler passed to 'catchesExit' must accept 'AbstractMessage' and return
646+-- a handler passed to 'catchesExit' must accept 'Message' and return
633647-- @Maybe@ (i.e., @Just p@ if it handled the exit reason, otherwise @Nothing@).
634648--
635−-- See 'maybeHandleMessage' and 'AsbtractMessage' for more details.
649+-- See 'maybeHandleMessage' and 'Message' for more details.
636650catchesExit :: Process b
637651 -> [(ProcessId -> Message -> (Process (Maybe b)))]
638652 -> Process b
src/Control/Distributed/Process/Internal/Spawn.hs+1−2
@@ -7,7 +7,6 @@ module Control.Distributed.Process.Internal.Spawn
77 , spawnChannel
88 ) where
99
10−import Data.Typeable (Typeable)
1110import Control.Distributed.Static
1211 ( Static
1312 , Closure
@@ -144,7 +143,7 @@ spawnSupervised nid proc = do
144143
145144-- | Spawn a new process, supplying it with a new 'ReceivePort' and return
146145-- the corresponding 'SendPort'.
147−spawnChannel :: forall a. Typeable a => Static (SerializableDict a)
146+spawnChannel :: forall a. Serializable a => Static (SerializableDict a)
148147 -> NodeId
149148 -> Closure (ReceivePort a -> Process ())
150149 -> Process (SendPort a)
src/Control/Distributed/Process/Internal/Types.hs+50−16
@@ -1,3 +1,5 @@
1+{-# LANGUAGE DeriveGeneric #-}
2+
13-- | Types used throughout the Cloud Haskell framework
24--
35-- We collect all types used internally in a single module because
@@ -29,6 +31,7 @@ module Control.Distributed.Process.Internal.Types
2931 , ReceivePort(..)
3032 -- * Messages
3133 , Message(..)
34+ , isEncoded
3235 , createMessage
3336 , createUnencodedMessage
3437 , unsafeCreateUnencodedMessage
@@ -77,7 +80,7 @@ module Control.Distributed.Process.Internal.Types
7780import System.Mem.Weak (Weak)
7881import Data.Map (Map)
7982import Data.Int (Int32)
80−import Data.Typeable (Typeable)
83+import Data.Typeable (Typeable, typeOf)
8184import Data.Binary (Binary(put, get), putWord8, getWord8, encode)
8285import qualified Data.ByteString as BSS (ByteString, concat, copy)
8386import qualified Data.ByteString.Lazy as BSL
@@ -90,6 +93,7 @@ import qualified Data.ByteString.Lazy as BSL
9093import qualified Data.ByteString.Lazy.Internal as BSL (ByteString(..))
9194import Data.Accessor (Accessor, accessor)
9295import Control.Category ((>>>))
96+import Control.DeepSeq (NFData(..))
9397import Control.Exception (Exception)
9498import Control.Concurrent (ThreadId)
9599import Control.Concurrent.Chan (Chan)
@@ -113,14 +117,19 @@ import Control.Distributed.Process.Internal.WeakTQueue (TQueue)
113117import Control.Distributed.Static (RemoteTable, Closure)
114118import qualified Control.Distributed.Process.Internal.StrictContainerAccessors as DAC (mapMaybe)
115119
120+import Data.Hashable
121+import GHC.Generics
122+
116123--------------------------------------------------------------------------------
117124-- Node and process identifiers --
118125--------------------------------------------------------------------------------
119126
120127-- | Node identifier
121128newtype NodeId = NodeId { nodeAddress :: NT.EndPointAddress }
122− deriving (Eq, Ord, Binary, Typeable)
123−
129+ deriving (Eq, Ord, Typeable, Generic)
130+instance Binary NodeId where
131+instance NFData NodeId
132+instance Hashable NodeId where
124133instance Show NodeId where
125134 show (NodeId addr) = "nid://" ++ show addr
126135
@@ -130,7 +139,9 @@ data LocalProcessId = LocalProcessId
130139 { lpidUnique :: {-# UNPACK #-} !Int32
131140 , lpidCounter :: {-# UNPACK #-} !Int32
132141 }
133− deriving (Eq, Ord, Typeable, Show)
142+ deriving (Eq, Ord, Typeable, Generic, Show)
143+
144+instance Hashable LocalProcessId where
134145
135146-- | Process identifier
136147data ProcessId = ProcessId
@@ -139,7 +150,11 @@ data ProcessId = ProcessId
139150 -- | Node-local identifier for the process
140151 , processLocalId :: {-# UNPACK #-} !LocalProcessId
141152 }
142− deriving (Eq, Ord, Typeable)
153+ deriving (Eq, Ord, Typeable, Generic)
154+
155+instance Binary ProcessId where
156+instance NFData ProcessId where
157+instance Hashable ProcessId where
143158
144159instance Show ProcessId where
145160 show (ProcessId (NodeId addr) (LocalProcessId _ lid))
@@ -150,7 +165,9 @@ data Identifier =
150165 NodeIdentifier !NodeId
151166 | ProcessIdentifier !ProcessId
152167 | SendPortIdentifier !SendPortId
153− deriving (Eq, Ord)
168+ deriving (Eq, Ord, Generic)
169+
170+instance Hashable Identifier where
154171
155172instance Show Identifier where
156173 show (NodeIdentifier nid) = show nid
@@ -276,8 +293,9 @@ data SendPortId = SendPortId {
276293 -- | Process-local ID of the channel
277294 , sendPortLocalId :: {-# UNPACK #-} !LocalSendPortId
278295 }
279− deriving (Eq, Ord)
296+ deriving (Eq, Ord, Typeable, Generic)
280297
298+instance Hashable SendPortId where
281299instance Show SendPortId where
282300 show (SendPortId (ProcessId (NodeId addr) (LocalProcessId _ plid)) clid)
283301 = "cid://" ++ show addr ++ ":" ++ show plid ++ ":" ++ show clid
@@ -289,7 +307,11 @@ newtype SendPort a = SendPort {
289307 -- | The (unique) ID of this send port
290308 sendPortId :: SendPortId
291309 }
292− deriving (Typeable, Binary, Show, Eq, Ord)
310+ deriving (Typeable, Generic, Show, Eq, Ord)
311+
312+instance (Serializable a) => Binary (SendPort a) where
313+instance (Hashable a) => Hashable (SendPort a) where
314+instance (NFData a) => NFData (SendPort a) where
293315
294316-- | The receive end of a typed channel (not serializable)
295317--
@@ -329,7 +351,19 @@ data Message =
329351
330352instance Show Message where
331353 show (EncodedMessage fp enc) = show enc ++ " :: " ++ showFingerprint fp []
332− show (UnencodedMessage fp _) = "[unencoded message] :: " ++ (showFingerprint fp [])
354+ show (UnencodedMessage _ uenc) = "[unencoded message] :: " ++ (show $ typeOf uenc)
355+
356+-- | /internal use only/.
357+isEncoded :: Message -> Bool
358+isEncoded (EncodedMessage _ _) = True
359+isEncoded _ = False
360+-- [note] isEncoded:
361+-- This is just as internal as it looks and yes, it does feel a bit odd that
362+-- we're exporting it for use, however DPP does a /lot/ of work with low
363+-- level APIs such as unwrapMessage and handleMessage, and in the process
364+-- tries very hard to avoid copying (and re-serialisation) where possible.
365+-- Being able to determine that a message is encoded (or otherwise) makes
366+-- that a lot more manageable.
333367
334368-- | Turn any serialiable term into a message
335369createMessage :: Serializable a => a -> Message
@@ -377,7 +411,8 @@ data MonitorRef = MonitorRef
377411 -- | Unique to distinguish multiple monitor requests by the same process
378412 , monitorRefCounter :: !Int32
379413 }
380− deriving (Eq, Ord, Show)
414+ deriving (Eq, Ord, Show, Generic)
415+instance Hashable MonitorRef where
381416
382417-- | Message sent by process monitors
383418data ProcessMonitorNotification =
@@ -531,10 +566,6 @@ instance Binary LocalProcessId where
531566 put lpid = put (lpidUnique lpid) >> put (lpidCounter lpid)
532567 get = LocalProcessId <$> get <*> get
533568
534−instance Binary ProcessId where
535− put pid = put (processNodeId pid) >> put (processLocalId pid)
536− get = ProcessId <$> get <*> get
537−
538569instance Binary ProcessMonitorNotification where
539570 put (ProcessMonitorNotification ref pid reason) = put ref >> put pid >> put reason
540571 get = ProcessMonitorNotification <$> get <*> get <*> get
@@ -567,7 +598,7 @@ instance Binary ProcessSignal where
567598 put (NamedSend label msg) = putWord8 8 >> put label >> put (messageToPayload msg)
568599 put (Kill pid reason) = putWord8 9 >> put pid >> put reason
569600 put (Exit pid reason) = putWord8 10 >> put pid >> put (messageToPayload reason)
570− put (LocalSend to msg) = putWord8 11 >> put to >> put (messageToPayload msg)
601+ put (LocalSend to' msg) = putWord8 11 >> put to' >> put (messageToPayload msg)
571602 put (LocalPortSend sid msg) = putWord8 12 >> put sid >> put (messageToPayload msg)
572603 put (GetInfo about) = putWord8 30 >> put about
573604 put (SigShutdown) = putWord8 31
@@ -615,6 +646,9 @@ instance Binary SendPortId where
615646 put cid = put (sendPortProcessId cid) >> put (sendPortLocalId cid)
616647 get = SendPortId <$> get <*> get
617648
649+instance NFData SendPortId where
650+ rnf cid = (sendPortProcessId cid) `seq` (sendPortLocalId cid) `seq` ()
651+
618652instance Binary Identifier where
619653 put (ProcessIdentifier pid) = putWord8 0 >> put pid
620654 put (NodeIdentifier nid) = putWord8 1 >> put nid
@@ -667,7 +701,7 @@ localProcessWithId :: LocalProcessId -> Accessor LocalNodeState (Maybe LocalProc
667701localProcessWithId lpid = localProcesses >>> DAC.mapMaybe lpid
668702
669703localConnectionBetween :: Identifier -> Identifier -> Accessor LocalNodeState (Maybe (NT.Connection, ImplicitReconnect))
670−localConnectionBetween from to = localConnections >>> DAC.mapMaybe (from, to)
704+localConnectionBetween from' to' = localConnections >>> DAC.mapMaybe (from', to')
671705
672706monitorCounter :: Accessor LocalProcessState Int32
673707monitorCounter = accessor _monitorCounter (\cnt st -> st { _monitorCounter = cnt })
tests/TestCH.hs+31−7
@@ -36,11 +36,11 @@ import Control.Distributed.Process.Internal.Types
3636import Control.Distributed.Process.Node
3737import Control.Distributed.Process.Serializable (Serializable)
3838
39−import Test.HUnit (Assertion)
39+import Test.HUnit (Assertion, assertFailure)
4040import Test.Framework (Test, defaultMain, testGroup)
4141import Test.Framework.Providers.HUnit (testCase)
42−import Control.Rematch hiding (expect, match)
43−import qualified Control.Rematch as Rematch (expect)
42+import Control.Rematch hiding (match)
43+import Control.Rematch.Run (Match(..))
4444
4545newtype Ping = Ping ProcessId
4646 deriving (Typeable, Binary, Show)
@@ -52,6 +52,12 @@ newtype Pong = Pong ProcessId
5252-- Supporting definitions --
5353--------------------------------------------------------------------------------
5454
55+expectThat :: a -> Matcher a -> Assertion
56+expectThat a matcher = case res of
57+ MatchSuccess -> return ()
58+ (MatchFailure msg) -> assertFailure msg
59+ where res = runMatch matcher a
60+
5561-- | Like fork, but throw exceptions in the child thread to the parent
5662forkTry :: IO () -> IO ThreadId
5763forkTry p = do
@@ -1080,6 +1086,22 @@ testCatchesExit transport = do
10801086
10811087 takeMVar done
10821088
1089+testHandleMessageIf :: NT.Transport -> Assertion
1090+testHandleMessageIf transport = do
1091+ localNode <- newLocalNode transport initRemoteTable
1092+ done <- newEmptyMVar
1093+ _ <- forkProcess localNode $ do
1094+ self <- getSelfPid
1095+ send self (5 :: Integer, 10 :: Integer)
1096+ msg <- receiveWait [ matchMessage return ]
1097+ Nothing <- handleMessageIf msg (\() -> True) (\() -> die $ "whoops")
1098+ handleMessageIf msg (\(x :: Integer, y :: Integer) -> x == 5 && y == 10)
1099+ (\input -> liftIO $ putMVar done input)
1100+ return ()
1101+
1102+ result <- takeMVar done
1103+ expectThat result $ equalTo (5, 10)
1104+
10831105testCatches :: NT.Transport -> Assertion
10841106testCatches transport = do
10851107 localNode <- newLocalNode transport initRemoteTable
@@ -1106,7 +1128,7 @@ testMaskRestoreScope transport = do
11061128
11071129 parent <- liftIO $ takeMVar parentPid
11081130 child <- liftIO $ takeMVar spawnedPid
1109− Rematch.expect parent $ isNot $ equalTo child
1131+ expectThat parent $ isNot $ equalTo child
11101132
11111133testDie :: NT.Transport -> Assertion
11121134testDie transport = do
@@ -1256,6 +1278,7 @@ tests (transport, transportInternals) = [
12561278 , testCase "Registry" (testRegistry transport)
12571279 , testCase "RemoteRegistry" (testRemoteRegistry transport)
12581280 , testCase "SpawnLocal" (testSpawnLocal transport)
1281+ , testCase "HandleMessageIf" (testHandleMessageIf transport)
12591282 , testCase "MatchAny" (testMatchAny transport)
12601283 , testCase "MatchAnyHandle" (testMatchAnyHandle transport)
12611284 , testCase "MatchAnyNoHandle" (testMatchAnyNoHandle transport)
@@ -1272,6 +1295,10 @@ tests (transport, transportInternals) = [
12721295 , testCase "MaskRestoreScope" (testMaskRestoreScope transport)
12731296 , testCase "ExitLocal" (testExitLocal transport)
12741297 , testCase "ExitRemote" (testExitRemote transport)
1298+ -- Unsafe Primitives
1299+ , testCase "TestUnsafeSend" (testUnsafeSend transport)
1300+ , testCase "TestUnsafeNSend" (testUnsafeNSend transport)
1301+ , testCase "TestUnsafeSendChan" (testUnsafeSendChan transport)
12751302 ]
12761303 , testGroup "Monitoring and Linking" [
12771304 -- Monitoring processes
@@ -1303,9 +1330,6 @@ tests (transport, transportInternals) = [
13031330 , testCase "MonitorChannel" (testMonitorChannel transport)
13041331 -- Reconnect
13051332 , testCase "Reconnect" (testReconnect transport transportInternals)
1306− , testCase "TestUnsafeSend" (testUnsafeSend transport)
1307− , testCase "TestUnsafeNSend" (testUnsafeNSend transport)
1308− , testCase "TestUnsafeSendChan" (testUnsafeSendChan transport)
13091333 ]
13101334 ]
13111335