[Haskell] Help needed interrupting accepting a network connection

Chris Kuklewicz haskell at list.mightyreason.com
Sun Dec 3 11:16:10 EST 2006


I realized there is another problem, since my code holds onto the ThreadId's the thread
data structures may or may not be getting garbage collected and for a long running
server the list of children grows without bound.

So I changed it to periodically clean out the finished child threads from the list
of children.  A simple counter IORef is used to avoid doing the cleanup on each
new child.

There are also a couple of other small style changes.

> {-
> 
> The main accepting thread spawns this a slave thread to run accept and
> stuffs the result into a TMVar.  The main loop then atomically checks
> the TVar used for graceful shutdown and the TMVar.  These two checks
> are combined by `orElse` which gives the semantics one wants: on each
> loop either the TVar has been set to True or the the slave thread has
> accepted a client into the TMVar.
> 
> There is still the possibility that a busy server could accept a
> connection from the last client and put it in the TMVar where the main
> loop will miss it when it exits.  This is handled by the finally
> action which waits for the slave thread to be well and truly dead and
> then looks for that last client in the TMVar.
> 
> The list of child threads is cleaned periodically (currently every
> 10th child), which allows the garbage collected to remove the dead
> threads' structures.
> 
> -}
> 
> -- Example using STM and orElse to compose a solution
> import Control.Monad
> import Control.Concurrent
> import Control.Exception
> import Control.Concurrent.STM
> import Data.IORef
> import Network
> import System.IO
> 
> forever x = x >> forever x
> 
> runExampleFor socket seconds = do
>   tv <- newTVarIO False           -- Set to True to indicate graceful exit requested
>   sInfo <- startServer socket tv
>   threadDelay (1000*1000*seconds)
>   shutdownServer tv sInfo
> 
> startServer socket tv = do
>   childrenList <- newMVar []
>   tInfo <- fork (acceptUntil socket exampleReceiver childrenList (retry'until'true tv))
>   return (tInfo,childrenList)
> 
> shutdownServer tv ((acceptLoopDone,_),childrenList) = do
>   atomically (writeTVar tv True)
>   readMVar acceptLoopDone
>   withMVar childrenList (mapM_ (readMVar . fst))
> 
> -- Capture idiom of notifying a new MVar when a thread is finished
> fork todo = do
>   doneMVar <- newEmptyMVar
>   tid <- forkIO $ finally todo (putMVar doneMVar ())
>   return (doneMVar,tid)
> 
> cond true false test = if test then true else false
> 
> -- This is an asychronous exception safe way to use accept to get one
> -- client at a time and pass them to the parent thread via a TMVar.
> acceptInto socket chan =  block . forever $ do
>   unblock . atomically $
>     isEmptyTMVar chan >>= cond (return ()) retry
>   client <- accept socket
>   atomically (putTMVar chan client)
> 
> -- This demonstrates how to use acceptInto to spawn client thread
> -- running "receiver".  It ends when checker commits instead of using
> -- retry.
> acceptUntil socket receiver childrenList checker = do
>   counter <- newIORef (0::Int) -- who cares if it rolls over?
>   chan <- atomically (newEmptyTMVar)
>   (mv,tid) <- fork (acceptInto socket chan)
>   let loop = atomically (fmap Left checker `orElse` fmap Right (takeTMVar chan))
>              >>= either (const (return ()))    (\client -> spawn client >> loop)
>       spawn client@(handle,_,_) = do
>         cInfo <- fork (finally (receiver client) (hClose handle))
>         count <- readIORef counter
>         writeIORef counter $! (succ count)
>         modifyMVar_ childrenList $ \kids -> fmap (cInfo:) $
>           if count `mod` 10 == 0  -- 10 is arbitrary frequency for cleaning list
>             then return kids
>             else filterM (isEmptyMVar . fst) kids
>       end = do
>         killThread tid
>         readMVar mv
>         atomically (tryTakeTMVar chan) >>= maybe (return ()) spawn
>   finally (handle (\e -> throwTo tid e >> throw e) loop) end
> 
> exampleReceiver (handle,_,_) = do
>   hPutStrLn handle "Hello."
>   hPutStrLn handle "Goodbye."
> 
> retry'until'true tv = (readTVar tv >>= cond (return ()) retry)



More information about the Haskell mailing list