never executed always true always false
1 {-# LANGUAGE DeriveDataTypeable #-}
2 {-# OPTIONS_GHC -funbox-strict-fields #-}
3 module Distribution.Client.Compat.Semaphore
4 ( QSem
5 , newQSem
6 , waitQSem
7 , signalQSem
8 ) where
9
10 import Prelude (IO, return, Eq (..), Int, Bool (..), ($), ($!), Num (..), flip)
11
12 import Control.Concurrent.STM (TVar, atomically, newTVar, readTVar, retry,
13 writeTVar)
14 import Control.Exception (mask_, onException)
15 import Control.Monad (join, unless)
16 import Data.Typeable (Typeable)
17 import Data.List.NonEmpty (NonEmpty (..))
18 import qualified Data.List.NonEmpty as NE
19
20 -- | 'QSem' is a quantity semaphore in which the resource is aqcuired
21 -- and released in units of one. It provides guaranteed FIFO ordering
22 -- for satisfying blocked `waitQSem` calls.
23 --
24 data QSem = QSem !(TVar Int) !(TVar [TVar Bool]) !(TVar [TVar Bool])
25 deriving (Eq, Typeable)
26
27 newQSem :: Int -> IO QSem
28 newQSem i = atomically $ do
29 q <- newTVar i
30 b1 <- newTVar []
31 b2 <- newTVar []
32 return (QSem q b1 b2)
33
34 waitQSem :: QSem -> IO ()
35 waitQSem s@(QSem q _b1 b2) =
36 mask_ $ join $ atomically $ do
37 -- join, because if we need to block, we have to add a TVar to
38 -- the block queue.
39 -- mask_, because we need a chance to set up an exception handler
40 -- after the join returns.
41 v <- readTVar q
42 if v == 0
43 then do b <- newTVar False
44 ys <- readTVar b2
45 writeTVar b2 (b:ys)
46 return (wait b)
47 else do writeTVar q $! v - 1
48 return (return ())
49 where
50 --
51 -- very careful here: if we receive an exception, then we need to
52 -- (a) write True into the TVar, so that another signalQSem doesn't
53 -- try to wake up this thread, and
54 -- (b) if the TVar is *already* True, then we need to do another
55 -- signalQSem to avoid losing a unit of the resource.
56 --
57 -- The 'wake' function does both (a) and (b), so we can just call
58 -- it here.
59 --
60 wait t =
61 flip onException (wake s t) $
62 atomically $ do
63 b <- readTVar t
64 unless b retry
65
66
67 wake :: QSem -> TVar Bool -> IO ()
68 wake s x = join $ atomically $ do
69 b <- readTVar x
70 if b then return (signalQSem s)
71 else do writeTVar x True
72 return (return ())
73
74 {-
75 property we want:
76
77 bracket waitQSem (\_ -> signalQSem) (\_ -> ...)
78
79 never loses a unit of the resource.
80 -}
81
82 signalQSem :: QSem -> IO ()
83 signalQSem s@(QSem q b1 b2) =
84 mask_ $ join $ atomically $ do
85 -- join, so we don't force the reverse inside the txn
86 -- mask_ is needed so we don't lose a wakeup
87 v <- readTVar q
88 if v /= 0
89 then do writeTVar q $! v + 1
90 return (return ())
91 else do xs <- readTVar b1
92 checkwake1 xs
93 where
94 checkwake1 [] = do
95 ys <- readTVar b2
96 checkwake2 ys
97 checkwake1 (x:xs) = do
98 writeTVar b1 xs
99 return (wake s x)
100
101 checkwake2 [] = do
102 writeTVar q 1
103 return (return ())
104 checkwake2 (y:ys) = do
105 let (z:|zs) = NE.reverse (y:|ys)
106 writeTVar b1 zs
107 writeTVar b2 []
108 return (wake s z)