Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / Locking / Allocation.hs @ 4b217f68

History | View | Annotate | Download (12.8 kB)

1
{-| Implementation of lock allocation.
2

    
3
-}
4

    
5
{-
6

    
7
Copyright (C) 2014 Google Inc.
8

    
9
This program is free software; you can redistribute it and/or modify
10
it under the terms of the GNU General Public License as published by
11
the Free Software Foundation; either version 2 of the License, or
12
(at your option) any later version.
13

    
14
This program is distributed in the hope that it will be useful, but
15
WITHOUT ANY WARRANTY; without even the implied warranty of
16
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17
General Public License for more details.
18

    
19
You should have received a copy of the GNU General Public License
20
along with this program; if not, write to the Free Software
21
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22
02110-1301, USA.
23

    
24
-}
25

    
26
module Ganeti.Locking.Allocation
27
  ( LockAllocation
28
  , emptyAllocation
29
  , OwnerState(..)
30
  , listLocks
31
  , LockRequest(..)
32
  , requestExclusive
33
  , requestShared
34
  , requestRelease
35
  , updateLocks
36
  , freeLocks
37
  , intersectLocks
38
  , opportunisticLockUnion
39
  ) where
40

    
41
import Control.Arrow (second, (***))
42
import Control.Monad
43
import Data.Foldable (for_, find)
44
import Data.List (sort)
45
import qualified Data.Map as M
46
import Data.Maybe (fromMaybe)
47
import qualified Data.Set as S
48

    
49
import Ganeti.BasicTypes
50
import Ganeti.Locking.Types
51

    
52
{-
53

    
54
This module is parametric in the type of locks and lock owners.
55
While we only state minimal requirements for the types, we will
56
consistently use the type variable 'a' for the type of locks and
57
the variable 'b' for the type of the lock owners throughout this
58
module.
59

    
60
-}
61

    
62
-- | Data type describing the way a lock can be owned.
63
data OwnerState = OwnShared | OwnExclusive deriving (Ord, Eq, Show)
64

    
65
-- | Type describing indirect ownership on a lock. We keep the set
66
-- of all (lock, owner)-pairs for locks that are implied in the given
67
-- lock, annotated with the type of ownership (shared or exclusive).
68
type IndirectOwners a b = M.Map (a, b) OwnerState
69

    
70
-- | The state of a lock that is taken. Besides the state of the lock
71
-- itself, we also keep track of all other lock allocation that affect
72
-- the given lock by means of implication.
73
data AllocationState a b = Exclusive b (IndirectOwners a b)
74
                         | Shared (S.Set b) (IndirectOwners a b)
75
                         deriving (Eq, Show)
76

    
77
-- | Compute the set of indirect owners from the information about
78
-- indirect ownership.
79
indirectOwners :: (Ord a, Ord b) => M.Map (a, b) OwnerState -> S.Set b
80
indirectOwners = S.map snd . M.keysSet
81

    
82
-- | Compute the (zero or one-elment) set of exclusive indirect owners.
83
indirectExclusives :: (Ord a, Ord b) => M.Map (a, b) OwnerState -> S.Set b
84
indirectExclusives = indirectOwners . M.filter (== OwnExclusive)
85

    
86
{-| Representation of a Lock allocation
87

    
88
To keep queries for locks efficient, we keep two
89
associations, with the invariant that they fit
90
together: the association from locks to their
91
allocation state, and the association from an
92
owner to the set of locks owned. As we do not
93
export the constructor, the problem of keeping
94
this invariant reduces to only exporting functions
95
that keep the invariant.
96

    
97
-}
98

    
99
data LockAllocation a b =
100
  LockAllocation { laLocks :: M.Map a (AllocationState a b)
101
                 , laOwned :: M.Map b (M.Map a OwnerState)
102
                 }
103
  deriving (Eq, Show)
104

    
105
-- | A state with all locks being free.
106
emptyAllocation :: (Ord a, Ord b) => LockAllocation a b
107
emptyAllocation =
108
  LockAllocation { laLocks = M.empty
109
                 , laOwned = M.empty
110
                 }
111

    
112
-- | Obtain the locks held by a given owner. The locks are reported
113
-- as a map from the owned locks to the form of ownership (OwnShared
114
-- or OwnExclusive).
115
listLocks :: Ord b => b -> LockAllocation a b -> M.Map a OwnerState
116
listLocks owner = fromMaybe M.empty . M.lookup owner . laOwned
117

    
118
-- | Data Type describing a change request on a single lock.
119
data LockRequest a = LockRequest { lockAffected :: a
120
                                 , lockRequestType :: Maybe OwnerState
121
                                 }
122
                     deriving (Eq, Show)
123

    
124
-- | Lock request for an exclusive lock.
125
requestExclusive :: a -> LockRequest a
126
requestExclusive lock = LockRequest { lockAffected = lock
127
                                    , lockRequestType = Just OwnExclusive }
128

    
129
-- | Lock request for a shared lock.
130
requestShared :: a -> LockRequest a
131
requestShared lock = LockRequest { lockAffected = lock
132
                                 , lockRequestType = Just OwnShared }
133

    
134
-- | Request to release a lock.
135
requestRelease :: a -> LockRequest a
136
requestRelease lock = LockRequest { lockAffected = lock
137
                                  , lockRequestType = Nothing }
138

    
139
-- | Update the Allocation state of a lock according to a given
140
-- function.
141
updateAllocState :: (Ord a, Ord b)
142
                  => (Maybe (AllocationState a b) -> AllocationState a b)
143
                  -> LockAllocation a b -> a -> LockAllocation a b
144
updateAllocState f state lock =
145
  let locks' = M.alter (find (/= Shared S.empty M.empty) . Just . f)
146
                        lock (laLocks state)
147
  in state { laLocks = locks' }
148

    
149
-- | Internal function to update the state according to a single
150
-- lock request, assuming all prerequisites are met.
151
updateLock :: (Ord a, Ord b)
152
           => b
153
           -> LockAllocation a b -> LockRequest a -> LockAllocation a b
154
updateLock owner state (LockRequest lock (Just OwnExclusive)) =
155
  let locks = laLocks state
156
      lockstate' = case M.lookup lock locks of
157
        Just (Exclusive _ i) -> Exclusive owner i
158
        Just (Shared _ i) -> Exclusive owner i
159
        Nothing -> Exclusive owner M.empty
160
      locks' = M.insert lock lockstate' locks
161
      ownersLocks' = M.insert lock OwnExclusive $ listLocks owner state
162
      owned' = M.insert owner ownersLocks' $ laOwned state
163
  in state { laLocks = locks', laOwned = owned' }
164
updateLock owner state (LockRequest lock (Just OwnShared)) =
165
  let ownersLocks' = M.insert lock OwnShared $ listLocks owner state
166
      owned' = M.insert owner ownersLocks' $ laOwned state
167
      locks = laLocks state
168
      lockState' = case M.lookup lock locks of
169
        Just (Exclusive _ i) -> Shared (S.singleton owner) i
170
        Just (Shared s i) -> Shared (S.insert owner s) i
171
        _ -> Shared (S.singleton owner) M.empty
172
      locks' = M.insert lock lockState' locks
173
  in state { laLocks = locks', laOwned = owned' }
174
updateLock owner state (LockRequest lock Nothing) =
175
  let ownersLocks' = M.delete lock $ listLocks owner state
176
      owned = laOwned state
177
      owned' = if M.null ownersLocks'
178
                 then M.delete owner owned
179
                 else M.insert owner ownersLocks' owned
180
      update (Just (Exclusive x i)) = if x == owner
181
                                        then Shared S.empty i
182
                                        else Exclusive x i
183
      update (Just (Shared s i)) = Shared (S.delete owner s) i
184
      update Nothing = Shared S.empty M.empty
185
  in updateAllocState update (state { laOwned = owned' }) lock
186

    
187
-- | Update the set of indirect ownerships of a lock by the given function.
188
updateIndirectSet :: (Ord a, Ord b)
189
                  => (IndirectOwners a b -> IndirectOwners a b)
190
                  -> LockAllocation a b -> a -> LockAllocation a b
191
updateIndirectSet f =
192
  let update (Just (Exclusive x i)) = Exclusive x (f i)
193
      update (Just (Shared s i)) = Shared s (f i)
194
      update Nothing = Shared S.empty (f M.empty)
195
  in updateAllocState update
196

    
197
-- | Update all indirect onwerships of a given lock.
198
updateIndirects :: (Lock a, Ord b)
199
                => b
200
                -> LockAllocation a b -> LockRequest a -> LockAllocation a b
201
updateIndirects owner state req =
202
  let lock = lockAffected req
203
      fn = case lockRequestType req of
204
             Nothing -> M.delete (lock, owner)
205
             Just tp -> M.insert (lock, owner) tp
206
  in foldl (updateIndirectSet fn) state $ lockImplications lock
207

    
208
-- | Update the locks of an owner according to the given request. Return
209
-- the pair of the new state and the result of the operation, which is the
210
-- the set of owners on which the operation was blocked on. so an empty set is
211
-- success, and the state is updated if, and only if, the returned set is emtpy.
212
-- In that way, it can be used in atomicModifyIORef.
213
updateLocks :: (Lock a, Ord b)
214
            => b
215
            -> [LockRequest a]
216
            -> LockAllocation a b -> (LockAllocation a b, Result (S.Set b))
217
updateLocks owner reqs state = genericResult ((,) state . Bad) (second Ok) $ do
218
  unless ((==) (length reqs) . S.size . S.fromList $ map lockAffected reqs)
219
    . runListHead (return ())
220
                  (fail . (++) "Inconsitent requests for lock " . show) $ do
221
      r <- reqs
222
      r' <- reqs
223
      guard $ r /= r'
224
      guard $ lockAffected r == lockAffected r'
225
      return $ lockAffected r
226
  let current = listLocks owner state
227
  unless (M.null current) $ do
228
    let (highest, _) = M.findMax current
229
        notHolding = not
230
                     . any (uncurry (==) . ((M.lookup `flip` current) *** Just))
231
        orderViolation l = fail $ "Order violation: requesting " ++ show l
232
                                   ++ " while holding " ++ show highest
233
    for_ reqs $ \req -> case req of
234
      LockRequest lock (Just OwnExclusive)
235
        | lock < highest && notHolding [ (lock, OwnExclusive) ]
236
        -> orderViolation lock
237
      LockRequest lock (Just OwnShared)
238
        | lock < highest && notHolding [ (lock, OwnExclusive)
239
                                       , (lock, OwnExclusive)]
240
        -> orderViolation lock
241
      _ -> Ok ()
242
  let sharedsHeld = M.keysSet $ M.filter (== OwnShared) current
243
      exclusivesRequested = map lockAffected
244
                            . filter ((== Just OwnExclusive) . lockRequestType)
245
                            $ reqs
246
  runListHead (return ()) fail $ do
247
    x <- exclusivesRequested
248
    i <- lockImplications x
249
    guard $ S.member i sharedsHeld
250
    return $ "Order violation: requesting exclusively " ++ show x
251
              ++ " while holding a shared lock on the group lock " ++ show i
252
              ++ " it belongs to."
253
  let blockedOn (LockRequest  _ Nothing) = S.empty
254
      blockedOn (LockRequest lock (Just OwnExclusive)) =
255
        case M.lookup lock (laLocks state) of
256
          Just (Exclusive x i) ->
257
            S.singleton x `S.union` indirectOwners i
258
          Just (Shared xs i) ->
259
            xs `S.union` indirectOwners i
260
          _ -> S.empty
261
      blockedOn (LockRequest lock (Just OwnShared)) =
262
        case M.lookup lock (laLocks state) of
263
          Just (Exclusive x i) ->
264
            S.singleton x `S.union` indirectExclusives i
265
          Just (Shared _ i) -> indirectExclusives i
266
          _ -> S.empty
267
  let indirectBlocked Nothing _ = S.empty
268
      indirectBlocked (Just OwnShared) lock =
269
        case M.lookup lock (laLocks state) of
270
          Just (Exclusive x _) -> S.singleton x
271
          _ -> S.empty
272
      indirectBlocked (Just OwnExclusive) lock =
273
        case M.lookup lock (laLocks state) of
274
          Just (Exclusive x _) -> S.singleton x
275
          Just (Shared xs _) -> xs
276
          _ -> S.empty
277
  let direct = S.unions $ map blockedOn reqs
278
      indirect = reqs >>= \req ->
279
        map (indirectBlocked (lockRequestType req))
280
          . lockImplications $ lockAffected req
281
  let blocked = S.delete owner . S.unions $ direct:indirect
282
  let state' = foldl (updateLock owner) state reqs
283
      state'' = foldl (updateIndirects owner) state' reqs
284
  return (if S.null blocked then state'' else state, blocked)
285

    
286
-- | Compute the state after an onwer releases all its locks.
287
freeLocks :: (Lock a, Ord b) => LockAllocation a b -> b -> LockAllocation a b
288
freeLocks state owner =
289
  fst . flip (updateLocks owner) state . map requestRelease . M.keys
290
    $ listLocks owner state
291

    
292
-- | Restrict the locks of a user to a given set.
293
intersectLocks :: (Lock a, Ord b) => b -> [a]
294
               -> LockAllocation a b -> LockAllocation a b
295
intersectLocks owner locks state =
296
  let lockset = S.fromList locks
297
      toFree = filter (not . flip S.member lockset)
298
                 . M.keys $ listLocks owner state
299
  in fst $ updateLocks owner (map requestRelease toFree) state
300

    
301
-- | Opportunistically allocate locks for a given user; return the set
302
-- of actually acquired. The signature is chosen to be suitable for
303
-- atomicModifyIORef.
304
opportunisticLockUnion :: (Lock a, Ord b)
305
                       => b -> [(a, OwnerState)]
306
                       -> LockAllocation a b -> (LockAllocation a b, S.Set a)
307
opportunisticLockUnion owner reqs state =
308
  let locks = listLocks owner state
309
      reqs' = sort $ filter (uncurry (<) . (flip M.lookup locks *** Just)) reqs
310
      maybeAllocate (s, success) (lock, ownstate) =
311
        let (s', result) = updateLocks owner
312
                                       [(if ownstate == OwnShared
313
                                           then requestShared
314
                                           else requestExclusive) lock]
315
                                       s
316
        in (s', if result == Ok S.empty then lock:success else success)
317
  in second S.fromList $ foldl maybeAllocate (state, []) reqs'