on master help sign in
Merging the fix for "Weak ThreadId still leaks threads" (#488)
commit0cd3664945feef8f04398c44e88323dc2ce9ae87 authorKazu Yamamoto <kazu@iij.ad.jp> dateWed 16 Dec 2015, 12:09 (11 years ago) parentaabf2d49ab44cf
2 files changed+75−67
auto-update/Control/Reaper.hs+41−22 warp/Network/Wai/Handler/Warp/Timeout.hs+34−45
auto-update/Control/Reaper.hs+41−22
@@ -1,4 +1,5 @@
11{-# LANGUAGE RecordWildCards #-}
2+{-# LANGUAGE BangPatterns #-}
23
34-- | This module provides the ability to create reapers: dedicated cleanup
45-- threads. These threads will automatically spawn and die based on the
@@ -22,10 +23,9 @@ module Control.Reaper (
2223 ) where
2324
2425import Control.AutoUpdate.Util (atomicModifyIORef')
25−import Control.Concurrent (forkIO, threadDelay)
26+import Control.Concurrent (forkIO, threadDelay, killThread, ThreadId)
2627import Control.Exception (mask_)
27−import Control.Monad (join, void)
28−import Data.IORef (IORef, newIORef, readIORef)
28+import Data.IORef (IORef, newIORef, readIORef, writeIORef)
2929
3030-- | Settings for creating a reaper. This type has two parameters:
3131-- @workload@ gives the entire workload, whereas @item@ gives an
@@ -96,6 +96,8 @@ data Reaper workload item = Reaper {
9696 -- | Stopping the reaper thread if exists.
9797 -- The current workload is returned.
9898 , reaperStop :: IO workload
99+ -- | Killing the reaper thread immediately if exists.
100+ , reaperKill :: IO ()
99101 }
100102
101103-- | State of reaper.
@@ -110,10 +112,12 @@ data State workload = NoReaper -- ^ No reaper thread
110112mkReaper :: ReaperSettings workload item -> IO (Reaper workload item)
111113mkReaper settings@ReaperSettings{..} = do
112114 stateRef <- newIORef NoReaper
115+ tidRef <- newIORef Nothing
113116 return Reaper {
114− reaperAdd = update settings stateRef
117+ reaperAdd = add settings stateRef tidRef
115118 , reaperRead = readRef stateRef
116119 , reaperStop = stop stateRef
120+ , reaperKill = kill tidRef
117121 }
118122 where
119123 readRef stateRef = do
@@ -125,31 +129,46 @@ mkReaper settings@ReaperSettings{..} = do
125129 case mx of
126130 NoReaper -> (NoReaper, reaperEmpty)
127131 Workload x -> (Workload reaperEmpty, x)
132+ kill tidRef = do
133+ mtid <- readIORef tidRef
134+ case mtid of
135+ Nothing -> return ()
136+ Just tid -> killThread tid
128137
129−update :: ReaperSettings workload item -> IORef (State workload) -> item
130− -> IO ()
131−update settings@ReaperSettings{..} stateRef item =
132− mask_ $ join $ atomicModifyIORef' stateRef cons
138+add :: ReaperSettings workload item
139+ -> IORef (State workload) -> IORef (Maybe ThreadId)
140+ -> item -> IO ()
141+add settings@ReaperSettings{..} stateRef tidRef item =
142+ mask_ $ do
143+ next <- atomicModifyIORef' stateRef cons
144+ next
133145 where
134− cons NoReaper = (Workload $ reaperCons item reaperEmpty
135− ,spawn settings stateRef)
136− cons (Workload wl) = (Workload $ reaperCons item wl
137− ,return ())
146+ cons NoReaper = let !wl = reaperCons item reaperEmpty
147+ in (Workload wl, spawn settings stateRef tidRef)
148+ cons (Workload wl) = let wl' = reaperCons item wl
149+ in (Workload wl', return ())
138150
139−spawn :: ReaperSettings workload item -> IORef (State workload) -> IO ()
140−spawn settings stateRef = void . forkIO $ reaper settings stateRef
151+spawn :: ReaperSettings workload item
152+ -> IORef (State workload) -> IORef (Maybe ThreadId)
153+ -> IO ()
154+spawn settings stateRef tidRef = do
155+ tid <- forkIO $ reaper settings stateRef tidRef
156+ writeIORef tidRef $ Just tid
141157
142−reaper :: ReaperSettings workload item -> IORef (State workload) -> IO ()
143−reaper settings@ReaperSettings{..} stateRef = do
158+reaper :: ReaperSettings workload item
159+ -> IORef (State workload) -> IORef (Maybe ThreadId)
160+ -> IO ()
161+reaper settings@ReaperSettings{..} stateRef tidRef = do
144162 threadDelay reaperDelay
145163 -- Getting the current jobs. Push an empty job to the reference.
146164 wl <- atomicModifyIORef' stateRef swapWithEmpty
147165 -- Do the jobs. A function to merge the left jobs and
148166 -- new jobs is returned.
149− merge <- reaperAction wl
167+ !merge <- reaperAction wl
150168 -- Merging the left jobs and new jobs.
151169 -- If there is no jobs, this thread finishes.
152− join $ atomicModifyIORef' stateRef (check merge)
170+ next <- atomicModifyIORef' stateRef (check merge)
171+ next
153172 where
154173 swapWithEmpty NoReaper = error "Control.Reaper.reaper: unexpected NoReaper (1)"
155174 swapWithEmpty (Workload wl) = (Workload reaperEmpty, wl)
@@ -157,9 +176,9 @@ reaper settings@ReaperSettings{..} stateRef = do
157176 check _ NoReaper = error "Control.Reaper.reaper: unexpected NoReaper (2)"
158177 check merge (Workload wl)
159178 -- If there is no job, reaper is terminated.
160− | reaperNull wl' = (NoReaper, return ())
179+ | reaperNull wl' = (NoReaper, writeIORef tidRef Nothing)
161180 -- If there are jobs, carry them out.
162− | otherwise = (Workload wl', reaper settings stateRef)
181+ | otherwise = (Workload wl', reaper settings stateRef tidRef)
163182 where
164183 wl' = merge wl
165184
@@ -175,8 +194,8 @@ mkListAction :: (item -> IO (Maybe item'))
175194mkListAction f =
176195 go id
177196 where
178− go front [] = return front
179− go front (x:xs) = do
197+ go !front [] = return front
198+ go !front (x:xs) = do
180199 my <- f x
181200 let front' =
182201 case my of
warp/Network/Wai/Handler/Warp/Timeout.hs+34−45
@@ -1,7 +1,4 @@
1−{-# LANGUAGE CPP #-}
21{-# LANGUAGE DeriveDataTypeable #-}
3−{-# LANGUAGE MagicHash #-}
4−{-# LANGUAGE UnboxedTuples #-} -- for GHC 7.4 or earlier
52
63module Network.Wai.Handler.Warp.Timeout (
74 -- ** Types
@@ -11,6 +8,7 @@ module Network.Wai.Handler.Warp.Timeout (
118 -- ** Manager
129 , initialize
1310 , stopManager
11+ , killManager
1412 , withManager
1513 -- ** Registration
1614 , register
@@ -24,25 +22,12 @@ module Network.Wai.Handler.Warp.Timeout (
2422 , TimeoutThread (..)
2523 ) where
2624
27−#ifndef MIN_VERSION_base
28−#define MIN_VERSION_base(x,y,z) 1
29−#endif
30−
31−#if MIN_VERSION_base(4,6,0)
32−import Control.Concurrent (mkWeakThreadId, ThreadId)
33−#else
34−import GHC.Conc (ThreadId(..))
35−import GHC.Exts (mkWeak#)
36−import GHC.IO (IO (IO))
37−#endif
3825import Control.Concurrent (myThreadId)
3926import qualified Control.Exception as E
40−import GHC.Weak (Weak (..))
27+import Control.Reaper
28+import Data.Typeable (Typeable)
4129import Network.Wai.Handler.Warp.IORef (IORef)
4230import qualified Network.Wai.Handler.Warp.IORef as I
43−import System.Mem.Weak (deRefWeak)
44−import Data.Typeable (Typeable)
45−import Control.Reaper
4631
4732----------------------------------------------------------------
4833
@@ -53,7 +38,7 @@ type Manager = Reaper [Handle] Handle
5338type TimeoutAction = IO ()
5439
5540-- | A handle used by 'Manager'
56−data Handle = Handle TimeoutAction (IORef State)
41+data Handle = Handle !(IORef TimeoutAction) !(IORef State)
5742
5843data State = Active -- Manager turns it to Inactive.
5944 | Inactive -- Manager removes it with timeout action.
@@ -70,10 +55,11 @@ initialize timeout = mkReaper defaultReaperSettings
7055 , reaperDelay = timeout
7156 }
7257 where
73− prune m@(Handle onTimeout iactive) = do
74− state <- I.atomicModifyIORef' iactive (\x -> (inactivate x, x))
58+ prune m@(Handle actionRef stateRef) = do
59+ state <- I.atomicModifyIORef' stateRef (\x -> (inactivate x, x))
7560 case state of
7661 Inactive -> do
62+ onTimeout <- I.readIORef actionRef
7763 onTimeout `E.catch` ignoreAll
7864 return Nothing
7965 Canceled -> return Nothing
@@ -84,38 +70,46 @@ initialize timeout = mkReaper defaultReaperSettings
8470
8571----------------------------------------------------------------
8672
87−-- | Stopping timeout manager.
73+-- | Stopping timeout manager with onTimeout fired.
8874stopManager :: Manager -> IO ()
8975stopManager mgr = E.mask_ (reaperStop mgr >>= mapM_ fire)
9076 where
91− fire (Handle onTimeout _) = onTimeout `E.catch` ignoreAll
77+ fire (Handle actionRef _) = do
78+ onTimeout <- I.readIORef actionRef
79+ onTimeout `E.catch` ignoreAll
9280
9381ignoreAll :: E.SomeException -> IO ()
9482ignoreAll _ = return ()
9583
84+-- | Killing timeout manager immediately without firing onTimeout.
85+killManager :: Manager -> IO ()
86+killManager mgr = E.mask_ (reaperStop mgr >>= mapM_ fire)
87+ where
88+ fire (Handle actionRef _) = do
89+ onTimeout <- I.readIORef actionRef
90+ onTimeout `E.catch` ignoreAll
91+
9692----------------------------------------------------------------
9793
9894-- | Registering a timeout action.
9995register :: Manager -> TimeoutAction -> IO Handle
10096register mgr onTimeout = do
101− iactive <- I.newIORef Active
102− let h = Handle onTimeout iactive
97+ actionRef <- I.newIORef onTimeout
98+ stateRef <- I.newIORef Active
99+ let h = Handle actionRef stateRef
103100 reaperAdd mgr h
104101 return h
105102
106103-- | Registering a timeout action of killing this thread.
107104registerKillThread :: Manager -> IO Handle
108105registerKillThread m = do
109− wtid <- myThreadId >>= mkWeakThreadId
110− register m $ killIfExist wtid
111−
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)
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".
111+ tid <- myThreadId
112+ register m $ E.throwTo tid TimeoutThread
119113
120114data TimeoutThread = TimeoutThread
121115 deriving Typeable
@@ -123,29 +117,24 @@ instance E.Exception TimeoutThread
123117instance Show TimeoutThread where
124118 show TimeoutThread = "Thread killed by Warp's timeout reaper"
125119
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 #)
131−#endif
132−
133120----------------------------------------------------------------
134121
135122-- | Setting the state to active.
136123-- 'Manager' turns active to inactive repeatedly.
137124tickle :: Handle -> IO ()
138−tickle (Handle _ iactive) = I.writeIORef iactive Active
125+tickle (Handle _ stateRef) = I.writeIORef stateRef Active
139126
140127-- | Setting the state to canceled.
141128-- 'Manager' eventually removes this without timeout action.
142129cancel :: 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
144133
145134-- | Setting the state to paused.
146135-- 'Manager' does not change the value.
147136pause :: Handle -> IO ()
148−pause (Handle _ iactive) = I.writeIORef iactive Paused
137+pause (Handle _ stateRef) = I.writeIORef stateRef Paused
149138
150139-- | Setting the paused state to active.
151140-- This is an alias to 'tickle'.