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 }