77module Data.Pool.Internal where
88
99import Control.Concurrent
10+ import Control.Concurrent.STM
1011import Control.Exception
1112import Control.Monad
1213import Data.Hashable (hash )
1314import Data.IORef
1415import qualified Data.List as L
1516import Data.Primitive.SmallArray
16- import GHC.Clock
17+ import GHC.Clock (getMonotonicTime )
18+ import GHC.Conc (unsafeIOToSTM )
1719
1820-- | Striped resource pool based on "Control.Concurrent.QSem".
1921data Pool a = Pool
@@ -25,12 +27,13 @@ data Pool a = Pool
2527-- | A single, local pool.
2628data LocalPool a = LocalPool
2729 { stripeId :: ! Int
28- , stripeVar :: ! (MVar (Stripe a ))
30+ , stripeVar :: ! (TVar (Stripe a ))
2931 , cleanerRef :: ! (IORef () )
3032 }
3133
3234-- | Stripe of a resource pool. If @available@ is 0, the list of threads waiting
33- -- for a resource (each with an associated 'MVar') is @queue ++ reverse queueR@.
35+ -- for a resource (each with an associated 'TMVar') is @queue ++ reverse queueR@
36+ -- to ensure fairness.
3437data Stripe a = Stripe
3538 { available :: ! Int
3639 , cache :: ! [Entry a ]
@@ -44,10 +47,10 @@ data Entry a = Entry
4447 , lastUsed :: ! Double
4548 }
4649
47- -- | A queue of MVarS corresponding to threads waiting for resources.
50+ -- | A queue of TMVarS corresponding to threads waiting for resources.
4851--
4952-- Basically a monomorphic list to save two pointer indirections.
50- data Queue a = Queue ! (MVar (Maybe a )) (Queue a ) | Empty
53+ data Queue a = Queue ! (TMVar (Maybe a )) (Queue a ) | Empty
5154
5255-- | Configuration of a 'Pool'.
5356data PoolConfig a = PoolConfig
@@ -129,7 +132,7 @@ newPool pc = do
129132 pools <- fmap (smallArrayFromListN numStripes) . forM [1 .. numStripes] $ \ n -> do
130133 ref <- newIORef ()
131134 stripe <-
132- newMVar
135+ newTVarIO
133136 Stripe
134137 { available = poolMaxResources pc `quotCeil` numStripes
135138 , cache = []
@@ -175,21 +178,18 @@ newPool pc = do
175178-- Note that this will ignore any exceptions in the destroy function.
176179destroyResource :: Pool a -> LocalPool a -> a -> IO ()
177180destroyResource pool lp a = do
178- uninterruptibleMask_ $ do
179- -- Note [signal uninterruptible]
180- stripe <- takeMVar (stripeVar lp)
181+ atomically $ do
182+ stripe <- readTVar (stripeVar lp)
181183 newStripe <- signal stripe Nothing
182- putMVar (stripeVar lp) newStripe
183- void . try @ SomeException $ freeResource (poolConfig pool) a
184+ writeTVar (stripeVar lp) $! newStripe
185+ void . try @ SomeException $ freeResource (poolConfig pool) a
184186
185187-- | Return a resource to the given 'LocalPool'.
186188putResource :: LocalPool a -> a -> IO ()
187- putResource lp a = do
188- uninterruptibleMask_ $ do
189- -- Note [signal uninterruptible]
190- stripe <- takeMVar (stripeVar lp)
191- newStripe <- signal stripe (Just a)
192- putMVar (stripeVar lp) newStripe
189+ putResource lp a = atomically $ do
190+ stripe <- readTVar (stripeVar lp)
191+ newStripe <- signal stripe (Just a)
192+ writeTVar (stripeVar lp) $! newStripe
193193
194194-- | Destroy all resources in all stripes in the pool.
195195--
@@ -235,89 +235,78 @@ getLocalPool pools = do
235235 where
236236 stripes = sizeofSmallArray pools
237237
238- -- | Wait for the resource to be put into a given 'MVar '.
239- waitForResource :: MVar (Stripe a ) -> MVar (Maybe a ) -> IO (Maybe a )
240- waitForResource mstripe q = takeMVar q `onException` cleanup
238+ -- | Wait for the resource to be put into a given 'TMVar '.
239+ waitForResource :: TVar (Stripe a ) -> TMVar (Maybe a ) -> IO (Maybe a )
240+ waitForResource mstripe q = atomically (takeTMVar q) `onException` cleanup
241241 where
242- cleanup = uninterruptibleMask_ $ do
243- -- Note [signal uninterruptible]
244- stripe <- takeMVar mstripe
242+ cleanup = atomically $ do
243+ stripe <- readTVar mstripe
245244 newStripe <-
246- tryTakeMVar q >>= \ case
245+ tryTakeTMVar q >>= \ case
247246 Just ma -> do
248247 -- Between entering the exception handler and taking ownership of
249248 -- the stripe we got the resource we wanted. We don't need it
250249 -- anymore though, so pass it to someone else.
251250 signal stripe ma
252251 Nothing -> do
253- -- If we're still waiting, fill up the MVar with an undefined value
254- -- so that 'signal' can discard our MVar from the queue.
255- putMVar q $ error " unreachable"
252+ -- If we're still waiting, fill up the TMVar with an undefined value
253+ -- so that 'signal' can discard our TMVar from the queue.
254+ writeTMVar q $ error " unreachable"
256255 pure stripe
257- putMVar mstripe newStripe
256+ writeTVar mstripe $! newStripe
258257
259258-- | If an exception is received while a resource is being created, restore the
260259-- original size of the stripe.
261- restoreSize :: MVar (Stripe a ) -> IO ()
262- restoreSize mstripe = uninterruptibleMask_ $ do
263- -- 'uninterruptibleMask_' is used since 'takeMVar' might block.
264- stripe <- takeMVar mstripe
265- putMVar mstripe $! stripe {available = available stripe + 1 }
260+ restoreSize :: TVar (Stripe a ) -> IO ()
261+ restoreSize mstripe = atomically $ do
262+ modifyTVar' mstripe $ \ stripe -> stripe {available = available stripe + 1 }
266263
267264-- | Free resource entries in the stripes that fulfil a given condition.
268265cleanStripe
269266 :: (Entry a -> Bool )
270267 -> (a -> IO () )
271- -> MVar (Stripe a )
268+ -> TVar (Stripe a )
272269 -> IO ()
273- cleanStripe isStale free mstripe = mask $ \ unmask -> do
270+ cleanStripe isStale free mstripe = mask_ $ do
274271 -- Asynchronous exceptions need to be masked here to prevent leaking of
275272 -- 'stale' resources before they're freed.
276- stale <- modifyMVar mstripe $ \ stripe -> unmask $ do
273+ stale <- atomically $ do
274+ stripe <- readTVar mstripe
277275 let (stale, fresh) = L. partition isStale (cache stripe)
278- -- There's no need to update 'available' here because it only tracks
279- -- the number of resources taken from the pool.
280- newStripe = stripe {cache = fresh}
281- newStripe `seq` pure (newStripe, map entry stale)
276+ -- There's no need to update 'available' here because it only tracks
277+ -- the number of resources taken from the pool.
278+ writeTVar mstripe $! stripe {cache = fresh}
279+ pure $ map entry stale
282280 -- We need to ignore exceptions in the 'free' function, otherwise if an
283281 -- exception is thrown half-way, we leak the rest of the resources. Also,
284282 -- asynchronous exceptions need to be hard masked here since freeing a
285283 -- resource might in theory block.
286284 uninterruptibleMask_ . forM_ stale $ try @ SomeException . free
287285
288- -- Note [signal uninterruptible]
289- --
290- -- If we have
291- --
292- -- bracket takeResource putResource (...)
293- --
294- -- and an exception arrives at the putResource, then we must not lose the
295- -- resource. The putResource is masked by bracket, but taking the MVar might
296- -- block, and so it would be interruptible. Hence we need an uninterruptible
297- -- variant of mask here.
298- signal :: Stripe a -> Maybe a -> IO (Stripe a )
286+ signal :: forall a . Stripe a -> Maybe a -> STM (Stripe a )
299287signal stripe ma =
300288 if available stripe == 0
301289 then loop (queue stripe) (queueR stripe)
302290 else do
303291 newCache <- case ma of
304292 Just a -> do
305- now <- getMonotonicTime
293+ now <- unsafeIOToSTM getMonotonicTime
306294 pure $ Entry a now : cache stripe
307295 Nothing -> pure $ cache stripe
308- pure $!
296+ pure
309297 stripe
310298 { available = available stripe + 1
311299 , cache = newCache
312300 }
313301 where
302+ loop :: Queue a -> Queue a -> STM (Stripe a )
314303 loop Empty Empty = do
315304 newCache <- case ma of
316305 Just a -> do
317- now <- getMonotonicTime
306+ now <- unsafeIOToSTM getMonotonicTime
318307 pure [Entry a now]
319308 Nothing -> pure []
320- pure $!
309+ pure
321310 Stripe
322311 { available = 1
323312 , cache = newCache
@@ -326,22 +315,22 @@ signal stripe ma =
326315 }
327316 loop Empty qR = loop (reverseQueue qR) Empty
328317 loop (Queue q qs) qR =
329- tryPutMVar q ma >>= \ case
318+ tryPutTMVar q ma >>= \ case
330319 -- This fails when 'waitForResource' went into the exception handler and
331- -- filled the MVar (with an undefined value) itself. In such case we
320+ -- filled the TMVar (with an undefined value) itself. In such case we
332321 -- simply ignore it.
333322 False -> loop qs qR
334323 True ->
335- pure $!
324+ pure
336325 stripe
337326 { available = 0
338327 , queue = qs
339328 , queueR = qR
340329 }
341330
342- reverseQueue :: Queue a -> Queue a
343- reverseQueue = go Empty
344- where
345- go acc = \ case
346- Empty -> acc
347- Queue x xs -> go (Queue x acc) xs
331+ reverseQueue :: Queue a -> Queue a
332+ reverseQueue = go Empty
333+ where
334+ go acc = \ case
335+ Empty -> acc
336+ Queue x xs -> go (Queue x acc) xs
0 commit comments