@@ -1,7 +1,4 @@
2{-# LANGUAGE DeriveDataTypeable #-}
1{-# LANGUAGE DeriveDataTypeable #-}
3−{-# LANGUAGE MagicHash #-}
4−{-# LANGUAGE UnboxedTuples #-} -- for GHC 7.4 or earlier
6module Network.Wai.Handler.Warp.Timeout (
3module Network.Wai.Handler.Warp.Timeout (
7 -- ** Types
4 -- ** Types
@@ -11,6 +8,7 @@ module Network.Wai.Handler.Warp.Timeout (
11 -- ** Manager
8 -- ** Manager
12 , initialize
9 , initialize
13 , stopManager
10 , stopManager
14 , withManager
12 , withManager
15 -- ** Registration
13 -- ** Registration
16 , register
14 , register
@@ -24,25 +22,12 @@ module Network.Wai.Handler.Warp.Timeout (
24 , TimeoutThread (..)
22 , TimeoutThread (..)
27−#ifndef MIN_VERSION_base
28−#define MIN_VERSION_base(x,y,z) 1
31−#if MIN_VERSION_base(4,6,0)
32−import Control.Concurrent (mkWeakThreadId, ThreadId)
34−import GHC.Conc (ThreadId(..))
35−import GHC.Exts (mkWeak#)
36−import GHC.IO (IO (IO))
38import Control.Concurrent (myThreadId)
25import Control.Concurrent (myThreadId)
39import qualified Control.Exception as E
26import qualified Control.Exception as E
40−import GHC.Weak (Weak (..))
27+import Control.Reaper
28+import Data.Typeable (Typeable)
41import Network.Wai.Handler.Warp.IORef (IORef)
29import Network.Wai.Handler.Warp.IORef (IORef)
42import qualified Network.Wai.Handler.Warp.IORef as I
30import qualified Network.Wai.Handler.Warp.IORef as I
43−import System.Mem.Weak (deRefWeak)
44−import Data.Typeable (Typeable)
47----------------------------------------------------------------
32----------------------------------------------------------------
@@ -53,7 +38,7 @@ type Manager = Reaper [Handle] Handle
53type TimeoutAction = IO ()
38type TimeoutAction = IO ()
55-- | A handle used by 'Manager'
40-- | A handle used by 'Manager'
56−data Handle = Handle TimeoutAction (IORef State)
41+data Handle = Handle !(IORef TimeoutAction) !(IORef State)
58data State = Active -- Manager turns it to Inactive.
43data State = Active -- Manager turns it to Inactive.
59 | Inactive -- Manager removes it with timeout action.
44 | Inactive -- Manager removes it with timeout action.
@@ -70,10 +55,11 @@ initialize timeout = mkReaper defaultReaperSettings
70 , reaperDelay = timeout
55 , reaperDelay = timeout
73− prune m@(Handle onTimeout iactive) = do
58+ prune m@(Handle actionRef stateRef) = do
74− state <- I.atomicModifyIORef' iactive (\x -> (inactivate x, x))
59+ state <- I.atomicModifyIORef' stateRef (\x -> (inactivate x, x))
75 case state of
60 case state of
76 Inactive -> do
61 Inactive -> do
62+ onTimeout <- I.readIORef actionRef
77 onTimeout `E.catch` ignoreAll
63 onTimeout `E.catch` ignoreAll
78 return Nothing
64 return Nothing
79 Canceled -> return Nothing
65 Canceled -> return Nothing
@@ -84,38 +70,46 @@ initialize timeout = mkReaper defaultReaperSettings
85----------------------------------------------------------------
71----------------------------------------------------------------
87−-- | Stopping timeout manager.
73+-- | Stopping timeout manager with onTimeout fired.
88stopManager :: Manager -> IO ()
74stopManager :: Manager -> IO ()
89stopManager mgr = E.mask_ (reaperStop mgr >>= mapM_ fire)
75stopManager mgr = E.mask_ (reaperStop mgr >>= mapM_ fire)
77+ fire (Handle actionRef _) = do
78+ onTimeout <- I.readIORef actionRef
91− fire (Handle onTimeout _) = onTimeout `E.catch` ignoreAll
79+ onTimeout `E.catch` ignoreAll
93ignoreAll :: E.SomeException -> IO ()
81ignoreAll :: E.SomeException -> IO ()
94ignoreAll _ = return ()
82ignoreAll _ = return ()
84+-- | Killing timeout manager immediately without firing onTimeout.
85+killManager :: Manager -> IO ()
86+killManager mgr = E.mask_ (reaperStop mgr >>= mapM_ fire)
88+ fire (Handle actionRef _) = do
89+ onTimeout <- I.readIORef actionRef
90+ onTimeout `E.catch` ignoreAll
96----------------------------------------------------------------
92----------------------------------------------------------------
98-- | Registering a timeout action.
94-- | Registering a timeout action.
99register :: Manager -> TimeoutAction -> IO Handle
95register :: Manager -> TimeoutAction -> IO Handle
100register mgr onTimeout = do
96register mgr onTimeout = do
97+ actionRef <- I.newIORef onTimeout
101− iactive <- I.newIORef Active
98+ stateRef <- I.newIORef Active
102− let h = Handle onTimeout iactive
99+ let h = Handle actionRef stateRef
103 reaperAdd mgr h
100 reaperAdd mgr h
106-- | Registering a timeout action of killing this thread.
103-- | Registering a timeout action of killing this thread.
107registerKillThread :: Manager -> IO Handle
104registerKillThread :: Manager -> IO Handle
108registerKillThread m = do
105registerKillThread m = do
106+ -- If we hold ThreadId, the stack and data of the thread is leaked.
107+ -- If we hold Weak ThreadId, the stack is released. However, its
108+ -- data is still leaked probably because of a bug of GHC.
109+ -- So, let's just use ThreadId and release ThreadId by
110+ -- overriding the timeout action by "cancel".
109− wtid <- myThreadId >>= mkWeakThreadId
111+ tid <- myThreadId
110− register m $ killIfExist wtid
112−-- If ThreadId is hold referred by a strong reference,
113−-- it leaks even after the thread is killed.
114−-- So, let's use a weak reference so that CG can throw ThreadId away.
115−-- deRefWeak checks if ThreadId referenced by the weak reference
116−-- exists. If exists, it means that the thread is alive.
117−killIfExist :: Weak ThreadId -> TimeoutAction
118−killIfExist wtid = deRefWeak wtid >>= maybe (return ()) (`E.throwTo` TimeoutThread)
112+ register m $ E.throwTo tid TimeoutThread
120data TimeoutThread = TimeoutThread
114data TimeoutThread = TimeoutThread
121 deriving Typeable
115 deriving Typeable
@@ -123,29 +117,24 @@ instance E.Exception TimeoutThread
123instance Show TimeoutThread where
117instance Show TimeoutThread where
124 show TimeoutThread = "Thread killed by Warp's timeout reaper"
118 show TimeoutThread = "Thread killed by Warp's timeout reaper"
126−#if !MIN_VERSION_base(4,6,0)
127−mkWeakThreadId :: ThreadId -> IO (Weak ThreadId)
128−mkWeakThreadId t@(ThreadId t#) = IO $ \s ->
129− case mkWeak# t# t Nothing s of
130− (# s1, w #) -> (# s1, Weak w #)
133----------------------------------------------------------------
120----------------------------------------------------------------
135-- | Setting the state to active.
122-- | Setting the state to active.
136-- 'Manager' turns active to inactive repeatedly.
123-- 'Manager' turns active to inactive repeatedly.
137tickle :: Handle -> IO ()
124tickle :: Handle -> IO ()
138−tickle (Handle _ iactive) = I.writeIORef iactive Active
125+tickle (Handle _ stateRef) = I.writeIORef stateRef Active
140-- | Setting the state to canceled.
127-- | Setting the state to canceled.
141-- 'Manager' eventually removes this without timeout action.
128-- 'Manager' eventually removes this without timeout action.
142cancel :: Handle -> IO ()
129cancel :: Handle -> IO ()
143−cancel (Handle _ iactive) = I.writeIORef iactive Canceled
130+cancel (Handle actionRef stateRef) = do
131+ I.writeIORef actionRef (return ()) -- ensuring to release ThreadId
132+ I.writeIORef stateRef Canceled
145-- | Setting the state to paused.
134-- | Setting the state to paused.
146-- 'Manager' does not change the value.
135-- 'Manager' does not change the value.
147pause :: Handle -> IO ()
136pause :: Handle -> IO ()
148−pause (Handle _ iactive) = I.writeIORef iactive Paused
137+pause (Handle _ stateRef) = I.writeIORef stateRef Paused
150-- | Setting the paused state to active.
139-- | Setting the paused state to active.
151-- This is an alias to 'tickle'.
140-- This is an alias to 'tickle'.