module Network.Wai.Handler.Warp.Timeout (
Manager
, Handle
, initialize
, stopManager
, register
, registerKillThread
, tickle
, pause
, resume
, cancel
, withManager
, dummyHandle
) where
import System.Mem.Weak (deRefWeak)
#if MIN_VERSION_base(4,6,0)
import Control.Concurrent (mkWeakThreadId)
#else
import GHC.Weak (Weak (..))
import GHC.Conc.Sync (ThreadId (..))
import GHC.IO (IO (IO))
import GHC.Exts (mkWeak#)
#endif
import Control.Concurrent (forkIO, threadDelay, myThreadId, killThread)
import qualified Control.Exception as E
import Control.Monad (forever, void)
import qualified Data.IORef as I
import System.IO.Unsafe (unsafePerformIO)
import Data.Typeable (Typeable)
newtype Manager = Manager (I.IORef [Handle])
data Handle = Handle (IO ()) (I.IORef State)
dummyHandle :: Handle
dummyHandle = Handle (return ()) (unsafePerformIO $ I.newIORef Active)
data State = Active | Inactive | Paused | Canceled
initialize :: Int -> IO Manager
initialize timeout = do
ref <- I.newIORef []
void . forkIO $ E.handle ignoreStop $ forever $ do
threadDelay timeout
ms <- I.atomicModifyIORef ref (\x -> ([], x))
ms' <- go ms id
I.atomicModifyIORef ref (\x -> (ms' x, ()))
return $ Manager ref
where
ignoreStop TimeoutManagerStopped = return ()
go [] front = return front
go (m@(Handle onTimeout iactive):rest) front = do
state <- I.atomicModifyIORef iactive (\x -> (go' x, x))
case state of
Inactive -> do
onTimeout `E.catch` ignoreAll
go rest front
Canceled -> go rest front
_ -> go rest (front . (:) m)
go' Active = Inactive
go' x = x
data TimeoutManagerStopped = TimeoutManagerStopped
deriving (Show, Typeable)
instance E.Exception TimeoutManagerStopped
stopManager :: Manager -> IO ()
stopManager (Manager ihandles) = E.mask_ $ do
!handles <- I.atomicModifyIORef ihandles $ \h -> (E.throw TimeoutManagerStopped, h)
mapM_ go handles
where
go (Handle onTimeout _) = onTimeout `E.catch` ignoreAll
ignoreAll :: E.SomeException -> IO ()
ignoreAll _ = return ()
register :: Manager -> IO () -> IO Handle
register (Manager ref) onTimeout = do
iactive <- I.newIORef Active
let h = Handle onTimeout iactive
I.atomicModifyIORef ref (\x -> (h : x, ()))
return h
registerKillThread :: Manager -> IO Handle
registerKillThread m = do
wtid <- myThreadId >>= mkWeakThreadId
register m $ deRefWeak wtid >>= maybe (return ()) killThread
#if !MIN_VERSION_base(4,6,0)
mkWeakThreadId :: ThreadId -> IO (Weak ThreadId)
mkWeakThreadId t@(ThreadId t#) = IO $ \s ->
case mkWeak# t# t Nothing s of
(# s1, w #) -> (# s1, Weak w #)
#endif
tickle, pause, resume, cancel :: Handle -> IO ()
tickle (Handle _ iactive) = I.writeIORef iactive Active
pause (Handle _ iactive) = I.writeIORef iactive Paused
resume = tickle
cancel (Handle _ iactive) = I.writeIORef iactive Canceled
withManager :: Int
-> (Manager -> IO a)
-> IO a
withManager timeout f = do
man <- initialize timeout
f man