Revision 15208e95
b/src/Ganeti/Locking/Allocation.hs | ||
---|---|---|
28 | 28 |
, emptyAllocation |
29 | 29 |
, OwnerState(..) |
30 | 30 |
, listLocks |
31 |
, LockRequest(..) |
|
32 |
, requestExclusive |
|
33 |
, requestShared |
|
34 |
, requestRelease |
|
35 |
, updateLocks |
|
31 | 36 |
) where |
32 | 37 |
|
38 |
import Control.Arrow (second, (***)) |
|
39 |
import Control.Monad |
|
40 |
import Data.Foldable (for_) |
|
33 | 41 |
import qualified Data.Map as M |
34 | 42 |
import Data.Maybe (fromMaybe) |
35 | 43 |
import qualified Data.Set as S |
36 | 44 |
|
45 |
import Ganeti.BasicTypes |
|
46 |
|
|
37 | 47 |
{- |
38 | 48 |
|
39 | 49 |
This module is parametric in the type of locks and lock owners. |
... | ... | |
76 | 86 |
, laOwned = M.empty |
77 | 87 |
} |
78 | 88 |
|
79 |
-- | Obtain the set of locks held by a given owner. |
|
80 | 89 |
listLocks :: Ord b => b -> LockAllocation a b -> M.Map a OwnerState |
81 | 90 |
listLocks owner = fromMaybe M.empty . M.lookup owner . laOwned |
91 |
|
|
92 |
-- | Data Type describing a change request on a single lock. |
|
93 |
data LockRequest a = LockRequest { lockAffected :: a |
|
94 |
, lockRequestType :: Maybe OwnerState |
|
95 |
} |
|
96 |
deriving (Eq, Show) |
|
97 |
|
|
98 |
-- | Lock request for an exclusive lock. |
|
99 |
requestExclusive :: a -> LockRequest a |
|
100 |
requestExclusive lock = LockRequest { lockAffected = lock |
|
101 |
, lockRequestType = Just OwnExclusive } |
|
102 |
|
|
103 |
-- | Lock request for a shared lock. |
|
104 |
requestShared :: a -> LockRequest a |
|
105 |
requestShared lock = LockRequest { lockAffected = lock |
|
106 |
, lockRequestType = Just OwnShared } |
|
107 |
|
|
108 |
-- | Request to release a lock. |
|
109 |
requestRelease :: a -> LockRequest a |
|
110 |
requestRelease lock = LockRequest { lockAffected = lock |
|
111 |
, lockRequestType = Nothing } |
|
112 |
|
|
113 |
-- | Internal function to update the state according to a single |
|
114 |
-- lock request, assuming all prerequisites are met. |
|
115 |
updateLock :: (Ord a, Ord b) |
|
116 |
=> b |
|
117 |
-> LockAllocation a b -> LockRequest a -> LockAllocation a b |
|
118 |
updateLock owner state (LockRequest lock (Just OwnExclusive)) = |
|
119 |
let locks' = M.insert lock (Exclusive owner) $ laLocks state |
|
120 |
ownersLocks' = M.insert lock OwnExclusive $ listLocks owner state |
|
121 |
owned' = M.insert owner ownersLocks' $ laOwned state |
|
122 |
in state { laLocks = locks', laOwned = owned' } |
|
123 |
updateLock owner state (LockRequest lock (Just OwnShared)) = |
|
124 |
let ownersLocks' = M.insert lock OwnShared $ listLocks owner state |
|
125 |
owned' = M.insert owner ownersLocks' $ laOwned state |
|
126 |
locks = laLocks state |
|
127 |
lockState' = case M.lookup lock locks of |
|
128 |
Just (Shared s) -> Shared (S.insert owner s) |
|
129 |
_ -> Shared $ S.singleton owner |
|
130 |
locks' = M.insert lock lockState' locks |
|
131 |
in state { laLocks = locks', laOwned = owned' } |
|
132 |
updateLock owner state (LockRequest lock Nothing) = |
|
133 |
let ownersLocks' = M.delete lock $ listLocks owner state |
|
134 |
owned = laOwned state |
|
135 |
owned' = if M.null ownersLocks' |
|
136 |
then M.delete owner owned |
|
137 |
else M.insert owner ownersLocks' owned |
|
138 |
locks = laLocks state |
|
139 |
lockRemoved = M.delete lock locks |
|
140 |
locks' = case M.lookup lock locks of |
|
141 |
Nothing -> locks |
|
142 |
Just (Exclusive x) -> |
|
143 |
if x == owner then lockRemoved else locks |
|
144 |
Just (Shared s) |
|
145 |
-> let s' = S.delete owner s |
|
146 |
in if S.null s' |
|
147 |
then lockRemoved |
|
148 |
else M.insert lock (Shared s') locks |
|
149 |
in state { laLocks = locks', laOwned = owned' } |
|
150 |
|
|
151 |
-- | Update the locks of an owner according to the given request. Return |
|
152 |
-- the pair of the new state and the result of the operation, which is the |
|
153 |
-- the set of owners on which the operation was blocked on. so an empty set is |
|
154 |
-- success, and the state is updated if, and only if, the returned set is emtpy. |
|
155 |
-- In that way, it can be used in atomicModifyIORef. |
|
156 |
updateLocks :: (Ord a, Show a, Ord b) |
|
157 |
=> b |
|
158 |
-> [LockRequest a] |
|
159 |
-> LockAllocation a b -> (LockAllocation a b, Result (S.Set b)) |
|
160 |
updateLocks owner reqs state = genericResult ((,) state . Bad) (second Ok) $ do |
|
161 |
runListHead (return ()) |
|
162 |
(fail . (++) "Inconsitent requests for lock " . show) $ do |
|
163 |
r <- reqs |
|
164 |
r' <- reqs |
|
165 |
guard $ r /= r' |
|
166 |
guard $ lockAffected r == lockAffected r' |
|
167 |
return $ lockAffected r |
|
168 |
let current = listLocks owner state |
|
169 |
unless (M.null current) $ do |
|
170 |
let (highest, _) = M.findMax current |
|
171 |
notHolding = not |
|
172 |
. any (uncurry (==) . ((M.lookup `flip` current) *** Just)) |
|
173 |
orderViolation l = fail $ "Order violation: requesting " ++ show l |
|
174 |
++ " while holding " ++ show highest |
|
175 |
for_ reqs $ \req -> case req of |
|
176 |
LockRequest lock (Just OwnExclusive) |
|
177 |
| lock < highest && notHolding [ (lock, OwnExclusive) ] |
|
178 |
-> orderViolation lock |
|
179 |
LockRequest lock (Just OwnShared) |
|
180 |
| lock < highest && notHolding [ (lock, OwnExclusive) |
|
181 |
, (lock, OwnExclusive)] |
|
182 |
-> orderViolation lock |
|
183 |
_ -> Ok () |
|
184 |
let blockedOn (LockRequest _ Nothing) = S.empty |
|
185 |
blockedOn (LockRequest lock (Just OwnExclusive)) = |
|
186 |
case M.lookup lock (laLocks state) of |
|
187 |
Just (Exclusive x) -> S.singleton x |
|
188 |
Just (Shared xs) -> xs |
|
189 |
_ -> S.empty |
|
190 |
blockedOn (LockRequest lock (Just OwnShared)) = |
|
191 |
case M.lookup lock (laLocks state) of |
|
192 |
Just (Exclusive x) -> S.singleton x |
|
193 |
_ -> S.empty |
|
194 |
let blocked = S.delete owner . S.unions $ map blockedOn reqs |
|
195 |
let state' = foldl (updateLock owner) state reqs |
|
196 |
return (if S.null blocked then state' else state, blocked) |
Also available in: Unified diff