Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / Locking / Allocation.hs @ 15208e95

History | View | Annotate | Download (7.3 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
  ) where
37

    
38
import Control.Arrow (second, (***))
39
import Control.Monad
40
import Data.Foldable (for_)
41
import qualified Data.Map as M
42
import Data.Maybe (fromMaybe)
43
import qualified Data.Set as S
44

    
45
import Ganeti.BasicTypes
46

    
47
{-
48

    
49
This module is parametric in the type of locks and lock owners.
50
While we only state minimal requirements for the types, we will
51
consistently use the type variable 'a' for the type of locks and
52
the variable 'b' for the type of the lock owners throughout this
53
module.
54

    
55
-}
56

    
57
-- | The state of a lock that is taken.
58
data AllocationState a = Exclusive a | Shared (S.Set a) deriving (Eq, Show)
59

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

    
63
{-| Representation of a Lock allocation
64

    
65
To keep queries for locks efficient, we keep two
66
associations, with the invariant that they fit
67
together: the association from locks to their
68
allocation state, and the association from an
69
owner to the set of locks owned. As we do not
70
export the constructor, the problem of keeping
71
this invariant reduces to only exporting functions
72
that keep the invariant.
73

    
74
-}
75

    
76
data LockAllocation a b =
77
  LockAllocation { laLocks :: M.Map a (AllocationState b)
78
                 , laOwned :: M.Map b (M.Map a OwnerState)
79
                 }
80
  deriving (Eq, Show)
81

    
82
-- | A state with all locks being free.
83
emptyAllocation :: (Ord a, Ord b) => LockAllocation a b
84
emptyAllocation =
85
  LockAllocation { laLocks = M.empty
86
                 , laOwned = M.empty
87
                 }
88

    
89
listLocks :: Ord b => b -> LockAllocation a b -> M.Map a OwnerState
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)