Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / Locking / Allocation.hs @ a55a40d6

History | View | Annotate | Download (15.7 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
  , lockOwners
31
  , listLocks
32
  , LockRequest(..)
33
  , requestExclusive
34
  , requestShared
35
  , requestRelease
36
  , updateLocks
37
  , freeLocks
38
  , intersectLocks
39
  , opportunisticLockUnion
40
  ) where
41

    
42
import Control.Applicative (liftA2)
43
import Control.Arrow (second, (***))
44
import Control.Monad
45
import Data.Foldable (for_, find)
46
import Data.List (sort)
47
import qualified Data.Map as M
48
import Data.Maybe (fromMaybe)
49
import qualified Data.Set as S
50
import qualified Text.JSON as J
51

    
52
import Ganeti.BasicTypes
53
import Ganeti.JSON (toArray)
54
import Ganeti.Locking.Types
55

    
56
{-
57

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

    
64
-}
65

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

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

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

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

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

    
90
{-| Representation of a Lock allocation
91

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

    
101
-}
102

    
103
data LockAllocation a b =
104
  LockAllocation { laLocks :: M.Map a (AllocationState a b)
105
                 , laOwned :: M.Map b (M.Map a OwnerState)
106
                 }
107
  deriving (Eq, Show)
108

    
109
-- | A state with all locks being free.
110
emptyAllocation :: (Ord a, Ord b) => LockAllocation a b
111
emptyAllocation =
112
  LockAllocation { laLocks = M.empty
113
                 , laOwned = M.empty
114
                 }
115

    
116
-- | Obtain the list of all owners holding at least a single lock.
117
lockOwners :: Ord b => LockAllocation a b -> [b]
118
lockOwners = M.keys . laOwned
119

    
120
-- | Obtain the locks held by a given owner. The locks are reported
121
-- as a map from the owned locks to the form of ownership (OwnShared
122
-- or OwnExclusive).
123
listLocks :: Ord b => b -> LockAllocation a b -> M.Map a OwnerState
124
listLocks owner = fromMaybe M.empty . M.lookup owner . laOwned
125

    
126
-- | Data Type describing a change request on a single lock.
127
data LockRequest a = LockRequest { lockAffected :: a
128
                                 , lockRequestType :: Maybe OwnerState
129
                                 }
130
                     deriving (Eq, Show)
131

    
132
-- | Lock request for an exclusive lock.
133
requestExclusive :: a -> LockRequest a
134
requestExclusive lock = LockRequest { lockAffected = lock
135
                                    , lockRequestType = Just OwnExclusive }
136

    
137
-- | Lock request for a shared lock.
138
requestShared :: a -> LockRequest a
139
requestShared lock = LockRequest { lockAffected = lock
140
                                 , lockRequestType = Just OwnShared }
141

    
142
-- | Request to release a lock.
143
requestRelease :: a -> LockRequest a
144
requestRelease lock = LockRequest { lockAffected = lock
145
                                  , lockRequestType = Nothing }
146

    
147
-- | Update the Allocation state of a lock according to a given
148
-- function.
149
updateAllocState :: (Ord a, Ord b)
150
                  => (Maybe (AllocationState a b) -> AllocationState a b)
151
                  -> LockAllocation a b -> a -> LockAllocation a b
152
updateAllocState f state lock =
153
  let locks' = M.alter (find (/= Shared S.empty M.empty) . Just . f)
154
                        lock (laLocks state)
155
  in state { laLocks = locks' }
156

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

    
195
-- | Update the set of indirect ownerships of a lock by the given function.
196
updateIndirectSet :: (Ord a, Ord b)
197
                  => (IndirectOwners a b -> IndirectOwners a b)
198
                  -> LockAllocation a b -> a -> LockAllocation a b
199
updateIndirectSet f =
200
  let update (Just (Exclusive x i)) = Exclusive x (f i)
201
      update (Just (Shared s i)) = Shared s (f i)
202
      update Nothing = Shared S.empty (f M.empty)
203
  in updateAllocState update
204

    
205
-- | Update all indirect onwerships of a given lock.
206
updateIndirects :: (Lock a, Ord b)
207
                => b
208
                -> LockAllocation a b -> LockRequest a -> LockAllocation a b
209
updateIndirects owner state req =
210
  let lock = lockAffected req
211
      fn = case lockRequestType req of
212
             Nothing -> M.delete (lock, owner)
213
             Just tp -> M.insert (lock, owner) tp
214
  in foldl (updateIndirectSet fn) state $ lockImplications lock
215

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

    
294
-- | Compute the state after an onwer releases all its locks.
295
freeLocks :: (Lock a, Ord b) => LockAllocation a b -> b -> LockAllocation a b
296
freeLocks state owner =
297
  fst . flip (updateLocks owner) state . map requestRelease . M.keys
298
    $ listLocks owner state
299

    
300
-- | Restrict the locks of a user to a given set.
301
intersectLocks :: (Lock a, Ord b) => b -> [a]
302
               -> LockAllocation a b -> LockAllocation a b
303
intersectLocks owner locks state =
304
  let lockset = S.fromList locks
305
      toFree = filter (not . flip S.member lockset)
306
                 . M.keys $ listLocks owner state
307
  in fst $ updateLocks owner (map requestRelease toFree) state
308

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

    
327
{-| Serializaiton of Lock Allocations
328

    
329
To serialize a lock allocation, we only remember which owner holds
330
which locks at which level (shared or exclusive). From this information,
331
everything else can be reconstructed, simply using updateLocks.
332
-}
333

    
334
instance J.JSON OwnerState where
335
  showJSON OwnShared = J.showJSON "shared"
336
  showJSON OwnExclusive = J.showJSON "exclusive"
337
  readJSON (J.JSString x) = let s = J.fromJSString x
338
                            in case s of
339
                              "shared" -> J.Ok OwnShared
340
                              "exclusive" -> J.Ok OwnExclusive
341
                              _ -> J.Error $ "Unknown owner type " ++ s
342
  readJSON _ = J.Error "Owner type not encoded as a string"
343

    
344
-- | Read a lock-ownerstate pair from JSON.
345
readLockOwnerstate :: (J.JSON a) => J.JSValue -> J.Result (a, OwnerState)
346
readLockOwnerstate (J.JSArray [x, y]) = liftA2 (,) (J.readJSON x) (J.readJSON y)
347
readLockOwnerstate x = fail $ "lock-ownerstate pairs are encoded as arrays"
348
                              ++ " of length 2, but found " ++ show x
349

    
350
-- | Read an owner-lock pair from JSON.
351
readOwnerLock :: (J.JSON a, J.JSON b)
352
              => J.JSValue -> J.Result (b, [(a, OwnerState)])
353
readOwnerLock (J.JSArray [x, J.JSArray ys]) =
354
  liftA2 (,) (J.readJSON x) (mapM readLockOwnerstate ys)
355
readOwnerLock x = fail $ "Expected pair of owner and list of owned locks,"
356
                         ++ " but found " ++ show x
357

    
358
-- | Transform a lock-ownerstate pair into a LockRequest.
359
toRequest :: (a, OwnerState) -> LockRequest a
360
toRequest (a, OwnExclusive) = requestExclusive a
361
toRequest (a, OwnShared) = requestShared a
362

    
363
-- | Obtain a LockAllocation from a given owner-locks list.
364
-- The obtained allocation is the one obtained if the respective owners
365
-- requested their locks sequentially.
366
allocationFromOwners :: (Lock a, Ord b, Show b)
367
                     => [(b, [(a, OwnerState)])]
368
                     -> J.Result (LockAllocation a b)
369
allocationFromOwners =
370
  let allocateOneOwner s (o, req) = do
371
        let (s', result) = updateLocks o (map toRequest req) s
372
        when (result /= Ok S.empty) . fail
373
          . (++) ("Inconsistent lock status for " ++ show o ++ ": ")
374
          $ case result of
375
            Bad err -> err
376
            Ok blocked -> "blocked on " ++ show (S.toList blocked)
377
        return s'
378
  in foldM allocateOneOwner emptyAllocation
379

    
380
instance (Lock a, J.JSON a, Ord b, J.JSON b, Show b)
381
           => J.JSON (LockAllocation a b) where
382
  showJSON = J.showJSON . M.toList . M.map M.toList . laOwned
383
  readJSON x = do
384
    xs <- toArray x
385
    owned <- mapM readOwnerLock xs
386
    allocationFromOwners owned