github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvclient/kvcoord/txn_lock_gatekeeper.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 kvcoord
    12  
    13  import (
    14  	"context"
    15  	"sync"
    16  
    17  	"github.com/cockroachdb/cockroach/pkg/kv"
    18  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    19  	"github.com/cockroachdb/errors"
    20  )
    21  
    22  // lockedSender is like a client.Sender but requires the caller to hold the
    23  // TxnCoordSender lock to send requests.
    24  type lockedSender interface {
    25  	// SendLocked sends the batch request and receives a batch response. It
    26  	// requires that the TxnCoordSender lock be held when called, but this lock
    27  	// is not held for the entire duration of the call. Instead, the lock is
    28  	// released immediately before the batch is sent to a lower-level Sender and
    29  	// is re-acquired when the response is returned.
    30  	// WARNING: because the lock is released when calling this method and
    31  	// re-acquired before it returned, callers cannot rely on a single mutual
    32  	// exclusion zone mainted across the call.
    33  	SendLocked(context.Context, roachpb.BatchRequest) (*roachpb.BatchResponse, *roachpb.Error)
    34  }
    35  
    36  // txnLockGatekeeper is a lockedSender that sits at the bottom of the
    37  // TxnCoordSender's interceptor stack and handles unlocking the TxnCoordSender's
    38  // mutex when sending a request and locking the TxnCoordSender's mutex when
    39  // receiving a response. It allows the entire txnInterceptor stack to operate
    40  // under lock without needing to worry about unlocking at the correct time.
    41  type txnLockGatekeeper struct {
    42  	wrapped kv.Sender
    43  	mu      sync.Locker // shared with TxnCoordSender
    44  
    45  	// If set, concurrent requests are allowed. If not set, concurrent requests
    46  	// result in an assertion error. Only leaf transactions are supposed allow
    47  	// concurrent requests - leaves don't restart the transaction and they don't
    48  	// bump the read timestamp through refreshes.
    49  	allowConcurrentRequests bool
    50  	// requestInFlight is set while a request is being processed by the wrapped
    51  	// sender. Used to detect and prevent concurrent txn use.
    52  	requestInFlight bool
    53  }
    54  
    55  // SendLocked implements the lockedSender interface.
    56  func (gs *txnLockGatekeeper) SendLocked(
    57  	ctx context.Context, ba roachpb.BatchRequest,
    58  ) (*roachpb.BatchResponse, *roachpb.Error) {
    59  	// If so configured, protect against concurrent use of the txn. Concurrent
    60  	// requests don't work generally because of races between clients sending
    61  	// requests and the TxnCoordSender restarting the transaction, and also
    62  	// concurrent requests are not compatible with the span refresher in
    63  	// particular since refreshing is invalid if done concurrently with requests
    64  	// in flight whose spans haven't been accounted for.
    65  	//
    66  	// As a special case, allow for async rollbacks and heartbeats to be sent
    67  	// whenever.
    68  	if !gs.allowConcurrentRequests {
    69  		asyncRequest := ba.IsSingleAbortTxnRequest() || ba.IsSingleHeartbeatTxnRequest()
    70  		if !asyncRequest {
    71  			if gs.requestInFlight {
    72  				return nil, roachpb.NewError(
    73  					errors.AssertionFailedf("concurrent txn use detected. ba: %s", ba))
    74  			}
    75  			gs.requestInFlight = true
    76  			defer func() {
    77  				gs.requestInFlight = false
    78  			}()
    79  		}
    80  	}
    81  
    82  	// Note the funky locking here: we unlock for the duration of the call and the
    83  	// lock again.
    84  	gs.mu.Unlock()
    85  	defer gs.mu.Lock()
    86  	return gs.wrapped.Send(ctx, ba)
    87  }