Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (16.1 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
  , freeLocksPredicate
39
  , intersectLocks
40
  , opportunisticLockUnion
41
  ) where
42

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

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

    
57
{-
58

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

    
65
-}
66

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

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

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

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

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

    
91
{-| Representation of a Lock allocation
92

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

    
102
-}
103

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

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

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

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

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

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

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

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

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

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

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

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

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

    
296
-- | Compute the state after an owner releases all its locks that
297
-- satisfy a certain property.
298
freeLocksPredicate :: (Lock a, Ord b)
299
                   => (a -> Bool)
300
                   -> LockAllocation a b -> b -> LockAllocation a b
301
freeLocksPredicate property state owner =
302
  fst . flip (updateLocks owner) state . map requestRelease
303
    . filter property
304
    . M.keys
305
    $ listLocks owner state
306

    
307
-- | Compute the state after an onwer releases all its locks.
308
freeLocks :: (Lock a, Ord b) => LockAllocation a b -> b -> LockAllocation a b
309
freeLocks = freeLocksPredicate (const True)
310

    
311
-- | Restrict the locks of a user to a given set.
312
intersectLocks :: (Lock a, Ord b) => b -> [a]
313
               -> LockAllocation a b -> LockAllocation a b
314
intersectLocks owner locks state =
315
  let lockset = S.fromList locks
316
      toFree = filter (not . flip S.member lockset)
317
                 . M.keys $ listLocks owner state
318
  in fst $ updateLocks owner (map requestRelease toFree) state
319

    
320
-- | Opportunistically allocate locks for a given user; return the set
321
-- of actually acquired. The signature is chosen to be suitable for
322
-- atomicModifyIORef.
323
opportunisticLockUnion :: (Lock a, Ord b)
324
                       => b -> [(a, OwnerState)]
325
                       -> LockAllocation a b -> (LockAllocation a b, S.Set a)
326
opportunisticLockUnion owner reqs state =
327
  let locks = listLocks owner state
328
      reqs' = sort $ filter (uncurry (<) . (flip M.lookup locks *** Just)) reqs
329
      maybeAllocate (s, success) (lock, ownstate) =
330
        let (s', result) = updateLocks owner
331
                                       [(if ownstate == OwnShared
332
                                           then requestShared
333
                                           else requestExclusive) lock]
334
                                       s
335
        in (s', if result == Ok S.empty then lock:success else success)
336
  in second S.fromList $ foldl maybeAllocate (state, []) reqs'
337

    
338
{-| Serializaiton of Lock Allocations
339

    
340
To serialize a lock allocation, we only remember which owner holds
341
which locks at which level (shared or exclusive). From this information,
342
everything else can be reconstructed, simply using updateLocks.
343
-}
344

    
345
instance J.JSON OwnerState where
346
  showJSON OwnShared = J.showJSON "shared"
347
  showJSON OwnExclusive = J.showJSON "exclusive"
348
  readJSON (J.JSString x) = let s = J.fromJSString x
349
                            in case s of
350
                              "shared" -> J.Ok OwnShared
351
                              "exclusive" -> J.Ok OwnExclusive
352
                              _ -> J.Error $ "Unknown owner type " ++ s
353
  readJSON _ = J.Error "Owner type not encoded as a string"
354

    
355
-- | Read a lock-ownerstate pair from JSON.
356
readLockOwnerstate :: (J.JSON a) => J.JSValue -> J.Result (a, OwnerState)
357
readLockOwnerstate (J.JSArray [x, y]) = liftA2 (,) (J.readJSON x) (J.readJSON y)
358
readLockOwnerstate x = fail $ "lock-ownerstate pairs are encoded as arrays"
359
                              ++ " of length 2, but found " ++ show x
360

    
361
-- | Read an owner-lock pair from JSON.
362
readOwnerLock :: (J.JSON a, J.JSON b)
363
              => J.JSValue -> J.Result (b, [(a, OwnerState)])
364
readOwnerLock (J.JSArray [x, J.JSArray ys]) =
365
  liftA2 (,) (J.readJSON x) (mapM readLockOwnerstate ys)
366
readOwnerLock x = fail $ "Expected pair of owner and list of owned locks,"
367
                         ++ " but found " ++ show x
368

    
369
-- | Transform a lock-ownerstate pair into a LockRequest.
370
toRequest :: (a, OwnerState) -> LockRequest a
371
toRequest (a, OwnExclusive) = requestExclusive a
372
toRequest (a, OwnShared) = requestShared a
373

    
374
-- | Obtain a LockAllocation from a given owner-locks list.
375
-- The obtained allocation is the one obtained if the respective owners
376
-- requested their locks sequentially.
377
allocationFromOwners :: (Lock a, Ord b, Show b)
378
                     => [(b, [(a, OwnerState)])]
379
                     -> J.Result (LockAllocation a b)
380
allocationFromOwners =
381
  let allocateOneOwner s (o, req) = do
382
        let (s', result) = updateLocks o (map toRequest req) s
383
        when (result /= Ok S.empty) . fail
384
          . (++) ("Inconsistent lock status for " ++ show o ++ ": ")
385
          $ case result of
386
            Bad err -> err
387
            Ok blocked -> "blocked on " ++ show (S.toList blocked)
388
        return s'
389
  in foldM allocateOneOwner emptyAllocation
390

    
391
instance (Lock a, J.JSON a, Ord b, J.JSON b, Show b)
392
           => J.JSON (LockAllocation a b) where
393
  showJSON = J.showJSON . M.toList . M.map M.toList . laOwned
394
  readJSON x = do
395
    xs <- toArray x
396
    owned <- mapM readOwnerLock xs
397
    allocationFromOwners owned