Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / Locking / Allocation.hs @ 71dc39a1

History | View | Annotate | Download (15.5 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.Applicative (liftA2)
42
import Control.Arrow (second, (***))
43
import Control.Monad
44
import Data.Foldable (for_, find)
45
import Data.List (sort)
46
import qualified Data.Map as M
47
import Data.Maybe (fromMaybe)
48
import qualified Data.Set as S
49
import qualified Text.JSON as J
50

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

    
55
{-
56

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

    
63
-}
64

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

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

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

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

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

    
89
{-| Representation of a Lock allocation
90

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

    
100
-}
101

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

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

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

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

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

    
132
-- | Lock request for a shared lock.
133
requestShared :: a -> LockRequest a
134
requestShared lock = LockRequest { lockAffected = lock
135
                                 , lockRequestType = Just OwnShared }
136

    
137
-- | Request to release a lock.
138
requestRelease :: a -> LockRequest a
139
requestRelease lock = LockRequest { lockAffected = lock
140
                                  , lockRequestType = Nothing }
141

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

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

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

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

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

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

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

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

    
322
{-| Serializaiton of Lock Allocations
323

    
324
To serialize a lock allocation, we only remember which owner holds
325
which locks at which level (shared or exclusive). From this information,
326
everything else can be reconstructed, simply using updateLocks.
327
-}
328

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

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

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

    
353
-- | Transform a lock-ownerstate pair into a LockRequest.
354
toRequest :: (a, OwnerState) -> LockRequest a
355
toRequest (a, OwnExclusive) = requestExclusive a
356
toRequest (a, OwnShared) = requestShared a
357

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

    
375
instance (Lock a, J.JSON a, Ord b, J.JSON b, Show b)
376
           => J.JSON (LockAllocation a b) where
377
  showJSON = J.showJSON . M.toList . M.map M.toList . laOwned
378
  readJSON x = do
379
    xs <- toArray x
380
    owned <- mapM readOwnerLock xs
381
    allocationFromOwners owned