// InitOrJoinRequest executes a RequestLease command asynchronously and returns a // channel on which the result will be posted. If there's already a request in // progress, we join in waiting for the results of that request. // It is an error to call InitOrJoinRequest() while a request is in progress // naming another replica as lease holder. // // replica is used to schedule and execute async work (proposing a RequestLease // command). replica.mu is locked when delivering results, so calls from the // replica happen either before or after a result for a pending request has // happened. // // transfer needs to be set if the request represents a lease transfer (as // opposed to an extension, or acquiring the lease when none is held). // // Note: Once this function gets a context to be used for cancellation, instead // of replica.store.Stopper().ShouldQuiesce(), care will be needed for cancelling // the Raft command, similar to replica.addWriteCmd. func (p *pendingLeaseRequest) InitOrJoinRequest( repl *Replica, nextLeaseHolder roachpb.ReplicaDescriptor, status LeaseStatus, startKey roachpb.Key, transfer bool, ) <-chan *roachpb.Error { if nextLease, ok := p.RequestPending(); ok { if nextLease.Replica.ReplicaID == nextLeaseHolder.ReplicaID { // Join a pending request asking for the same replica to become lease // holder. return p.JoinRequest() } llChan := make(chan *roachpb.Error, 1) // We can't join the request in progress. llChan <- roachpb.NewErrorf("request for different replica in progress "+ "(requesting: %+v, in progress: %+v)", nextLeaseHolder.ReplicaID, nextLease.Replica.ReplicaID) return llChan } llChan := make(chan *roachpb.Error, 1) // No request in progress. Let's propose a Lease command asynchronously. reqSpan := roachpb.Span{ Key: startKey, } var leaseReq roachpb.Request now := repl.store.Clock().Now() reqLease := roachpb.Lease{ Start: status.timestamp, Replica: nextLeaseHolder, ProposedTS: &now, } if repl.requiresExpiringLease() { reqLease.Expiration = status.timestamp.Add(int64(repl.store.cfg.RangeLeaseActiveDuration), 0) } else { // Get the liveness for the next lease holder and set the epoch in the lease request. liveness, err := repl.store.cfg.NodeLiveness.GetLiveness(nextLeaseHolder.NodeID) if err != nil { llChan <- roachpb.NewErrorf("couldn't request lease for %+v: %v", nextLeaseHolder, err) return llChan } reqLease.Epoch = proto.Int64(liveness.Epoch) } if transfer { leaseReq = &roachpb.TransferLeaseRequest{ Span: reqSpan, Lease: reqLease, PrevLease: status.lease, } } else { leaseReq = &roachpb.RequestLeaseRequest{ Span: reqSpan, Lease: reqLease, PrevLease: status.lease, } } if err := p.requestLeaseAsync(repl, nextLeaseHolder, reqLease, status, leaseReq); err != nil { // We failed to start the asynchronous task. Send a blank NotLeaseHolderError // back to indicate that we have no idea who the range lease holder might // be; we've withdrawn from active duty. llChan <- roachpb.NewError( newNotLeaseHolderError(nil, repl.store.StoreID(), repl.mu.state.Desc)) return llChan } // TODO(andrei): document this subtlety. p.llChans = append(p.llChans, llChan) p.nextLease = reqLease return llChan }