github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvserver/replica_application_decoder.go (about)

     1  // Copyright 2019 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 kvserver
    12  
    13  import (
    14  	"context"
    15  
    16  	"github.com/cockroachdb/cockroach/pkg/kv/kvserver/apply"
    17  	"github.com/cockroachdb/cockroach/pkg/util/log"
    18  	"github.com/cockroachdb/cockroach/pkg/util/quotapool"
    19  	"github.com/cockroachdb/cockroach/pkg/util/tracing"
    20  	"github.com/opentracing/opentracing-go"
    21  	"go.etcd.io/etcd/raft/raftpb"
    22  )
    23  
    24  // replica_application_*.go files provide concrete implementations of
    25  // the interfaces defined in the storage/apply package:
    26  //
    27  // replica_application_state_machine.go  ->  apply.StateMachine
    28  // replica_application_decoder.go        ->  apply.Decoder
    29  // replica_application_cmd.go            ->  apply.Command         (and variants)
    30  // replica_application_cmd_buf.go        ->  apply.CommandIterator (and variants)
    31  // replica_application_cmd_buf.go        ->  apply.CommandList     (and variants)
    32  //
    33  // These allow Replica to interface with the storage/apply package.
    34  
    35  // replicaDecoder implements the apply.Decoder interface.
    36  //
    37  // The object is capable of decoding committed raft entries into a list of
    38  // replicatedCmd objects (which implement all variants of apply.Command), binding
    39  // these commands to their local proposals, and providing an iterator over these
    40  // commands.
    41  type replicaDecoder struct {
    42  	r      *Replica
    43  	cmdBuf replicatedCmdBuf
    44  }
    45  
    46  // getDecoder returns the Replica's apply.Decoder. The Replica's raftMu
    47  // is held for the entire lifetime of the replicaDecoder.
    48  func (r *Replica) getDecoder() *replicaDecoder {
    49  	d := &r.raftMu.decoder
    50  	d.r = r
    51  	return d
    52  }
    53  
    54  // DecodeAndBind implements the apply.Decoder interface.
    55  func (d *replicaDecoder) DecodeAndBind(ctx context.Context, ents []raftpb.Entry) (bool, error) {
    56  	if err := d.decode(ctx, ents); err != nil {
    57  		return false, err
    58  	}
    59  	anyLocal := d.retrieveLocalProposals(ctx)
    60  	d.createTracingSpans(ctx)
    61  	return anyLocal, nil
    62  }
    63  
    64  // decode decodes the provided entries into the decoder.
    65  func (d *replicaDecoder) decode(ctx context.Context, ents []raftpb.Entry) error {
    66  	for i := range ents {
    67  		ent := &ents[i]
    68  		if err := d.cmdBuf.allocate().decode(ctx, ent); err != nil {
    69  			return err
    70  		}
    71  	}
    72  	return nil
    73  }
    74  
    75  // retrieveLocalProposals binds each of the decoder's commands to their local
    76  // proposals if they were proposed locally. The method also sets the ctx fields
    77  // on all commands.
    78  func (d *replicaDecoder) retrieveLocalProposals(ctx context.Context) (anyLocal bool) {
    79  	d.r.mu.Lock()
    80  	defer d.r.mu.Unlock()
    81  	// Assign all the local proposals first then delete all of them from the map
    82  	// in a second pass. This ensures that we retrieve all proposals correctly
    83  	// even if the applier has multiple entries for the same proposal, in which
    84  	// case the proposal was reproposed (either under its original or a new
    85  	// MaxLeaseIndex) which we handle in a second pass below.
    86  	var it replicatedCmdBufSlice
    87  	for it.init(&d.cmdBuf); it.Valid(); it.Next() {
    88  		cmd := it.cur()
    89  		cmd.proposal = d.r.mu.proposals[cmd.idKey]
    90  		anyLocal = anyLocal || cmd.IsLocal()
    91  	}
    92  	if !anyLocal && d.r.mu.proposalQuota == nil {
    93  		// Fast-path.
    94  		return false
    95  	}
    96  	for it.init(&d.cmdBuf); it.Valid(); it.Next() {
    97  		cmd := it.cur()
    98  		var toRelease *quotapool.IntAlloc
    99  		shouldRemove := cmd.IsLocal() &&
   100  			// If this entry does not have the most up-to-date view of the
   101  			// corresponding proposal's maximum lease index then the proposal
   102  			// must have been reproposed with a higher lease index. (see
   103  			// tryReproposeWithNewLeaseIndex). In that case, there's a newer
   104  			// version of the proposal in the pipeline, so don't remove the
   105  			// proposal from the map. We expect this entry to be rejected by
   106  			// checkForcedErr.
   107  			cmd.raftCmd.MaxLeaseIndex == cmd.proposal.command.MaxLeaseIndex
   108  		if shouldRemove {
   109  			// Delete the proposal from the proposals map. There may be reproposals
   110  			// of the proposal in the pipeline, but those will all have the same max
   111  			// lease index, meaning that they will all be rejected after this entry
   112  			// applies (successfully or otherwise). If tryReproposeWithNewLeaseIndex
   113  			// picks up the proposal on failure, it will re-add the proposal to the
   114  			// proposal map, but this won't affect this replicaApplier.
   115  			//
   116  			// While here, add the proposal's quota size to the quota release queue.
   117  			// We check the proposal map again first to avoid double free-ing quota
   118  			// when reproposals from the same proposal end up in the same entry
   119  			// application batch.
   120  			delete(d.r.mu.proposals, cmd.idKey)
   121  			toRelease = cmd.proposal.quotaAlloc
   122  			cmd.proposal.quotaAlloc = nil
   123  		}
   124  		// At this point we're not guaranteed to have proposalQuota initialized,
   125  		// the same is true for quotaReleaseQueues. Only queue the proposal's
   126  		// quota for release if the proposalQuota is initialized.
   127  		if d.r.mu.proposalQuota != nil {
   128  			d.r.mu.quotaReleaseQueue = append(d.r.mu.quotaReleaseQueue, toRelease)
   129  		}
   130  	}
   131  	return anyLocal
   132  }
   133  
   134  // createTracingSpans creates and assigns a new tracing span for each decoded
   135  // command. If a command was proposed locally, it will be given a tracing span
   136  // that follows from its proposal's span.
   137  func (d *replicaDecoder) createTracingSpans(ctx context.Context) {
   138  	const opName = "raft application"
   139  	var it replicatedCmdBufSlice
   140  	for it.init(&d.cmdBuf); it.Valid(); it.Next() {
   141  		cmd := it.cur()
   142  		if cmd.IsLocal() {
   143  			cmd.ctx, cmd.sp = tracing.ForkCtxSpan(cmd.proposal.ctx, opName)
   144  		} else if cmd.raftCmd.TraceData != nil {
   145  			// The proposal isn't local, and trace data is available. Extract
   146  			// the span context and start a server-side span.
   147  			spanCtx, err := d.r.AmbientContext.Tracer.Extract(
   148  				opentracing.TextMap, opentracing.TextMapCarrier(cmd.raftCmd.TraceData))
   149  			if err != nil {
   150  				log.Errorf(ctx, "unable to extract trace data from raft command: %s", err)
   151  			} else {
   152  				cmd.sp = d.r.AmbientContext.Tracer.StartSpan(
   153  					"raft application", opentracing.FollowsFrom(spanCtx))
   154  				cmd.ctx = opentracing.ContextWithSpan(ctx, cmd.sp)
   155  			}
   156  		} else {
   157  			cmd.ctx, cmd.sp = tracing.ForkCtxSpan(ctx, opName)
   158  		}
   159  	}
   160  }
   161  
   162  // NewCommandIter implements the apply.Decoder interface.
   163  func (d *replicaDecoder) NewCommandIter() apply.CommandIterator {
   164  	it := d.cmdBuf.newIter()
   165  	it.init(&d.cmdBuf)
   166  	return it
   167  }
   168  
   169  // Reset implements the apply.Decoder interface.
   170  func (d *replicaDecoder) Reset() {
   171  	d.cmdBuf.clear()
   172  }