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 }