network-transport-inmemory 0.5.1 → 0.5.2
raw patch · 6 files changed
+480/−437 lines, 6 filesdep ~basedep ~bytestringdep ~containersPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: base, bytestring, containers, network-transport, network-transport-tests
API changes (from Hackage documentation)
+ Network.Transport.InMemory.Debug: breakConnection :: TransportInternals -> EndPointAddress -> EndPointAddress -> String -> IO ()
+ Network.Transport.InMemory.Internal: LocalConnection :: !ConnectionId -> !EndPointAddress -> !EndPointAddress -> !(TVar LocalConnectionState) -> LocalConnection
+ Network.Transport.InMemory.Internal: LocalConnectionClosed :: LocalConnectionState
+ Network.Transport.InMemory.Internal: LocalConnectionFailed :: LocalConnectionState
+ Network.Transport.InMemory.Internal: LocalConnectionValid :: LocalConnectionState
+ Network.Transport.InMemory.Internal: LocalEndPoint :: !EndPointAddress -> !(TChan Event) -> !(TVar LocalEndPointState) -> LocalEndPoint
+ Network.Transport.InMemory.Internal: LocalEndPointClosed :: LocalEndPointState
+ Network.Transport.InMemory.Internal: LocalEndPointValid :: {-# UNPACK #-} !ValidLocalEndPointState -> LocalEndPointState
+ Network.Transport.InMemory.Internal: TransportClosed :: TransportState
+ Network.Transport.InMemory.Internal: TransportInternals :: (TVar TransportState) -> TransportInternals
+ Network.Transport.InMemory.Internal: TransportValid :: {-# UNPACK #-} !ValidTransportState -> TransportState
+ Network.Transport.InMemory.Internal: ValidLocalEndPointState :: !ConnectionId -> !(Map (EndPointAddress, ConnectionId) LocalConnection) -> Map MulticastAddress (TVar (Set EndPointAddress)) -> ValidLocalEndPointState
+ Network.Transport.InMemory.Internal: ValidTransportState :: !(Map EndPointAddress LocalEndPoint) -> !Int -> ValidTransportState
+ Network.Transport.InMemory.Internal: [_connections] :: ValidLocalEndPointState -> !(Map (EndPointAddress, ConnectionId) LocalConnection)
+ Network.Transport.InMemory.Internal: [_localEndPoints] :: ValidTransportState -> !(Map EndPointAddress LocalEndPoint)
+ Network.Transport.InMemory.Internal: [_multigroups] :: ValidLocalEndPointState -> Map MulticastAddress (TVar (Set EndPointAddress))
+ Network.Transport.InMemory.Internal: [_nextConnectionId] :: ValidLocalEndPointState -> !ConnectionId
+ Network.Transport.InMemory.Internal: [_nextLocalEndPointId] :: ValidTransportState -> !Int
+ Network.Transport.InMemory.Internal: [localConnectionId] :: LocalConnection -> !ConnectionId
+ Network.Transport.InMemory.Internal: [localConnectionLocalAddress] :: LocalConnection -> !EndPointAddress
+ Network.Transport.InMemory.Internal: [localConnectionRemoteAddress] :: LocalConnection -> !EndPointAddress
+ Network.Transport.InMemory.Internal: [localConnectionState] :: LocalConnection -> !(TVar LocalConnectionState)
+ Network.Transport.InMemory.Internal: [localEndPointAddress] :: LocalEndPoint -> !EndPointAddress
+ Network.Transport.InMemory.Internal: [localEndPointChannel] :: LocalEndPoint -> !(TChan Event)
+ Network.Transport.InMemory.Internal: [localEndPointState] :: LocalEndPoint -> !(TVar LocalEndPointState)
+ Network.Transport.InMemory.Internal: apiBreakConnection :: TVar TransportState -> EndPointAddress -> EndPointAddress -> String -> STM ()
+ Network.Transport.InMemory.Internal: apiClose :: TChan Event -> TVar TransportState -> LocalConnection -> IO ()
+ Network.Transport.InMemory.Internal: apiCloseEndPoint :: TVar TransportState -> EndPointAddress -> IO ()
+ Network.Transport.InMemory.Internal: apiConnect :: EndPointAddress -> TVar TransportState -> EndPointAddress -> Reliability -> ConnectHints -> IO (Either (TransportError ConnectErrorCode) Connection)
+ Network.Transport.InMemory.Internal: apiNewEndPoint :: TVar TransportState -> IO (Either (TransportError NewEndPointErrorCode) EndPoint)
+ Network.Transport.InMemory.Internal: apiSend :: TChan Event -> TVar TransportState -> LocalConnection -> [ByteString] -> IO (Either (TransportError SendErrorCode) ())
+ Network.Transport.InMemory.Internal: createTransportExposeInternals :: IO (Transport, TransportInternals)
+ Network.Transport.InMemory.Internal: data LocalConnection
+ Network.Transport.InMemory.Internal: data LocalConnectionState
+ Network.Transport.InMemory.Internal: data LocalEndPoint
+ Network.Transport.InMemory.Internal: data LocalEndPointState
+ Network.Transport.InMemory.Internal: data TransportState
+ Network.Transport.InMemory.Internal: data ValidLocalEndPointState
+ Network.Transport.InMemory.Internal: data ValidTransportState
+ Network.Transport.InMemory.Internal: newtype TransportInternals
Files
- ChangeLog +2/−0
- network-transport-inmemory.cabal +6/−4
- src/Network/Transport/InMemory.hs +3/−432
- src/Network/Transport/InMemory/Debug.hs +24/−0
- src/Network/Transport/InMemory/Internal.hs +445/−0
- tests/TestInMemory.hs +0/−1
ChangeLog view
@@ -1,3 +1,5 @@+0.5.2+* Introduced and published N.T.IM.Internal module. 0.5.1 * Fixed bug in cleanup procedure. 0.5
network-transport-inmemory.cabal view
@@ -1,5 +1,5 @@ Name: network-transport-inmemory-Version: 0.5.1+Version: 0.5.2 Cabal-Version: >=1.8 Build-Type: Simple License: BSD3@@ -30,19 +30,21 @@ Location: https://github.com/haskell-distributed/network-transport-inmemory Library- Build-Depends: base >= 4.3 && < 5,+ Build-Depends: base >= 4.6 && < 5, network-transport >= 0.4.0.0 && < 0.5, data-accessor >= 0.2 && < 0.3, bytestring >= 0.9 && < 0.11, containers >= 0.4 && < 0.6, stm >= 2.0 && < 3.0 Exposed-modules: Network.Transport.InMemory+ Network.Transport.InMemory.Internal+ Network.Transport.InMemory.Debug ghc-options: -Wall HS-Source-Dirs: src Test-Suite TestMulticastInMemory Type: exitcode-stdio-1.0- Build-Depends: base >= 4.3 && < 5,+ Build-Depends: base >= 4.6 && < 5, network-transport-inmemory, network-transport, network-transport-tests >= 0.1 && < 0.3@@ -53,7 +55,7 @@ Test-Suite TestInMemory Type: exitcode-stdio-1.0- Build-Depends: base >= 4.3 && < 5,+ Build-Depends: base >= 4.6 && < 5, network-transport-inmemory, network-transport-tests >= 0.1 && < 0.3, network-transport
src/Network/Transport/InMemory.hs view
@@ -1,6 +1,3 @@-{-# LANGUAGE RecursiveDo #-}-{-# OPTIONS_GHC -fno-warn-deprecations #-}- -- | In-memory implementation of the Transport API. module Network.Transport.InMemory ( createTransport@@ -10,440 +7,14 @@ , breakConnection ) where -import Control.Applicative import Network.Transport-import Network.Transport.Internal ( mapIOException )-import Control.Category ((>>>))-import Control.Concurrent.STM-import Control.Exception (handle, throw)-import Data.Map (Map)-import Data.Maybe (fromJust)-import Data.Monoid-import Data.Foldable-import qualified Data.Map as Map-import Data.Set (Set)-import qualified Data.Set as Set-import Data.ByteString (ByteString)-import qualified Data.ByteString.Char8 as BSC (pack)-import Data.Accessor (Accessor, accessor, (^.), (^=), (^:))-import qualified Data.Accessor.Container as DAC (mapMaybe)-import Data.Typeable (Typeable)-import Prelude hiding (foldr)--data TransportState- = TransportValid {-# UNPACK #-} !ValidTransportState- | TransportClosed--data ValidTransportState = ValidTransportState- { _localEndPoints :: !(Map EndPointAddress LocalEndPoint)- , _nextLocalEndPointId :: !Int- }--data LocalEndPoint = LocalEndPoint- { localEndPointAddress :: !EndPointAddress- , localEndPointChannel :: !(TChan Event)- , localEndPointState :: !(TVar LocalEndPointState)- }--data LocalEndPointState- = LocalEndPointValid {-# UNPACK #-} !ValidLocalEndPointState- | LocalEndPointClosed--data ValidLocalEndPointState = ValidLocalEndPointState- { _nextConnectionId :: !ConnectionId- , _connections :: !(Map (EndPointAddress,ConnectionId) LocalConnection)- , _multigroups :: Map MulticastAddress (TVar (Set EndPointAddress))- }--data LocalConnection = LocalConnection- { localConnectionId :: !ConnectionId- , localConnectionLocalAddress :: !EndPointAddress- , localConnectionRemoteAddress :: !EndPointAddress- , localConnectionState :: !(TVar LocalConnectionState)- }--data LocalConnectionState- = LocalConnectionValid- | LocalConnectionClosed- | LocalConnectionFailed--newtype TransportInternals = TransportInternals (TVar TransportState)+import Network.Transport.InMemory.Internal+import Network.Transport.InMemory.Debug -- | Create a new Transport. -- -- Only a single transport should be created per Haskell process -- (threads can, and should, create their own endpoints though). createTransport :: IO Transport-createTransport = fst <$> createTransportExposeInternals---- | Create a new Transport exposing internal state.------ Useful for testing and/or debugging purposes.--- Should not be used in production. No guarantee as to the stability of the internals API.-createTransportExposeInternals :: IO (Transport, TransportInternals)-createTransportExposeInternals = do- state <- newTVarIO $ TransportValid $ ValidTransportState- { _localEndPoints = Map.empty- , _nextLocalEndPointId = 0- }- return (Transport- { newEndPoint = apiNewEndPoint state- , closeTransport = do- -- transactions are splitted into smaller ones intentionally- old <- atomically $ swapTVar state TransportClosed- case old of- TransportClosed -> return ()- TransportValid tvst -> do- forM_ (tvst ^. localEndPoints) $ \l -> do- cons <- atomically $ whenValidLocalEndPointState l $ \lvst -> do- writeTChan (localEndPointChannel l) EndPointClosed- writeTVar (localEndPointState l) LocalEndPointClosed- return (lvst ^. connections)- forM_ cons $ \con -> atomically $- writeTVar (localConnectionState con) LocalConnectionClosed- }, TransportInternals state)---- | Create a new end point.-apiNewEndPoint :: TVar TransportState- -> IO (Either (TransportError NewEndPointErrorCode) EndPoint)-apiNewEndPoint state = handle (return . Left) $ atomically $ do- chan <- newTChan- (lep,addr) <- withValidTransportState state NewEndPointFailed $ \vst -> do- lepState <- newTVar $ LocalEndPointValid $ ValidLocalEndPointState- { _nextConnectionId = 1- , _connections = Map.empty- , _multigroups = Map.empty- }- let r = nextLocalEndPointId ^: (+ 1) $ vst- addr = EndPointAddress . BSC.pack . show $ r ^. nextLocalEndPointId- lep = LocalEndPoint- { localEndPointAddress = addr- , localEndPointChannel = chan- , localEndPointState = lepState- }- writeTVar state (TransportValid $ localEndPointAt addr ^= Just lep $ r)- return (lep, addr)- return $ Right $ EndPoint- { receive = atomically $ do- result <- tryReadTChan chan- case result of- Nothing -> do st <- readTVar (localEndPointState lep)- case st of- LocalEndPointClosed ->- throwSTM (userError "Channel is closed.")- LocalEndPointValid{} -> retry- Just x -> return x- , address = addr- , connect = apiConnect addr state- , closeEndPoint = apiCloseEndPoint state addr- , newMulticastGroup = return $ Left $ newMulticastGroupError- , resolveMulticastGroup = return . Left . const resolveMulticastGroupError- }- where- -- see [Multicast] section- newMulticastGroupError =- TransportError NewMulticastGroupUnsupported "Multicast not supported"- resolveMulticastGroupError =- TransportError ResolveMulticastGroupUnsupported "Multicast not supported"--apiCloseEndPoint :: TVar TransportState -> EndPointAddress -> IO ()-apiCloseEndPoint state addr = atomically $ whenValidTransportState state $ \vst ->- forM_ (vst ^. localEndPointAt addr) $ \lep -> do- old <- swapTVar (localEndPointState lep) LocalEndPointClosed- case old of- LocalEndPointClosed -> return ()- LocalEndPointValid lepvst -> do- forM_ (Map.elems (lepvst ^. connections)) $ \lconn -> do- st <- swapTVar (localConnectionState lconn) LocalConnectionClosed- case st of- LocalConnectionClosed -> return ()- LocalConnectionFailed -> return ()- _ -> forM_ (vst ^. localEndPointAt (localConnectionRemoteAddress lconn)) $ \thep ->- whenValidLocalEndPointState thep $ \_ -> do- writeTChan (localEndPointChannel thep)- (ConnectionClosed (localConnectionId lconn))- writeTChan (localEndPointChannel lep) EndPointClosed- writeTVar (localEndPointState lep) LocalEndPointClosed- writeTVar state (TransportValid $ (localEndPoints ^: Map.delete addr) vst)---- | Function that simulate failing connection between two endpoints,--- after calling this function both endpoints will receive ConnectionEventLost--- message, and all @LocalConnectionValid@ connections will--- be put into @LocalConnectionFailed@ state.-breakConnection :: TransportInternals- -> EndPointAddress- -> EndPointAddress- -> String -- ^ Error message- -> IO ()-breakConnection (TransportInternals state) from to message =- atomically $ apiBreakConnection state from to message----- | Tear down functions that should be called in case if conncetion fails.-apiBreakConnection :: TVar TransportState- -> EndPointAddress- -> EndPointAddress- -> String- -> STM ()-apiBreakConnection state us them msg- | us == them = return ()- | otherwise = whenValidTransportState state $ \vst -> do- breakOne vst us them >> breakOne vst them us- where- breakOne vst a b = do- forM_ (vst ^. localEndPointAt a) $ \lep ->- whenValidLocalEndPointState lep $ \lepvst -> do- let (cl, other) = Map.partitionWithKey (\(addr,_) _ -> addr == b)- (lepvst ^.connections)- forM_ cl $ \c -> modifyTVar (localConnectionState c)- (\x -> case x of- LocalConnectionValid -> LocalConnectionFailed- _ -> x)- writeTChan (localEndPointChannel lep)- (ErrorEvent (TransportError (EventConnectionLost b) msg))- writeTVar (localEndPointState lep)- (LocalEndPointValid $ (connections ^= other) lepvst)----- | Create a new connection-apiConnect :: EndPointAddress- -> TVar TransportState- -> EndPointAddress- -> Reliability- -> ConnectHints- -> IO (Either (TransportError ConnectErrorCode) Connection)-apiConnect ourAddress state theirAddress _reliability _hints = do- handle (return . Left) $ fmap Right $ atomically $ do- (chan, lconn) <- do- withValidTransportState state ConnectFailed $ \vst -> do- ourlep <- case vst ^. localEndPointAt ourAddress of- Nothing ->- throwSTM $ TransportError ConnectFailed "Endpoint closed"- Just x -> return x- theirlep <- case vst ^. localEndPointAt theirAddress of- Nothing ->- throwSTM $ TransportError ConnectNotFound "Endpoint not found"- Just x -> return x- conid <- withValidLocalEndPointState theirlep ConnectFailed $ \lepvst -> do- let r = nextConnectionId ^: (+ 1) $ lepvst- writeTVar (localEndPointState theirlep) (LocalEndPointValid r)- return (r ^. nextConnectionId)- withValidLocalEndPointState ourlep ConnectFailed $ \lepvst -> do- lconnState <- newTVar LocalConnectionValid- let lconn = LocalConnection- { localConnectionId = conid- , localConnectionLocalAddress = ourAddress- , localConnectionRemoteAddress = theirAddress- , localConnectionState = lconnState- }- writeTVar (localEndPointState ourlep)- (LocalEndPointValid $- connectionAt (theirAddress, conid) ^= lconn $ lepvst)- return (localEndPointChannel theirlep, lconn)- writeTChan chan $- ConnectionOpened (localConnectionId lconn) ReliableOrdered ourAddress- return $ Connection- { send = apiSend chan state lconn- , close = apiClose chan state lconn- }---- | Send a message over a connection-apiSend :: TChan Event- -> TVar TransportState- -> LocalConnection- -> [ByteString]- -> IO (Either (TransportError SendErrorCode) ())-apiSend chan state lconn msg = handle handleFailure $ mapIOException sendFailed $- atomically $ do- connst <- readTVar (localConnectionState lconn)- case connst of- LocalConnectionValid -> do- foldr seq () msg `seq`- writeTChan chan (Received (localConnectionId lconn) msg)- return $ Right ()- LocalConnectionClosed -> do- -- If the local connection was closed, check why.- withValidTransportState state SendFailed $ \vst -> do- let addr = localConnectionLocalAddress lconn- mblep = vst ^. localEndPointAt addr- case mblep of- Nothing -> throwSTM $ TransportError SendFailed "Endpoint closed"- Just lep -> do- lepst <- readTVar (localEndPointState lep)- case lepst of- LocalEndPointValid _ -> do- return $ Left $ TransportError SendClosed "Connection closed"- LocalEndPointClosed -> do- throwSTM $ TransportError SendFailed "Endpoint closed"- LocalConnectionFailed -> return $- Left $ TransportError SendFailed "Endpoint closed"- where- sendFailed = TransportError SendFailed . show- handleFailure ex@(TransportError SendFailed reason) = atomically $ do- apiBreakConnection state (localConnectionLocalAddress lconn)- (localConnectionRemoteAddress lconn)- reason- return (Left ex)- handleFailure ex = return (Left ex)---- | Close a connection-apiClose :: TChan Event- -> TVar TransportState- -> LocalConnection- -> IO ()-apiClose chan state lconn = do- atomically $ do -- XXX: whenValidConnectionState- connst <- readTVar (localConnectionState lconn)- case connst of- LocalConnectionValid -> do- writeTChan chan $ ConnectionClosed (localConnectionId lconn)- writeTVar (localConnectionState lconn) LocalConnectionClosed- whenValidTransportState state $ \vst -> do- let mblep = vst ^. localEndPointAt (localConnectionLocalAddress lconn)- theirAddress = localConnectionRemoteAddress lconn- forM_ mblep $ \lep ->- whenValidLocalEndPointState lep $- writeTVar (localEndPointState lep)- . LocalEndPointValid- . (connections ^: Map.delete (theirAddress, localConnectionId lconn))- _ -> return ()---- [Multicast]--- Currently multicast implementation doesn't pass it's tests, so it--- disabled. Here we have old code that could be improved, see GitHub ISSUE 5--- https://github.com/haskell-distributed/network-transport-inmemory/issues/5---- | Create a new multicast group-_apiNewMulticastGroup :: TVar TransportState- -> EndPointAddress- -> IO (Either (TransportError NewMulticastGroupErrorCode) MulticastGroup)-_apiNewMulticastGroup state ourAddress = handle (return . Left) $ do- group <- newTVarIO Set.empty- groupAddr <- atomically $- withValidTransportState state NewMulticastGroupFailed $ \vst -> do- lep <- maybe (throwSTM $ TransportError NewMulticastGroupFailed "Endpoint closed")- return- (vst ^. localEndPointAt ourAddress)- withValidLocalEndPointState lep NewMulticastGroupFailed $ \lepvst -> do- let addr = MulticastAddress . BSC.pack . show . Map.size $ lepvst ^. multigroups- writeTVar (localEndPointState lep) (LocalEndPointValid $ multigroupAt addr ^= group $ lepvst)- return addr- return . Right $ createMulticastGroup state ourAddress groupAddr group---- | Construct a multicast group------ When the group is deleted some endpoints may still receive messages, but--- subsequent calls to resolveMulticastGroup will fail. This mimicks the fact--- that some multicast messages may still be in transit when the group is--- deleted.-createMulticastGroup :: TVar TransportState- -> EndPointAddress- -> MulticastAddress- -> TVar (Set EndPointAddress)- -> MulticastGroup-createMulticastGroup state ourAddress groupAddress group = MulticastGroup- { multicastAddress = groupAddress- , deleteMulticastGroup = atomically $- whenValidTransportState state $ \vst -> do- -- XXX best we can do given current broken API, which needs fixing.- let lep = fromJust $ vst ^. localEndPointAt ourAddress- modifyTVar' (localEndPointState lep) $ \lepst -> case lepst of- LocalEndPointValid lepvst ->- LocalEndPointValid $ multigroups ^: Map.delete groupAddress $ lepvst- LocalEndPointClosed ->- LocalEndPointClosed- , maxMsgSize = Nothing- , multicastSend = \payload -> atomically $- withValidTransportState state SendFailed $ \vst -> do- es <- readTVar group- forM_ (Set.elems es) $ \ep -> do- let ch = localEndPointChannel $ fromJust $ vst ^. localEndPointAt ep- writeTChan ch (ReceivedMulticast groupAddress payload)- , multicastSubscribe = atomically $ modifyTVar' group $ Set.insert ourAddress- , multicastUnsubscribe = atomically $ modifyTVar' group $ Set.delete ourAddress- , multicastClose = return ()- }---- | Resolve a multicast group-_apiResolveMulticastGroup :: TVar TransportState- -> EndPointAddress- -> MulticastAddress- -> IO (Either (TransportError ResolveMulticastGroupErrorCode) MulticastGroup)-_apiResolveMulticastGroup state ourAddress groupAddress = handle (return . Left) $ atomically $- withValidTransportState state ResolveMulticastGroupFailed $ \vst -> do- lep <- maybe (throwSTM $ TransportError ResolveMulticastGroupFailed "Endpoint closed")- return- (vst ^. localEndPointAt ourAddress)- withValidLocalEndPointState lep ResolveMulticastGroupFailed $ \lepvst -> do- let group = lepvst ^. (multigroups >>> DAC.mapMaybe groupAddress)- case group of- Nothing ->- return . Left $- TransportError ResolveMulticastGroupNotFound- ("Group " ++ show groupAddress ++ " not found")- Just mvar ->- return . Right $ createMulticastGroup state ourAddress groupAddress mvar------------------------------------------------------------------------------------- Lens definitions -------------------------------------------------------------------------------------nextLocalEndPointId :: Accessor ValidTransportState Int-nextLocalEndPointId = accessor _nextLocalEndPointId (\eid st -> st{ _nextLocalEndPointId = eid} )--localEndPoints :: Accessor ValidTransportState (Map EndPointAddress LocalEndPoint)-localEndPoints = accessor _localEndPoints (\leps st -> st { _localEndPoints = leps })--nextConnectionId :: Accessor ValidLocalEndPointState ConnectionId-nextConnectionId = accessor _nextConnectionId (\cid st -> st { _nextConnectionId = cid })--connections :: Accessor ValidLocalEndPointState (Map (EndPointAddress,ConnectionId) LocalConnection)-connections = accessor _connections (\conns st -> st { _connections = conns })--multigroups :: Accessor ValidLocalEndPointState (Map MulticastAddress (TVar (Set EndPointAddress)))-multigroups = accessor _multigroups (\gs st -> st { _multigroups = gs })--at :: Ord k => k -> String -> Accessor (Map k v) v-at k err = accessor (Map.findWithDefault (error err) k) (Map.insert k)--localEndPointAt :: EndPointAddress -> Accessor ValidTransportState (Maybe LocalEndPoint)-localEndPointAt addr = localEndPoints >>> DAC.mapMaybe addr--connectionAt :: (EndPointAddress, ConnectionId) -> Accessor ValidLocalEndPointState LocalConnection-connectionAt addr = connections >>> at addr "Invalid connection"--multigroupAt :: MulticastAddress -> Accessor ValidLocalEndPointState (TVar (Set EndPointAddress))-multigroupAt addr = multigroups >>> at addr "Invalid multigroup"-------------------------------------------------------------------------------------- Helpers-------------------------------------------------------------------------------------- | LocalEndPoint state deconstructor.-overValidLocalEndPointState :: LocalEndPoint -> STM a -> (ValidLocalEndPointState -> STM a) -> STM a-overValidLocalEndPointState lep fallback action = do- lepst <- readTVar (localEndPointState lep)- case lepst of- LocalEndPointValid lepvst -> action lepvst- _ -> fallback---- | Specialized deconstructor that throws TransportError in case of Closed state-withValidLocalEndPointState :: (Typeable e, Show e) => LocalEndPoint -> e -> (ValidLocalEndPointState -> STM a) -> STM a-withValidLocalEndPointState lep ex = overValidLocalEndPointState lep (throw $ TransportError ex "EndPoint closed")---- | Specialized deconstructor that do nothing in case of failure-whenValidLocalEndPointState :: Monoid m => LocalEndPoint -> (ValidLocalEndPointState -> STM m) -> STM m-whenValidLocalEndPointState lep = overValidLocalEndPointState lep (return mempty)--overValidTransportState :: TVar TransportState -> STM a -> (ValidTransportState -> STM a) -> STM a-overValidTransportState ts fallback action = do- tsst <- readTVar ts- case tsst of- TransportValid tsvst -> action tsvst- _ -> fallback--withValidTransportState :: (Typeable e, Show e) => TVar TransportState -> e -> (ValidTransportState -> STM a) -> STM a-withValidTransportState ts ex = overValidTransportState ts (throw $ TransportError ex "Transport closed")+createTransport = fmap fst createTransportExposeInternals -whenValidTransportState :: Monoid m => TVar TransportState -> (ValidTransportState -> STM m) -> STM m-whenValidTransportState ts = overValidTransportState ts (return mempty)
+ src/Network/Transport/InMemory/Debug.hs view
@@ -0,0 +1,24 @@+-- |+-- Module: Network.Transport.InMemory.Debug+--+-- Miscelanteous functions for debug purposes.+module Network.Transport.InMemory.Debug+ ( breakConnection+ ) where++import Control.Concurrent.STM+import Network.Transport+import Network.Transport.InMemory.Internal++-- | Function that simulate failing connection between two endpoints,+-- after calling this function both endpoints will receive ConnectionEventLost+-- message, and all @LocalConnectionValid@ connections will+-- be put into @LocalConnectionFailed@ state.+breakConnection :: TransportInternals+ -> EndPointAddress -- ^ @From@ connection+ -> EndPointAddress -- ^ @To@ connection+ -> String -- ^ Error message+ -> IO ()+breakConnection (TransportInternals state) from to message =+ atomically $ apiBreakConnection state from to message+
+ src/Network/Transport/InMemory/Internal.hs view
@@ -0,0 +1,445 @@+{-# LANGUAGE RecursiveDo #-}+{-# OPTIONS_GHC -fno-warn-deprecations #-}+-- |+-- Module: Network.Transport.InMemory.Internal+--+-- Internal part of the implementation. This module is for internal use+-- or advanced debuging. There are no guarantees about stability of this+-- module.+module Network.Transport.InMemory.Internal+ ( createTransportExposeInternals+ -- * Internal structures+ , TransportInternals(..)+ , TransportState(..)+ , ValidTransportState(..)+ , LocalEndPoint(..)+ , LocalEndPointState(..)+ , ValidLocalEndPointState(..)+ , LocalConnection(..)+ , LocalConnectionState(..)+ -- * Low level functionality+ , apiNewEndPoint+ , apiCloseEndPoint+ , apiBreakConnection+ , apiConnect+ , apiSend+ , apiClose+ ) where++import Network.Transport+import Network.Transport.Internal ( mapIOException )+import Control.Category ((>>>))+import Control.Concurrent.STM+import Control.Exception (handle, throw)+import Data.Map (Map)+import Data.Maybe (fromJust)+import Data.Monoid+import Data.Foldable+import qualified Data.Map as Map+import Data.Set (Set)+import qualified Data.Set as Set+import Data.ByteString (ByteString)+import qualified Data.ByteString.Char8 as BSC (pack)+import Data.Accessor (Accessor, accessor, (^.), (^=), (^:))+import qualified Data.Accessor.Container as DAC (mapMaybe)+import Data.Typeable (Typeable)+import Prelude hiding (foldr)++data TransportState+ = TransportValid {-# UNPACK #-} !ValidTransportState+ | TransportClosed++data ValidTransportState = ValidTransportState+ { _localEndPoints :: !(Map EndPointAddress LocalEndPoint)+ , _nextLocalEndPointId :: !Int+ }++data LocalEndPoint = LocalEndPoint+ { localEndPointAddress :: !EndPointAddress+ , localEndPointChannel :: !(TChan Event)+ , localEndPointState :: !(TVar LocalEndPointState)+ }++data LocalEndPointState+ = LocalEndPointValid {-# UNPACK #-} !ValidLocalEndPointState+ | LocalEndPointClosed++data ValidLocalEndPointState = ValidLocalEndPointState+ { _nextConnectionId :: !ConnectionId+ , _connections :: !(Map (EndPointAddress,ConnectionId) LocalConnection)+ , _multigroups :: Map MulticastAddress (TVar (Set EndPointAddress))+ }++data LocalConnection = LocalConnection+ { localConnectionId :: !ConnectionId+ , localConnectionLocalAddress :: !EndPointAddress+ , localConnectionRemoteAddress :: !EndPointAddress+ , localConnectionState :: !(TVar LocalConnectionState)+ }++data LocalConnectionState+ = LocalConnectionValid+ | LocalConnectionClosed+ | LocalConnectionFailed++newtype TransportInternals = TransportInternals (TVar TransportState)++-- | Create a new Transport exposing internal state.+--+-- Useful for testing and/or debugging purposes.+-- Should not be used in production. No guarantee as to the stability of the internals API.+createTransportExposeInternals :: IO (Transport, TransportInternals)+createTransportExposeInternals = do+ state <- newTVarIO $ TransportValid $ ValidTransportState+ { _localEndPoints = Map.empty+ , _nextLocalEndPointId = 0+ }+ return (Transport+ { newEndPoint = apiNewEndPoint state+ , closeTransport = do+ -- transactions are splitted into smaller ones intentionally+ old <- atomically $ swapTVar state TransportClosed+ case old of+ TransportClosed -> return ()+ TransportValid tvst -> do+ forM_ (tvst ^. localEndPoints) $ \l -> do+ cons <- atomically $ whenValidLocalEndPointState l $ \lvst -> do+ writeTChan (localEndPointChannel l) EndPointClosed+ writeTVar (localEndPointState l) LocalEndPointClosed+ return (lvst ^. connections)+ forM_ cons $ \con -> atomically $+ writeTVar (localConnectionState con) LocalConnectionClosed+ }, TransportInternals state)+++-- | Create a new end point.+apiNewEndPoint :: TVar TransportState+ -> IO (Either (TransportError NewEndPointErrorCode) EndPoint)+apiNewEndPoint state = handle (return . Left) $ atomically $ do+ chan <- newTChan+ (lep,addr) <- withValidTransportState state NewEndPointFailed $ \vst -> do+ lepState <- newTVar $ LocalEndPointValid $ ValidLocalEndPointState+ { _nextConnectionId = 1+ , _connections = Map.empty+ , _multigroups = Map.empty+ }+ let r = nextLocalEndPointId ^: (+ 1) $ vst+ addr = EndPointAddress . BSC.pack . show $ r ^. nextLocalEndPointId+ lep = LocalEndPoint+ { localEndPointAddress = addr+ , localEndPointChannel = chan+ , localEndPointState = lepState+ }+ writeTVar state (TransportValid $ localEndPointAt addr ^= Just lep $ r)+ return (lep, addr)+ return $ Right $ EndPoint+ { receive = atomically $ do+ result <- tryReadTChan chan+ case result of+ Nothing -> do st <- readTVar (localEndPointState lep)+ case st of+ LocalEndPointClosed ->+ throwSTM (userError "Channel is closed.")+ LocalEndPointValid{} -> retry+ Just x -> return x+ , address = addr+ , connect = apiConnect addr state+ , closeEndPoint = apiCloseEndPoint state addr+ , newMulticastGroup = return $ Left $ newMulticastGroupError+ , resolveMulticastGroup = return . Left . const resolveMulticastGroupError+ }+ where+ -- see [Multicast] section+ newMulticastGroupError =+ TransportError NewMulticastGroupUnsupported "Multicast not supported"+ resolveMulticastGroupError =+ TransportError ResolveMulticastGroupUnsupported "Multicast not supported"++apiCloseEndPoint :: TVar TransportState -> EndPointAddress -> IO ()+apiCloseEndPoint state addr = atomically $ whenValidTransportState state $ \vst ->+ forM_ (vst ^. localEndPointAt addr) $ \lep -> do+ old <- swapTVar (localEndPointState lep) LocalEndPointClosed+ case old of+ LocalEndPointClosed -> return ()+ LocalEndPointValid lepvst -> do+ forM_ (Map.elems (lepvst ^. connections)) $ \lconn -> do+ st <- swapTVar (localConnectionState lconn) LocalConnectionClosed+ case st of+ LocalConnectionClosed -> return ()+ LocalConnectionFailed -> return ()+ _ -> forM_ (vst ^. localEndPointAt (localConnectionRemoteAddress lconn)) $ \thep ->+ whenValidLocalEndPointState thep $ \_ -> do+ writeTChan (localEndPointChannel thep)+ (ConnectionClosed (localConnectionId lconn))+ writeTChan (localEndPointChannel lep) EndPointClosed+ writeTVar (localEndPointState lep) LocalEndPointClosed+ writeTVar state (TransportValid $ (localEndPoints ^: Map.delete addr) vst)++-- | Tear down functions that should be called in case if conncetion fails.+apiBreakConnection :: TVar TransportState+ -> EndPointAddress+ -> EndPointAddress+ -> String+ -> STM ()+apiBreakConnection state us them msg+ | us == them = return ()+ | otherwise = whenValidTransportState state $ \vst -> do+ breakOne vst us them >> breakOne vst them us+ where+ breakOne vst a b = do+ forM_ (vst ^. localEndPointAt a) $ \lep ->+ whenValidLocalEndPointState lep $ \lepvst -> do+ let (cl, other) = Map.partitionWithKey (\(addr,_) _ -> addr == b)+ (lepvst ^.connections)+ forM_ cl $ \c -> modifyTVar (localConnectionState c)+ (\x -> case x of+ LocalConnectionValid -> LocalConnectionFailed+ _ -> x)+ writeTChan (localEndPointChannel lep)+ (ErrorEvent (TransportError (EventConnectionLost b) msg))+ writeTVar (localEndPointState lep)+ (LocalEndPointValid $ (connections ^= other) lepvst)+++-- | Create a new connection+apiConnect :: EndPointAddress+ -> TVar TransportState+ -> EndPointAddress+ -> Reliability+ -> ConnectHints+ -> IO (Either (TransportError ConnectErrorCode) Connection)+apiConnect ourAddress state theirAddress _reliability _hints = do+ handle (return . Left) $ fmap Right $ atomically $ do+ (chan, lconn) <- do+ withValidTransportState state ConnectFailed $ \vst -> do+ ourlep <- case vst ^. localEndPointAt ourAddress of+ Nothing ->+ throwSTM $ TransportError ConnectFailed "Endpoint closed"+ Just x -> return x+ theirlep <- case vst ^. localEndPointAt theirAddress of+ Nothing ->+ throwSTM $ TransportError ConnectNotFound "Endpoint not found"+ Just x -> return x+ conid <- withValidLocalEndPointState theirlep ConnectFailed $ \lepvst -> do+ let r = nextConnectionId ^: (+ 1) $ lepvst+ writeTVar (localEndPointState theirlep) (LocalEndPointValid r)+ return (r ^. nextConnectionId)+ withValidLocalEndPointState ourlep ConnectFailed $ \lepvst -> do+ lconnState <- newTVar LocalConnectionValid+ let lconn = LocalConnection+ { localConnectionId = conid+ , localConnectionLocalAddress = ourAddress+ , localConnectionRemoteAddress = theirAddress+ , localConnectionState = lconnState+ }+ writeTVar (localEndPointState ourlep)+ (LocalEndPointValid $+ connectionAt (theirAddress, conid) ^= lconn $ lepvst)+ return (localEndPointChannel theirlep, lconn)+ writeTChan chan $+ ConnectionOpened (localConnectionId lconn) ReliableOrdered ourAddress+ return $ Connection+ { send = apiSend chan state lconn+ , close = apiClose chan state lconn+ }++-- | Send a message over a connection+apiSend :: TChan Event+ -> TVar TransportState+ -> LocalConnection+ -> [ByteString]+ -> IO (Either (TransportError SendErrorCode) ())+apiSend chan state lconn msg = handle handleFailure $ mapIOException sendFailed $+ atomically $ do+ connst <- readTVar (localConnectionState lconn)+ case connst of+ LocalConnectionValid -> do+ foldr seq () msg `seq`+ writeTChan chan (Received (localConnectionId lconn) msg)+ return $ Right ()+ LocalConnectionClosed -> do+ -- If the local connection was closed, check why.+ withValidTransportState state SendFailed $ \vst -> do+ let addr = localConnectionLocalAddress lconn+ mblep = vst ^. localEndPointAt addr+ case mblep of+ Nothing -> throwSTM $ TransportError SendFailed "Endpoint closed"+ Just lep -> do+ lepst <- readTVar (localEndPointState lep)+ case lepst of+ LocalEndPointValid _ -> do+ return $ Left $ TransportError SendClosed "Connection closed"+ LocalEndPointClosed -> do+ throwSTM $ TransportError SendFailed "Endpoint closed"+ LocalConnectionFailed -> return $+ Left $ TransportError SendFailed "Endpoint closed"+ where+ sendFailed = TransportError SendFailed . show+ handleFailure ex@(TransportError SendFailed reason) = atomically $ do+ apiBreakConnection state (localConnectionLocalAddress lconn)+ (localConnectionRemoteAddress lconn)+ reason+ return (Left ex)+ handleFailure ex = return (Left ex)++-- | Close a connection+apiClose :: TChan Event+ -> TVar TransportState+ -> LocalConnection+ -> IO ()+apiClose chan state lconn = do+ atomically $ do -- XXX: whenValidConnectionState+ connst <- readTVar (localConnectionState lconn)+ case connst of+ LocalConnectionValid -> do+ writeTChan chan $ ConnectionClosed (localConnectionId lconn)+ writeTVar (localConnectionState lconn) LocalConnectionClosed+ whenValidTransportState state $ \vst -> do+ let mblep = vst ^. localEndPointAt (localConnectionLocalAddress lconn)+ theirAddress = localConnectionRemoteAddress lconn+ forM_ mblep $ \lep ->+ whenValidLocalEndPointState lep $+ writeTVar (localEndPointState lep)+ . LocalEndPointValid+ . (connections ^: Map.delete (theirAddress, localConnectionId lconn))+ _ -> return ()++-- [Multicast]+-- Currently multicast implementation doesn't pass it's tests, so it+-- disabled. Here we have old code that could be improved, see GitHub ISSUE 5+-- https://github.com/haskell-distributed/network-transport-inmemory/issues/5++-- | Construct a multicast group+--+-- When the group is deleted some endpoints may still receive messages, but+-- subsequent calls to resolveMulticastGroup will fail. This mimicks the fact+-- that some multicast messages may still be in transit when the group is+-- deleted.+createMulticastGroup :: TVar TransportState+ -> EndPointAddress+ -> MulticastAddress+ -> TVar (Set EndPointAddress)+ -> MulticastGroup+createMulticastGroup state ourAddress groupAddress group = MulticastGroup+ { multicastAddress = groupAddress+ , deleteMulticastGroup = atomically $+ whenValidTransportState state $ \vst -> do+ -- XXX best we can do given current broken API, which needs fixing.+ let lep = fromJust $ vst ^. localEndPointAt ourAddress+ modifyTVar' (localEndPointState lep) $ \lepst -> case lepst of+ LocalEndPointValid lepvst ->+ LocalEndPointValid $ multigroups ^: Map.delete groupAddress $ lepvst+ LocalEndPointClosed ->+ LocalEndPointClosed+ , maxMsgSize = Nothing+ , multicastSend = \payload -> atomically $+ withValidTransportState state SendFailed $ \vst -> do+ es <- readTVar group+ forM_ (Set.elems es) $ \ep -> do+ let ch = localEndPointChannel $ fromJust $ vst ^. localEndPointAt ep+ writeTChan ch (ReceivedMulticast groupAddress payload)+ , multicastSubscribe = atomically $ modifyTVar' group $ Set.insert ourAddress+ , multicastUnsubscribe = atomically $ modifyTVar' group $ Set.delete ourAddress+ , multicastClose = return ()+ }++-- | Create a new multicast group+_apiNewMulticastGroup :: TVar TransportState+ -> EndPointAddress+ -> IO (Either (TransportError NewMulticastGroupErrorCode) MulticastGroup)+_apiNewMulticastGroup state ourAddress = handle (return . Left) $ do+ group <- newTVarIO Set.empty+ groupAddr <- atomically $+ withValidTransportState state NewMulticastGroupFailed $ \vst -> do+ lep <- maybe (throwSTM $ TransportError NewMulticastGroupFailed "Endpoint closed")+ return+ (vst ^. localEndPointAt ourAddress)+ withValidLocalEndPointState lep NewMulticastGroupFailed $ \lepvst -> do+ let addr = MulticastAddress . BSC.pack . show . Map.size $ lepvst ^. multigroups+ writeTVar (localEndPointState lep) (LocalEndPointValid $ multigroupAt addr ^= group $ lepvst)+ return addr+ return . Right $ createMulticastGroup state ourAddress groupAddr group++-- | Resolve a multicast group+_apiResolveMulticastGroup :: TVar TransportState+ -> EndPointAddress+ -> MulticastAddress+ -> IO (Either (TransportError ResolveMulticastGroupErrorCode) MulticastGroup)+_apiResolveMulticastGroup state ourAddress groupAddress = handle (return . Left) $ atomically $+ withValidTransportState state ResolveMulticastGroupFailed $ \vst -> do+ lep <- maybe (throwSTM $ TransportError ResolveMulticastGroupFailed "Endpoint closed")+ return+ (vst ^. localEndPointAt ourAddress)+ withValidLocalEndPointState lep ResolveMulticastGroupFailed $ \lepvst -> do+ let group = lepvst ^. (multigroups >>> DAC.mapMaybe groupAddress)+ case group of+ Nothing ->+ return . Left $+ TransportError ResolveMulticastGroupNotFound+ ("Group " ++ show groupAddress ++ " not found")+ Just mvar ->+ return . Right $ createMulticastGroup state ourAddress groupAddress mvar++--------------------------------------------------------------------------------+-- Lens definitions --+--------------------------------------------------------------------------------++nextLocalEndPointId :: Accessor ValidTransportState Int+nextLocalEndPointId = accessor _nextLocalEndPointId (\eid st -> st{ _nextLocalEndPointId = eid} )++localEndPoints :: Accessor ValidTransportState (Map EndPointAddress LocalEndPoint)+localEndPoints = accessor _localEndPoints (\leps st -> st { _localEndPoints = leps })++nextConnectionId :: Accessor ValidLocalEndPointState ConnectionId+nextConnectionId = accessor _nextConnectionId (\cid st -> st { _nextConnectionId = cid })++connections :: Accessor ValidLocalEndPointState (Map (EndPointAddress,ConnectionId) LocalConnection)+connections = accessor _connections (\conns st -> st { _connections = conns })++multigroups :: Accessor ValidLocalEndPointState (Map MulticastAddress (TVar (Set EndPointAddress)))+multigroups = accessor _multigroups (\gs st -> st { _multigroups = gs })++at :: Ord k => k -> String -> Accessor (Map k v) v+at k err = accessor (Map.findWithDefault (error err) k) (Map.insert k)++localEndPointAt :: EndPointAddress -> Accessor ValidTransportState (Maybe LocalEndPoint)+localEndPointAt addr = localEndPoints >>> DAC.mapMaybe addr++connectionAt :: (EndPointAddress, ConnectionId) -> Accessor ValidLocalEndPointState LocalConnection+connectionAt addr = connections >>> at addr "Invalid connection"++multigroupAt :: MulticastAddress -> Accessor ValidLocalEndPointState (TVar (Set EndPointAddress))+multigroupAt addr = multigroups >>> at addr "Invalid multigroup"++---------------------------------------------------------------------------------+-- Helpers+---------------------------------------------------------------------------------++-- | LocalEndPoint state deconstructor.+overValidLocalEndPointState :: LocalEndPoint -> STM a -> (ValidLocalEndPointState -> STM a) -> STM a+overValidLocalEndPointState lep fallback action = do+ lepst <- readTVar (localEndPointState lep)+ case lepst of+ LocalEndPointValid lepvst -> action lepvst+ _ -> fallback++-- | Specialized deconstructor that throws TransportError in case of Closed state+withValidLocalEndPointState :: (Typeable e, Show e) => LocalEndPoint -> e -> (ValidLocalEndPointState -> STM a) -> STM a+withValidLocalEndPointState lep ex = overValidLocalEndPointState lep (throw $ TransportError ex "EndPoint closed")++-- | Specialized deconstructor that do nothing in case of failure+whenValidLocalEndPointState :: Monoid m => LocalEndPoint -> (ValidLocalEndPointState -> STM m) -> STM m+whenValidLocalEndPointState lep = overValidLocalEndPointState lep (return mempty)++overValidTransportState :: TVar TransportState -> STM a -> (ValidTransportState -> STM a) -> STM a+overValidTransportState ts fallback action = do+ tsst <- readTVar ts+ case tsst of+ TransportValid tsvst -> action tsvst+ _ -> fallback++withValidTransportState :: (Typeable e, Show e) => TVar TransportState -> e -> (ValidTransportState -> STM a) -> STM a+withValidTransportState ts ex = overValidTransportState ts (throw $ TransportError ex "Transport closed")++whenValidTransportState :: Monoid m => TVar TransportState -> (ValidTransportState -> STM m) -> STM m+whenValidTransportState ts = overValidTransportState ts (return mempty)
tests/TestInMemory.hs view
@@ -5,7 +5,6 @@ import Network.Transport.InMemory import Network.Transport import Control.Applicative ((<$>))-import Control.Concurrent main :: IO () main = do