WrapConc

From HaskellWiki
Revision as of 11:51, 25 January 2007 by ChrisKuklewicz (talk | contribs)
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.
-- By Chris Kuklewicz <haskell at list dot mightyreason dot com>
--
-- This WrapConc module provides modified version of forkIO which have
-- been combined with block from Control.Exception to provide stronger
-- guarantees as well as notification when a thread has finished.
module WrapConc(finally',bracket',fork,forkB,forkC,forkF
               ,ThreadFlag,isSetThreadFlag,waitThreadFlag,waitKillThread,abandonThread) where

import Prelude hiding (catch)
import Control.Concurrent(forkIO,ThreadId,killThread,yield)
import Control.Concurrent.MVar(MVar,newEmptyMVar,tryPutMVar,takeMVar,putMVar,isEmptyMVar,readMVar)
import Control.Exception(Exception,catch,block,throw)
import Control.Monad (liftM)

-- Some code copied from http://darcs.haskell.org/packages/base/Control/Exception.hs

-- This is like finally but leaves your code to execute in 'block'
-- instead of 'unblock'
finally' :: IO a -> IO b -> IO a
finally' a sequel = block $ do
  r <- catch a (\e -> sequel >> throw e)
  sequel
  return r

-- This is like bracket but leaves your code to execute in 'block'
-- instead of 'unblock'
bracket' :: IO a         -- ^ computation to run first (\"acquire resource\")
         -> (a -> IO b)  -- ^ computation to run last (\"release resource\")
         -> (a -> IO c)  -- ^ computation to run in-between
         -> IO c         -- returns the value from the in-between computation
bracket' before after thing = block $ do
  a <- before 
  r <- catch (thing a) (\e -> after a >> throw e )
  after a
  return r

-- ThreadFlag is a limited newtype of an MVar
newtype ThreadFlag = ThreadFlag (MVar ())

-- Intially unset, the ThreadFlag will become set when the thread is finished
isSetThreadFlag :: ThreadFlag -> IO Bool
isSetThreadFlag (ThreadFlag m) = liftM not (isEmptyMVar m)

-- If unset then block until set.  This will wait for the thread to have finished
waitThreadFlag :: ThreadFlag -> IO ()
waitThreadFlag (ThreadFlag m) = readMVar m

-- This does a background killThread and then waits for the passed
-- ThreadFlag to signal that it is done via the ThreadFlag
waitKillThread :: (ThreadFlag,ThreadId) -> IO ()
waitKillThread (flag,tid) = forkIO (killThread tid) >> yield >> waitThreadFlag flag

-- This does two things:
--   First it does a background killThread.
--   Second it immediately ensure the ThreadFlag is set
--
-- The main effect will be to release any thread waiting on (or that
-- will wait on) the ThreadFlag.
--
-- This should only be needed in highly unusual situations where
-- waiting for thread to properly exit and set the ThreadFlag is
-- impossible.  It may be useful if the thread has hung or the
-- application needs to perform an "emergency shutdown".
abandonThread (ThreadFlag m,tid) = forkIO (killThread tid) >> tryPutMVar m ()

-- Safer forkIO. If this returns then you are not guaranteed that your
-- operations run but you are guranteed that the thread has started
-- and that the ThreadFlag will be set when it finishes.  Your code
-- starts to execute in the scope of a 'block' function.
--
-- It is possible to interrupt the fork and to have still succeeded in
-- starting the thread.
fork :: IO a -> IO (ThreadFlag,ThreadId)
fork ioA = block $ do
  a <- newEmptyMVar
  b <- newEmptyMVar
  tid <- forkIO (finally' (putMVar a () >> yield >> ioA)
                          (tryPutMVar b ()) >> return ())
  yield
  takeMVar a
  return (ThreadFlag b, tid)

-- fork merged with bracket'. If this returns then you are not
-- guaranteed that your operations run but you are guranteed that the
-- thread has started and that the ThreadFlag will be set when it
-- finishes.  Your code starts to execute in the scope of a 'block'
-- function.
--
-- The is equivalent to (fork (bracket' ioA ioAB ioAC))
--
-- It is possible to interrupt the forkB and to have still succeeded in
-- starting the thread.
forkB :: IO a -> (a -> IO b) -> (a -> IO c) -> IO (ThreadFlag,ThreadId)
forkB ioA ioAB ioAC = block $ do
  a <- newEmptyMVar
  b <- newEmptyMVar
  tid <- forkIO (finally' (putMVar a () >> yield >> bracket' ioA ioAB ioAC)
                          (tryPutMVar b ()) >> return ())
  yield
  takeMVar a
  return (ThreadFlag b, tid)

-- fork merged with catch. If this returns then you are not guarenteed
-- that your operation is running, but you are guranteed that the
-- handler will run if your code received or raises an exception. And
-- the ThreadFlag will be set when the thread finishes.  Your code
-- starts to execute in the scope of a 'block' function.
--
-- This is a stronger guarantee than (fork (catch ioA ioEA))
--
-- It is possible to interrupt the forkC and to have still succeeded in
-- starting the thread.
forkC :: IO a -> (Exception -> IO a) -> IO (ThreadFlag,ThreadId)
forkC ioA ioEA = block $ do
  a <- newEmptyMVar
  b <- newEmptyMVar
  tid <- forkIO (finally' (catch (putMVar a () >> yield >> ioA) ioEA)
                          (tryPutMVar b ()) >> return ())
  yield
  takeMVar a
  return (ThreadFlag b, tid)

-- fork merged with finally'. If this returns then you are guaranteed
-- that your second command will be run even if your first command
-- receives or raises an exception. And the ThreadFlag will be set
-- when the thread finishes.  Your code starts to execute in the
-- scope of a 'block' function.
--
-- This is a stronger guarantee than (fork (finally' ioA ioB))
--
-- It is possible to interrupt the forkF and to have still succeeded in
-- starting the thread.
forkF :: IO a -> IO b -> IO (ThreadFlag,ThreadId)
forkF ioA ioB = block $ do
  a <- newEmptyMVar
  b <- newEmptyMVar
  tid <- forkIO (finally' (finally' (putMVar a () >> yield >> ioA) ioB)
                          (tryPutMVar b ()) >> return ())
  yield
  takeMVar a
  return (ThreadFlag b, tid)