github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/lease/lease.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package lease 5 6 import ( 7 "fmt" 8 "time" 9 10 "github.com/juju/errors" 11 "github.com/juju/loggo" 12 ) 13 14 const ( 15 // There are no blocking calls, so this can be long. We just don't 16 // want goroutines to hang around indefinitely, so notifications 17 // will time out after this value. 18 notificationTimeout = 1 * time.Minute 19 20 // This is a useful thing to know in several contexts. 21 maxDuration = time.Duration(1<<63 - 1) 22 ) 23 24 var ( 25 singleton *leaseManager 26 LeaseClaimDeniedErr = errors.New("lease claim denied") 27 NotLeaseOwnerErr = errors.Unauthorizedf("caller did not own lease for namespace") 28 logger = loggo.GetLogger("juju.lease") 29 ) 30 31 func init() { 32 singleton = &leaseManager{ 33 claimLease: make(chan Token), 34 releaseLease: make(chan releaseLeaseMsg), 35 leaseReleasedSub: make(chan leaseReleasedMsg), 36 copyOfTokens: make(chan []Token), 37 } 38 } 39 40 type leasePersistor interface { 41 WriteToken(string, Token) error 42 RemoveToken(id string) error 43 PersistedTokens() ([]Token, error) 44 } 45 46 // WorkerLoop returns a function which can be utilized within a 47 // worker. 48 func WorkerLoop(persistor leasePersistor) func(<-chan struct{}) error { 49 singleton.leasePersistor = persistor 50 return singleton.workerLoop 51 } 52 53 // Token represents a lease claim. 54 type Token struct { 55 Namespace, Id string 56 Expiration time.Time 57 } 58 59 // Manager returns a manager. 60 func Manager() *leaseManager { 61 // Guaranteed to be initialized because the init function runs 62 // first. 63 return singleton 64 } 65 66 // 67 // Messages for channels. 68 // 69 70 type releaseLeaseMsg struct { 71 Token *Token 72 Err error 73 } 74 type leaseReleasedMsg struct { 75 Watcher chan<- struct{} 76 ForNamespace string 77 } 78 79 type leaseManager struct { 80 leasePersistor leasePersistor 81 retrieveLease chan Token 82 claimLease chan Token 83 releaseLease chan releaseLeaseMsg 84 leaseReleasedSub chan leaseReleasedMsg 85 copyOfTokens chan []Token 86 } 87 88 // CopyOfLeaseTokens returns a copy of the lease tokens current held 89 // by the manager. 90 func (m *leaseManager) CopyOfLeaseTokens() []Token { 91 m.copyOfTokens <- nil 92 return <-m.copyOfTokens 93 } 94 95 // RetrieveLease returns the lease token currently stored for the 96 // given namespace. 97 func (m *leaseManager) RetrieveLease(namespace string) Token { 98 for _, tok := range m.CopyOfLeaseTokens() { 99 if tok.Namespace != namespace { 100 continue 101 } 102 return tok 103 } 104 return Token{} 105 } 106 107 // Claimlease claims a lease for the given duration for the given 108 // namespace and id. If the lease is already owned, a 109 // LeaseClaimDeniedErr will be returned. Either way the current lease 110 // owner's ID will be returned. 111 func (m *leaseManager) ClaimLease(namespace, id string, forDur time.Duration) (leaseOwnerId string, err error) { 112 113 token := Token{namespace, id, time.Now().Add(forDur)} 114 m.claimLease <- token 115 activeClaim := <-m.claimLease 116 117 leaseOwnerId = activeClaim.Id 118 if id != leaseOwnerId { 119 err = LeaseClaimDeniedErr 120 } 121 122 return leaseOwnerId, err 123 } 124 125 // ReleaseLease releases the lease held for namespace by id. 126 func (m *leaseManager) ReleaseLease(namespace, id string) (err error) { 127 128 token := Token{Namespace: namespace, Id: id} 129 m.releaseLease <- releaseLeaseMsg{Token: &token} 130 response := <-m.releaseLease 131 132 if err := response.Err; err != nil { 133 err = errors.Annotatef(response.Err, `could not release lease for namespace "%s", id "%s"`, namespace, id) 134 135 // Log errors so that we're aware they're happening, but don't 136 // burden the caller with dealing with an error if it's 137 // essential a no-op. 138 if errors.IsUnauthorized(err) { 139 logger.Warningf(err.Error()) 140 return nil 141 } 142 return err 143 } 144 145 return nil 146 } 147 148 // LeaseReleasedNotifier returns a channel a caller can block on to be 149 // notified of when a lease is released for namespace. This channel is 150 // reusable, but will be closed if it does not respond within 151 // "notificationTimeout". 152 func (m *leaseManager) LeaseReleasedNotifier(namespace string) (notifier <-chan struct{}) { 153 watcher := make(chan struct{}) 154 m.leaseReleasedSub <- leaseReleasedMsg{watcher, namespace} 155 156 return watcher 157 } 158 159 // workerLoop serializes all requests into a single thread. 160 func (m *leaseManager) workerLoop(stop <-chan struct{}) error { 161 162 // These data-structures are local to ensure they're only utilized 163 // within this thread-safe context. 164 165 releaseSubs := make(map[string][]chan<- struct{}, 0) 166 167 // Pull everything off our data-store & check for expirations. 168 leaseCache, err := populateTokenCache(m.leasePersistor) 169 if err != nil { 170 return err 171 } 172 nextExpiration := m.expireLeases(leaseCache, releaseSubs) 173 174 for { 175 select { 176 case <-stop: 177 return nil 178 case claim := <-m.claimLease: 179 lease := claimLease(leaseCache, claim) 180 if lease.Id != claim.Id { 181 m.claimLease <- lease 182 } 183 184 m.leasePersistor.WriteToken(lease.Namespace, lease) 185 if lease.Expiration.Before(nextExpiration) { 186 nextExpiration = lease.Expiration 187 } 188 m.claimLease <- lease 189 case claim := <-m.releaseLease: 190 var response releaseLeaseMsg 191 response.Err = releaseLease(leaseCache, claim.Token) 192 if response.Err != nil { 193 m.releaseLease <- response 194 } 195 196 // Unwind our layers from most volatile to least. 197 response.Err = m.leasePersistor.RemoveToken(claim.Token.Namespace) 198 m.releaseLease <- response 199 notifyOfRelease(releaseSubs[claim.Token.Namespace], claim.Token.Namespace) 200 201 case subscription := <-m.leaseReleasedSub: 202 subscribe(releaseSubs, subscription) 203 case <-m.copyOfTokens: 204 // create a copy of the lease cache for use by code 205 // external to our thread-safe context. 206 m.copyOfTokens <- copyTokens(leaseCache) 207 case <-time.After(nextExpiration.Sub(time.Now())): 208 nextExpiration = m.expireLeases(leaseCache, releaseSubs) 209 break 210 } 211 } 212 } 213 214 func (m *leaseManager) expireLeases( 215 cache map[string]Token, 216 subscribers map[string][]chan<- struct{}, 217 ) time.Time { 218 219 // Having just looped through all the leases we're holding, we can 220 // inform the caller of when the next expiration will occur. 221 nextExpiration := time.Now().Add(maxDuration) 222 223 for _, token := range cache { 224 225 if token.Expiration.After(time.Now()) { 226 // For the tokens that aren't expiring yet, find the 227 // minimum time we should wait before cleaning up again. 228 if nextExpiration.After(token.Expiration) { 229 nextExpiration = token.Expiration 230 fmt.Printf("Setting next expiration to %s\n", nextExpiration) 231 } 232 continue 233 } 234 235 logger.Infof(`Lease for namespace "%s" has expired.`, token.Namespace) 236 if err := releaseLease(cache, &token); err == nil { 237 notifyOfRelease(subscribers[token.Namespace], token.Namespace) 238 } 239 } 240 241 return nextExpiration 242 } 243 244 func copyTokens(cache map[string]Token) (copy []Token) { 245 for _, t := range cache { 246 copy = append(copy, t) 247 } 248 return copy 249 } 250 251 func claimLease(cache map[string]Token, claim Token) Token { 252 if active, ok := cache[claim.Namespace]; ok && active.Id != claim.Id { 253 return active 254 } 255 cache[claim.Namespace] = claim 256 logger.Infof(`"%s" obtained lease for "%s"`, claim.Id, claim.Namespace) 257 return claim 258 } 259 260 func releaseLease(cache map[string]Token, claim *Token) error { 261 if active, ok := cache[claim.Namespace]; !ok || active.Id != claim.Id { 262 return NotLeaseOwnerErr 263 } 264 delete(cache, claim.Namespace) 265 logger.Infof(`"%s" released lease for namespace "%s"`, claim.Id, claim.Namespace) 266 return nil 267 } 268 269 func subscribe(subMap map[string][]chan<- struct{}, subscription leaseReleasedMsg) { 270 subList := subMap[subscription.ForNamespace] 271 subList = append(subList, subscription.Watcher) 272 subMap[subscription.ForNamespace] = subList 273 } 274 275 func notifyOfRelease(subscribers []chan<- struct{}, namespace string) { 276 logger.Infof(`Notifying namespace "%s" subscribers that its lease has been released.`, namespace) 277 for _, subscriber := range subscribers { 278 // Spin off into go-routine so we don't rely on listeners to 279 // not block. 280 go func(subscriber chan<- struct{}) { 281 select { 282 case subscriber <- struct{}{}: 283 case <-time.After(notificationTimeout): 284 // TODO(kate): Remove this bad-citizen from the 285 // notifier's list. 286 logger.Warningf("A notification timed out after %s.", notificationTimeout) 287 } 288 }(subscriber) 289 } 290 } 291 292 func populateTokenCache(persistor leasePersistor) (map[string]Token, error) { 293 294 tokens, err := persistor.PersistedTokens() 295 if err != nil { 296 return nil, err 297 } 298 299 cache := make(map[string]Token) 300 for _, tok := range tokens { 301 cache[tok.Namespace] = tok 302 } 303 304 return cache, nil 305 }