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  }