@@ -15,8 +15,9 @@
15-- * Output exception on channel after endpoint is closed
15-- * Output exception on channel after endpoint is closed
16module Network.Transport.TCP ( -- * Main API
16module Network.Transport.TCP ( -- * Main API
17 createTransport
17 createTransport
18− , -- * TCP specific functionality (exported mostly for testing purposes_
18+ -- * Internals (exposed for unit tests)
19+ , createTransportExposeInternals
19− EndPointId
20+ , EndPointId
20 , encodeEndPointAddress
21 , encodeEndPointAddress
21 , decodeEndPointAddress
22 , decodeEndPointAddress
22 , ControlHeader(..)
23 , ControlHeader(..)
@@ -319,6 +320,16 @@ data ConnectionRequestResponse =
319-- TODOs: deal with hints
320-- TODOs: deal with hints
320createTransport :: N.HostName -> N.ServiceName -> IO (Either IOException Transport)
321createTransport :: N.HostName -> N.ServiceName -> IO (Either IOException Transport)
321createTransport host port = do
322createTransport host port = do
323+ result <- createTransportExposeInternals host port
327+ Right (transport, _) ->
328+ return $ Right transport
330+-- | You should probably not use this function (used for unit testing only)
331+createTransportExposeInternals :: N.HostName -> N.ServiceName -> IO (Either IOException (Transport, ThreadId))
332+createTransportExposeInternals host port = do
322 state <- newMVar . TransportValid $ ValidTransportState { _localEndPoints = Map.empty }
333 state <- newMVar . TransportValid $ ValidTransportState { _localEndPoints = Map.empty }
323 let transport = TCPTransport { transportState = state
334 let transport = TCPTransport { transportState = state
324 , transportHost = host
335 , transportHost = host
@@ -329,27 +340,45 @@ createTransport host port = do
329 -- http://tangentsoft.net/wskfaq/advanced.html#backlog
340 -- http://tangentsoft.net/wskfaq/advanced.html#backlog
330 -- http://www.linuxjournal.com/files/linuxjournal.com/linuxjournal/articles/023/2333/2333s2.html
341 -- http://www.linuxjournal.com/files/linuxjournal.com/linuxjournal/articles/023/2333/2333s2.html
331 transportThread <- forkServer host port N.sOMAXCONN (terminationHandler transport) (handleConnectionRequest transport)
342 transportThread <- forkServer host port N.sOMAXCONN (terminationHandler transport) (handleConnectionRequest transport)
332− return Transport { newEndPoint = apiNewEndPoint transport
343+ return ( Transport { newEndPoint = apiNewEndPoint transport
333− , closeTransport = killThread transportThread -- This will invoke the termination handler
344+ , closeTransport = apiCloseTransport transport (Just transportThread) [EndPointClosed]
336 terminationHandler :: TCPTransport -> SomeException -> IO ()
349 terminationHandler :: TCPTransport -> SomeException -> IO ()
337− terminationHandler transport _ = do
350+ terminationHandler transport ex = do
338− -- TODO: we currently don't make a distinction between endpoint failure and manual closure
351+ let ev = ErrorEvent (TransportError EventTransportFailed (show ex))
339− mTSt <- modifyMVar (transportState transport) $ \st -> case st of
352+ apiCloseTransport transport Nothing [ev]
340− TransportValid vst -> return (TransportClosed, Just vst)
341− TransportClosed -> return (TransportClosed, Nothing)
344− -- Transport already closed
347− mapM_ (apiCloseEndPoint transport) (Map.elems $ tSt ^. localEndPoints)
349--------------------------------------------------------------------------------
354--------------------------------------------------------------------------------
350-- API functions --
355-- API functions --
351--------------------------------------------------------------------------------
356--------------------------------------------------------------------------------
358+-- | Close the transport
359+apiCloseTransport :: TCPTransport -> Maybe ThreadId -> [Event] -> IO ()
360+apiCloseTransport transport mTransportThread evs = do
361+ mTSt <- modifyMVar (transportState transport) $ \st -> case st of
362+ TransportValid vst -> return (TransportClosed, Just vst)
363+ TransportClosed -> return (TransportClosed, Nothing)
366+ -- Transport already closed
369+ mapM_ (apiCloseEndPoint transport evs) (Map.elems $ tSt ^. localEndPoints)
370+ case mTransportThread of
371+ Just transportThread ->
372+ -- This will invoke the termination handler, which in turn will call
373+ -- apiCloseTransport again, but at that point the transport is
374+ -- already in closed state so at that point we return immediately
375+ killThread transportThread
377+ -- This will happen is apiCloseTransport was invoked *because* the
378+ -- transport thread died (in which case we don't need to kill it)
353-- | Create a new endpoint
382-- | Create a new endpoint
354apiNewEndPoint :: TCPTransport -> IO (Either (TransportError NewEndPointErrorCode) EndPoint)
383apiNewEndPoint :: TCPTransport -> IO (Either (TransportError NewEndPointErrorCode) EndPoint)
355apiNewEndPoint transport = try $ do
384apiNewEndPoint transport = try $ do
@@ -358,7 +387,7 @@ apiNewEndPoint transport = try $ do
358 { receive = readChan (localChannel ourEndPoint)
387 { receive = readChan (localChannel ourEndPoint)
359 , address = localAddress ourEndPoint
388 , address = localAddress ourEndPoint
360 , connect = apiConnect ourEndPoint
389 , connect = apiConnect ourEndPoint
361− , closeEndPoint = apiCloseEndPoint transport ourEndPoint
390+ , closeEndPoint = apiCloseEndPoint transport [EndPointClosed] ourEndPoint
362 , newMulticastGroup = return . Left $ newMulticastGroupError
391 , newMulticastGroup = return . Left $ newMulticastGroupError
363 , resolveMulticastGroup = return . Left . const resolveMulticastGroupError
392 , resolveMulticastGroup = return . Left . const resolveMulticastGroupError
@@ -442,8 +471,11 @@ apiSend theirEndPoint connId connAlive payload = do
442 sendFailed = TransportError SendFailed . show
471 sendFailed = TransportError SendFailed . show
444-- | Force-close the endpoint
473-- | Force-close the endpoint
445−apiCloseEndPoint :: TCPTransport -> LocalEndPoint -> IO ()
474+apiCloseEndPoint :: TCPTransport -- ^ Transport
475+ -> [Event] -- ^ Events to output on the endpoint to indicate closure
476+ -> LocalEndPoint -- ^ Local endpoint
446−apiCloseEndPoint transport ourEndPoint = do
478+apiCloseEndPoint transport evs ourEndPoint = do
447 -- Remove the reference from the transport state
479 -- Remove the reference from the transport state
448 removeLocalEndPoint transport ourEndPoint
480 removeLocalEndPoint transport ourEndPoint
449 -- Close the local endpoint
481 -- Close the local endpoint
@@ -460,11 +492,7 @@ apiCloseEndPoint transport ourEndPoint = do
460 -- Close all endpoints and kill all threads
492 -- Close all endpoints and kill all threads
461 forM_ (Map.elems $ vst ^. localConnections) $ tryCloseRemoteSocket
493 forM_ (Map.elems $ vst ^. localConnections) $ tryCloseRemoteSocket
462 forM_ (vst ^. internalThreads) killThread
494 forM_ (vst ^. internalThreads) killThread
463− -- We send a single message to the endpoint that it is closed. Subsequent
464− -- calls will block. We could change this so that all subsequent calls to
465− -- receive return an error, but this would mean checking for some state on
466− -- every call to receive, which is an unnecessary overhead.
467− writeChan (localChannel ourEndPoint) EndPointClosed
495+ forM_ evs $ writeChan (localChannel ourEndPoint)
469 -- Close the remote socket and return the set of all incoming connections
497 -- Close the remote socket and return the set of all incoming connections
470 tryCloseRemoteSocket :: RemoteEndPoint -> IO ()
498 tryCloseRemoteSocket :: RemoteEndPoint -> IO ()