github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sqlmigrations/leasemanager/lease.go (about)

     1  // Copyright 2016 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  // Package leasemanager provides functionality for acquiring and managing leases
    12  // via the kv api for use during sqlmigrations.
    13  package leasemanager
    14  
    15  import (
    16  	"context"
    17  	"fmt"
    18  	"time"
    19  
    20  	"github.com/cockroachdb/cockroach/pkg/kv"
    21  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    22  	"github.com/cockroachdb/cockroach/pkg/util/hlc"
    23  	"github.com/cockroachdb/cockroach/pkg/util/uuid"
    24  	"github.com/cockroachdb/errors"
    25  )
    26  
    27  // DefaultLeaseDuration is the duration a lease will be acquired for if no
    28  // duration was specified in a LeaseManager's options.
    29  // Exported for testing purposes.
    30  const DefaultLeaseDuration = 1 * time.Minute
    31  
    32  // LeaseNotAvailableError indicates that the lease the caller attempted to
    33  // acquire is currently held by a different client.
    34  type LeaseNotAvailableError struct {
    35  	key        roachpb.Key
    36  	expiration hlc.Timestamp
    37  }
    38  
    39  func (e *LeaseNotAvailableError) Error() string {
    40  	return fmt.Sprintf("lease %q is not available until at least %s", e.key, e.expiration)
    41  }
    42  
    43  // LeaseManager provides functionality for acquiring and managing leases
    44  // via the kv api.
    45  type LeaseManager struct {
    46  	db            *kv.DB
    47  	clock         *hlc.Clock
    48  	clientID      string
    49  	leaseDuration time.Duration
    50  }
    51  
    52  // Lease contains the state of a lease on a particular key.
    53  type Lease struct {
    54  	key roachpb.Key
    55  	val struct {
    56  		sem      chan struct{}
    57  		lease    *LeaseVal
    58  		leaseRaw roachpb.Value
    59  	}
    60  }
    61  
    62  // Options are used to configure a new LeaseManager.
    63  type Options struct {
    64  	// ClientID must be unique to this LeaseManager instance.
    65  	ClientID      string
    66  	LeaseDuration time.Duration
    67  }
    68  
    69  // New allocates a new LeaseManager.
    70  func New(db *kv.DB, clock *hlc.Clock, options Options) *LeaseManager {
    71  	if options.ClientID == "" {
    72  		options.ClientID = uuid.MakeV4().String()
    73  	}
    74  	if options.LeaseDuration <= 0 {
    75  		options.LeaseDuration = DefaultLeaseDuration
    76  	}
    77  	return &LeaseManager{
    78  		db:            db,
    79  		clock:         clock,
    80  		clientID:      options.ClientID,
    81  		leaseDuration: options.LeaseDuration,
    82  	}
    83  }
    84  
    85  // AcquireLease attempts to grab a lease on the provided key. Returns a non-nil
    86  // lease object if it was successful, or an error if it failed to acquire the
    87  // lease for any reason.
    88  //
    89  // NB: Acquiring a non-expired lease is allowed if this LeaseManager's clientID
    90  // matches the lease owner's ID. This behavior allows a process to re-grab
    91  // leases without having to wait if it restarts and uses the same ID.
    92  func (m *LeaseManager) AcquireLease(ctx context.Context, key roachpb.Key) (*Lease, error) {
    93  	lease := &Lease{
    94  		key: key,
    95  	}
    96  	lease.val.sem = make(chan struct{}, 1)
    97  	if err := m.db.Txn(ctx, func(ctx context.Context, txn *kv.Txn) error {
    98  		var val LeaseVal
    99  		err := txn.GetProto(ctx, key, &val)
   100  		if err != nil {
   101  			return err
   102  		}
   103  		if !m.leaseAvailable(&val) {
   104  			return &LeaseNotAvailableError{key: key, expiration: val.Expiration}
   105  		}
   106  		lease.val.lease = &LeaseVal{
   107  			Owner:      m.clientID,
   108  			Expiration: m.clock.Now().Add(m.leaseDuration.Nanoseconds(), 0),
   109  		}
   110  		var leaseRaw roachpb.Value
   111  		if err := leaseRaw.SetProto(lease.val.lease); err != nil {
   112  			return err
   113  		}
   114  		if err := txn.Put(ctx, key, &leaseRaw); err != nil {
   115  			return err
   116  		}
   117  		// After using newRaw as an arg to CPut, we're not allowed to modify it.
   118  		// Passing it back to CPut again (which is the whole point of keeping it
   119  		// around) will clear and re-init the checksum, so defensively copy it before
   120  		// we save it.
   121  		lease.val.leaseRaw = roachpb.Value{RawBytes: append([]byte(nil), leaseRaw.RawBytes...)}
   122  		return nil
   123  	}); err != nil {
   124  		return nil, err
   125  	}
   126  	return lease, nil
   127  }
   128  
   129  func (m *LeaseManager) leaseAvailable(val *LeaseVal) bool {
   130  	return val.Owner == m.clientID || m.timeRemaining(val) <= 0
   131  }
   132  
   133  // TimeRemaining returns the amount of time left on the given lease.
   134  func (m *LeaseManager) TimeRemaining(l *Lease) time.Duration {
   135  	l.val.sem <- struct{}{}
   136  	defer func() { <-l.val.sem }()
   137  	return m.timeRemaining(l.val.lease)
   138  }
   139  
   140  func (m *LeaseManager) timeRemaining(val *LeaseVal) time.Duration {
   141  	maxOffset := m.clock.MaxOffset()
   142  	return val.Expiration.GoTime().Sub(m.clock.Now().GoTime()) - maxOffset
   143  }
   144  
   145  // ExtendLease attempts to push the expiration time of the lease farther out
   146  // into the future.
   147  func (m *LeaseManager) ExtendLease(ctx context.Context, l *Lease) error {
   148  	select {
   149  	case <-ctx.Done():
   150  		return ctx.Err()
   151  	case l.val.sem <- struct{}{}:
   152  	}
   153  	defer func() { <-l.val.sem }()
   154  
   155  	if m.timeRemaining(l.val.lease) < 0 {
   156  		return errors.Errorf("can't extend lease that expired at time %s", l.val.lease.Expiration)
   157  	}
   158  
   159  	newVal := &LeaseVal{
   160  		Owner:      m.clientID,
   161  		Expiration: m.clock.Now().Add(m.leaseDuration.Nanoseconds(), 0),
   162  	}
   163  	var newRaw roachpb.Value
   164  	if err := newRaw.SetProto(newVal); err != nil {
   165  		return err
   166  	}
   167  	if err := m.db.CPut(ctx, l.key, &newRaw, &l.val.leaseRaw); err != nil {
   168  		if errors.HasType(err, (*roachpb.ConditionFailedError)(nil)) {
   169  			// Something is wrong - immediately expire the local lease state.
   170  			l.val.lease.Expiration = hlc.Timestamp{}
   171  			return errors.Wrapf(err, "local lease state %v out of sync with DB state", l.val.lease)
   172  		}
   173  		return err
   174  	}
   175  	l.val.lease = newVal
   176  	// After using newRaw as an arg to CPut, we're not allowed to modify it.
   177  	// Passing it back to CPut again (which is the whole point of keeping it
   178  	// around) will clear and re-init the checksum, so defensively copy it before
   179  	// we save it.
   180  	l.val.leaseRaw = roachpb.Value{RawBytes: append([]byte(nil), newRaw.RawBytes...)}
   181  	return nil
   182  }
   183  
   184  // ReleaseLease attempts to release the given lease so that another process can
   185  // grab it.
   186  func (m *LeaseManager) ReleaseLease(ctx context.Context, l *Lease) error {
   187  	select {
   188  	case <-ctx.Done():
   189  		return ctx.Err()
   190  	case l.val.sem <- struct{}{}:
   191  	}
   192  	defer func() { <-l.val.sem }()
   193  
   194  	return m.db.CPut(ctx, l.key, nil, &l.val.leaseRaw)
   195  }