// TestTxnCoordSenderSingleRoundtripTxn checks that a batch which completely // holds the writing portion of a Txn (including EndTransaction) does not // launch a heartbeat goroutine at all. func TestTxnCoordSenderSingleRoundtripTxn(t *testing.T) { defer leaktest.AfterTest(t) stopper := stop.NewStopper() manual := hlc.NewManualClock(0) clock := hlc.NewClock(manual.UnixNano) clock.SetMaxOffset(20) ts := NewTxnCoordSender(senderFn(func(_ context.Context, ba roachpb.BatchRequest) (*roachpb.BatchResponse, *roachpb.Error) { return ba.CreateReply(), nil }), clock, false, nil, stopper) // Stop the stopper manually, prior to trying the transaction. This has the // effect of returning a NodeUnavailableError for any attempts at launching // a heartbeat goroutine. stopper.Stop() var ba roachpb.BatchRequest put := &roachpb.PutRequest{} put.Key = roachpb.Key("test") ba.Add(put) ba.Add(&roachpb.EndTransactionRequest{}) ba.Txn = &roachpb.Transaction{Name: "test"} _, pErr := ts.Send(context.Background(), ba) if pErr != nil { t.Fatal(pErr) } }
// rangeLookup dispatches an RangeLookup request for the given // metadata key to the replicas of the given range. Note that we allow // inconsistent reads when doing range lookups for efficiency. Getting // stale data is not a correctness problem but instead may // infrequently result in additional latency as additional range // lookups may be required. Note also that rangeLookup bypasses the // DistSender's Send() method, so there is no error inspection and // retry logic here; this is not an issue since the lookup performs a // single inconsistent read only. func (ds *DistSender) rangeLookup(key roachpb.Key, options lookupOptions, desc *roachpb.RangeDescriptor) ([]roachpb.RangeDescriptor, error) { ba := roachpb.BatchRequest{} ba.ReadConsistency = roachpb.INCONSISTENT ba.Add(&roachpb.RangeLookupRequest{ RequestHeader: roachpb.RequestHeader{ Key: key, ReadConsistency: roachpb.INCONSISTENT, }, MaxRanges: ds.rangeLookupMaxRanges, ConsiderIntents: options.considerIntents, Reverse: options.useReverseScan, }) replicas := newReplicaSlice(ds.gossip, desc) // TODO(tschottdorf) consider a Trace here, potentially that of the request // that had the cache miss and waits for the result. br, err := ds.sendRPC(nil /* Trace */, desc.RangeID, replicas, rpc.OrderRandom, ba) if err != nil { return nil, err } if err := br.GoError(); err != nil { return nil, err } return br.Responses[0].GetInner().(*roachpb.RangeLookupResponse).Ranges, nil }
// TestBatchError verifies that Range returns an error if a request has an invalid range. func TestBatchError(t *testing.T) { testCases := []struct { req [2]string errMsg string }{ { req: [2]string{"\xff\xff\xff\xff", "a"}, errMsg: "must be less than KeyMax", }, { req: [2]string{"a", "\xff\xff\xff\xff"}, errMsg: "must be less than or equal to KeyMax", }, } for i, c := range testCases { var ba roachpb.BatchRequest ba.Add(&roachpb.ScanRequest{Span: roachpb.Span{Key: roachpb.Key(c.req[0]), EndKey: roachpb.Key(c.req[1])}}) if _, err := Range(ba); !testutils.IsError(err, c.errMsg) { t.Errorf("%d: unexpected error %v", i, err) } } // Test a case where a non-range request has an end key. var ba roachpb.BatchRequest ba.Add(&roachpb.GetRequest{Span: roachpb.Span{Key: roachpb.Key("a"), EndKey: roachpb.Key("b")}}) if _, err := Range(ba); !testutils.IsError(err, "end key specified for non-range operation") { t.Errorf("unexpected error %v", err) } }
// RangeLookup dispatches an RangeLookup request for the given // metadata key to the replicas of the given range. Note that we allow // inconsistent reads when doing range lookups for efficiency. Getting // stale data is not a correctness problem but instead may // infrequently result in additional latency as additional range // lookups may be required. Note also that rangeLookup bypasses the // DistSender's Send() method, so there is no error inspection and // retry logic here; this is not an issue since the lookup performs a // single inconsistent read only. func (ds *DistSender) RangeLookup(key roachpb.RKey, desc *roachpb.RangeDescriptor, considerIntents, useReverseScan bool) ([]roachpb.RangeDescriptor, *roachpb.Error) { ba := roachpb.BatchRequest{} ba.ReadConsistency = roachpb.INCONSISTENT ba.Add(&roachpb.RangeLookupRequest{ Span: roachpb.Span{ // We can interpret the RKey as a Key here since it's a metadata // lookup; those are never local. Key: key.AsRawKey(), }, MaxRanges: ds.rangeLookupMaxRanges, ConsiderIntents: considerIntents, Reverse: useReverseScan, }) replicas := newReplicaSlice(ds.gossip, desc) trace := ds.Tracer.StartSpan("range lookup") defer trace.Finish() // TODO(tschottdorf): Ideally we would use the trace of the request which // caused this lookup instead of a new one. br, err := ds.sendRPC(trace, desc.RangeID, replicas, orderRandom, ba) if err != nil { return nil, err } if br.Error != nil { return nil, br.Error } return br.Responses[0].GetInner().(*roachpb.RangeLookupResponse).Ranges, nil }
// send runs the specified calls synchronously in a single batch and returns // any errors. Returns a nil response for empty input (no requests). func (db *DB) send(maxScanResults int64, readConsistency roachpb.ReadConsistencyType, reqs ...roachpb.Request) (*roachpb.BatchResponse, *roachpb.Error) { if len(reqs) == 0 { return nil, nil } if readConsistency == roachpb.INCONSISTENT { for _, req := range reqs { if req.Method() != roachpb.Get && req.Method() != roachpb.Scan && req.Method() != roachpb.ReverseScan { return nil, roachpb.NewErrorf("method %s not allowed with INCONSISTENT batch", req.Method) } } } ba := roachpb.BatchRequest{} ba.Add(reqs...) ba.MaxScanResults = maxScanResults if db.userPriority != 1 { ba.UserPriority = db.userPriority } ba.ReadConsistency = readConsistency tracing.AnnotateTrace() br, pErr := db.sender.Send(context.TODO(), ba) if pErr != nil { if log.V(1) { log.Infof("failed batch: %s", pErr) } return nil, pErr } return br, nil }
// RangeLookup dispatches an RangeLookup request for the given // metadata key to the replicas of the given range. Note that we allow // inconsistent reads when doing range lookups for efficiency. Getting // stale data is not a correctness problem but instead may // infrequently result in additional latency as additional range // lookups may be required. Note also that rangeLookup bypasses the // DistSender's Send() method, so there is no error inspection and // retry logic here; this is not an issue since the lookup performs a // single inconsistent read only. func (ds *DistSender) RangeLookup(key roachpb.RKey, desc *roachpb.RangeDescriptor, considerIntents, useReverseScan bool) ([]roachpb.RangeDescriptor, *roachpb.Error) { ba := roachpb.BatchRequest{} ba.ReadConsistency = roachpb.INCONSISTENT ba.Add(&roachpb.RangeLookupRequest{ Span: roachpb.Span{ // We can interpret the RKey as a Key here since it's a metadata // lookup; those are never local. Key: key.AsRawKey(), }, MaxRanges: ds.rangeLookupMaxRanges, ConsiderIntents: considerIntents, Reverse: useReverseScan, }) replicas := newReplicaSlice(ds.gossip, desc) // TODO(tschottdorf) consider a Trace here, potentially that of the request // that had the cache miss and waits for the result. br, err := ds.sendRPC(nil /* Trace */, desc.RangeID, replicas, rpc.OrderRandom, ba) if err != nil { return nil, err } if br.Error != nil { return nil, br.Error } return br.Responses[0].GetInner().(*roachpb.RangeLookupResponse).Ranges, nil }
// send runs the specified calls synchronously in a single batch and returns // any errors. Returns a nil response for empty input (no requests). func (db *DB) send(maxScanResults int64, reqs ...roachpb.Request) ( *roachpb.BatchResponse, *roachpb.Error) { if len(reqs) == 0 { return nil, nil } ba := roachpb.BatchRequest{} ba.Add(reqs...) ba.MaxScanResults = maxScanResults if db.userPriority != 1 { ba.UserPriority = db.userPriority } tracing.AnnotateTrace() br, pErr := db.sender.Send(context.TODO(), ba) if pErr != nil { if log.V(1) { log.Infof("failed batch: %s", pErr) } return nil, pErr } return br, nil }
// RangeLookup implements the RangeDescriptorDB interface. // RangeLookup dispatches a RangeLookup request for the given metadata // key to the replicas of the given range. Note that we allow // inconsistent reads when doing range lookups for efficiency. Getting // stale data is not a correctness problem but instead may // infrequently result in additional latency as additional range // lookups may be required. Note also that rangeLookup bypasses the // DistSender's Send() method, so there is no error inspection and // retry logic here; this is not an issue since the lookup performs a // single inconsistent read only. func (ds *DistSender) RangeLookup( key roachpb.RKey, desc *roachpb.RangeDescriptor, considerIntents, useReverseScan bool, ) ([]roachpb.RangeDescriptor, []roachpb.RangeDescriptor, *roachpb.Error) { ba := roachpb.BatchRequest{} ba.ReadConsistency = roachpb.INCONSISTENT ba.Add(&roachpb.RangeLookupRequest{ Span: roachpb.Span{ // We can interpret the RKey as a Key here since it's a metadata // lookup; those are never local. Key: key.AsRawKey(), }, MaxRanges: ds.rangeLookupMaxRanges, ConsiderIntents: considerIntents, Reverse: useReverseScan, }) replicas := newReplicaSlice(ds.gossip, desc) replicas.Shuffle() // TODO(tschottdorf): Ideally we would use the trace of the request which // caused this lookup. _ = context.TODO() br, err := ds.sendRPC(ds.Ctx, desc.RangeID, replicas, ba) if err != nil { return nil, nil, roachpb.NewError(err) } if br.Error != nil { return nil, nil, br.Error } resp := br.Responses[0].GetInner().(*roachpb.RangeLookupResponse) return resp.Ranges, resp.PrefetchedRanges, nil }
func testPut() roachpb.BatchRequest { var ba roachpb.BatchRequest ba.Timestamp = testTS put := &roachpb.PutRequest{} put.Key = testKey ba.Add(put) return ba }
func (tc *TxnCoordSender) heartbeat(id string, trace *tracer.Trace, ctx context.Context) bool { tc.Lock() proceed := true txnMeta := tc.txns[id] // Before we send a heartbeat, determine whether this transaction // should be considered abandoned. If so, exit heartbeat. if txnMeta.hasClientAbandonedCoord(tc.clock.PhysicalNow()) { // TODO(tschottdorf): should we be more proactive here? // The client might be continuing the transaction // through another coordinator, but in the most likely // case it's just gone and the open transaction record // could block concurrent operations. if log.V(1) { log.Infof("transaction %s abandoned; stopping heartbeat", txnMeta.txn) } proceed = false } // txnMeta.txn is possibly replaced concurrently, // so grab a copy before unlocking. txn := txnMeta.txn tc.Unlock() if !proceed { return false } hb := &roachpb.HeartbeatTxnRequest{} hb.Key = txn.Key ba := roachpb.BatchRequest{} ba.Timestamp = tc.clock.Now() ba.CmdID = ba.GetOrCreateCmdID(ba.Timestamp.WallTime) ba.Txn = txn.Clone() ba.Add(hb) epochEnds := trace.Epoch("heartbeat") _, err := tc.wrapped.Send(ctx, ba) epochEnds() // If the transaction is not in pending state, then we can stop // the heartbeat. It's either aborted or committed, and we resolve // write intents accordingly. if err != nil { log.Warningf("heartbeat to %s failed: %s", txn, err) } // TODO(bdarnell): once we have gotten a heartbeat response with // Status != PENDING, future heartbeats are useless. However, we // need to continue the heartbeatLoop until the client either // commits or abandons the transaction. We could save a little // pointless work by restructuring this loop to stop sending // heartbeats between the time that the transaction is aborted and // the client finds out. Furthermore, we could use this information // to send TransactionAbortedErrors to the client so it can restart // immediately instead of running until its EndTransaction. return true }
// TestBatchPrevNext tests batch.{Prev,Next}. func TestBatchPrevNext(t *testing.T) { defer leaktest.AfterTest(t)() loc := func(s string) string { return string(keys.RangeDescriptorKey(roachpb.RKey(s))) } span := func(strs ...string) []roachpb.Span { var r []roachpb.Span for i, str := range strs { if i%2 == 0 { r = append(r, roachpb.Span{Key: roachpb.Key(str)}) } else { r[len(r)-1].EndKey = roachpb.Key(str) } } return r } max, min := string(roachpb.RKeyMax), string(roachpb.RKeyMin) abc := span("a", "", "b", "", "c", "") testCases := []struct { spans []roachpb.Span key, expFW, expBW string }{ {spans: span("a", "c", "b", ""), key: "b", expFW: "b", expBW: "b"}, {spans: span("a", "c", "b", ""), key: "a", expFW: "a", expBW: "a"}, {spans: span("a", "c", "d", ""), key: "c", expFW: "d", expBW: "c"}, {spans: span("a", "c\x00", "d", ""), key: "c", expFW: "c", expBW: "c"}, {spans: abc, key: "b", expFW: "b", expBW: "b"}, {spans: abc, key: "b\x00", expFW: "c", expBW: "b\x00"}, {spans: abc, key: "bb", expFW: "c", expBW: "b"}, {spans: span(), key: "whatevs", expFW: max, expBW: min}, {spans: span(loc("a"), loc("c")), key: "c", expFW: "c", expBW: "c"}, {spans: span(loc("a"), loc("c")), key: "c\x00", expFW: max, expBW: "c\x00"}, } for i, test := range testCases { var ba roachpb.BatchRequest for _, span := range test.spans { args := &roachpb.ScanRequest{} args.Key, args.EndKey = span.Key, span.EndKey ba.Add(args) } if next, err := next(ba, roachpb.RKey(test.key)); err != nil { t.Errorf("%d: %v", i, err) } else if !bytes.Equal(next, roachpb.Key(test.expFW)) { t.Errorf("%d: next: expected %q, got %q", i, test.expFW, next) } if prev, err := prev(ba, roachpb.RKey(test.key)); err != nil { t.Errorf("%d: %v", i, err) } else if !bytes.Equal(prev, roachpb.Key(test.expBW)) { t.Errorf("%d: prev: expected %q, got %q", i, test.expBW, prev) } } }
// process iterates through all keys in a replica's range, calling the garbage // collector for each key and associated set of values. GC'd keys are batched // into GC calls. Extant intents are resolved if intents are older than // intentAgeThreshold. The transaction and abort cache records are also // scanned and old entries evicted. During normal operation, both of these // records are cleaned up when their respective transaction finishes, so the // amount of work done here is expected to be small. // // Some care needs to be taken to avoid cyclic recreation of entries during GC: // * a Push initiated due to an intent may recreate a transaction entry // * resolving an intent may write a new abort cache entry // * obtaining the transaction for a abort cache entry requires a Push // // The following order is taken below: // 1) collect all intents with sufficiently old txn record // 2) collect these intents' transactions // 3) scan the transaction table, collecting abandoned or completed txns // 4) push all of these transactions (possibly recreating entries) // 5) resolve all intents (unless the txn is still PENDING), which will recreate // abort cache entries (but with the txn timestamp; i.e. likely gc'able) // 6) scan the abort cache table for old entries // 7) push these transactions (again, recreating txn entries). // 8) send a GCRequest. func (gcq *gcQueue) process( ctx context.Context, now hlc.Timestamp, repl *Replica, sysCfg config.SystemConfig, ) error { snap := repl.store.Engine().NewSnapshot() desc := repl.Desc() defer snap.Close() // Lookup the GC policy for the zone containing this key range. zone, err := sysCfg.GetZoneConfigForKey(desc.StartKey) if err != nil { return errors.Errorf("could not find zone config for range %s: %s", repl, err) } gcKeys, info, err := RunGC(ctx, desc, snap, now, zone.GC, func(now hlc.Timestamp, txn *roachpb.Transaction, typ roachpb.PushTxnType) { pushTxn(gcq.store.DB(), now, txn, typ) }, func(intents []roachpb.Intent, poison bool, wait bool) error { return repl.store.intentResolver.resolveIntents(ctx, intents, poison, wait) }) if err != nil { return err } gcq.eventLog.VInfof(true, "completed with stats %+v", info) var ba roachpb.BatchRequest var gcArgs roachpb.GCRequest // TODO(tschottdorf): This is one of these instances in which we want // to be more careful that the request ends up on the correct Replica, // and we might have to worry about mixing range-local and global keys // in a batch which might end up spanning Ranges by the time it executes. gcArgs.Key = desc.StartKey.AsRawKey() gcArgs.EndKey = desc.EndKey.AsRawKey() gcArgs.Keys = gcKeys gcArgs.Threshold = info.Threshold // Technically not needed since we're talking directly to the Range. ba.RangeID = desc.RangeID ba.Timestamp = now ba.Add(&gcArgs) if _, pErr := repl.Send(ctx, ba); pErr != nil { return pErr.GoError() } return nil }
// SendWrappedWith is a convenience function which wraps the request in a batch // and sends it via the provided Sender at the given timestamp. It returns the // unwrapped response or an error. It's valid to pass a `nil` context; // context.Background() is used in that case. func SendWrappedWith(sender Sender, ctx context.Context, h roachpb.Header, args roachpb.Request) (roachpb.Response, error) { if ctx == nil { ctx = context.Background() } ba := roachpb.BatchRequest{} ba.Header = h ba.Add(args) br, pErr := sender.Send(ctx, ba) if err := pErr.GoError(); err != nil { return nil, err } unwrappedReply := br.Responses[0].GetInner() unwrappedReply.Header().Txn = br.Txn return unwrappedReply, nil }
func (tc *TxnCoordSender) clientHasAbandoned(txnID uuid.UUID) { tc.Lock() txnMeta := tc.txns[txnID] var intentSpans []roachpb.Span // TODO(tschottdorf): should we be more proactive here? // The client might be continuing the transaction // through another coordinator, but in the most likely // case it's just gone and the open transaction record // could block concurrent operations. if log.V(1) { log.Infof("transaction %s abandoned; stopping heartbeat", txnMeta.txn) } // Grab the intents here to avoid potential race. intentSpans = collectIntentSpans(txnMeta.keys) txnMeta.keys.Clear() // txnMeta.txn is possibly replaced concurrently, // so grab a copy before unlocking. txn := txnMeta.txn.Clone() tc.Unlock() ba := roachpb.BatchRequest{} ba.Txn = &txn // Actively abort the transaction and its intents since we assume it's abandoned. et := &roachpb.EndTransactionRequest{ Span: roachpb.Span{ Key: txn.Key, }, Commit: false, IntentSpans: intentSpans, } ba.Add(et) tc.stopper.RunAsyncTask(func() { // Use the wrapped sender since the normal Sender // does not allow clients to specify intents. // TODO(tschottdorf): not using the existing context here since that // leads to use-after-finish of the contained trace. Should fork off // before the goroutine. if _, pErr := tc.wrapped.Send(context.Background(), ba); pErr != nil { if log.V(1) { log.Warningf("abort due to inactivity failed for %s: %s ", txn, pErr) } } }) }
// rangeLookup implements the rangeDescriptorDB interface. It looks up // the descriptors for the given (meta) key. func (ls *LocalSender) rangeLookup(key roachpb.Key, options lookupOptions, _ *roachpb.RangeDescriptor) ([]roachpb.RangeDescriptor, error) { ba := roachpb.BatchRequest{} ba.ReadConsistency = roachpb.INCONSISTENT ba.Add(&roachpb.RangeLookupRequest{ RequestHeader: roachpb.RequestHeader{ Key: key, }, MaxRanges: 1, ConsiderIntents: options.considerIntents, Reverse: options.useReverseScan, }) br, pErr := ls.Send(context.Background(), ba) if pErr != nil { return nil, pErr.GoError() } return br.Responses[0].GetInner().(*roachpb.RangeLookupResponse).Ranges, nil }
// RangeLookup implements the RangeDescriptorDB interface. It looks up // the descriptors for the given (meta) key. func (ls *Stores) RangeLookup(key roachpb.RKey, _ *roachpb.RangeDescriptor, considerIntents, useReverseScan bool) ([]roachpb.RangeDescriptor, *roachpb.Error) { ba := roachpb.BatchRequest{} ba.ReadConsistency = roachpb.INCONSISTENT ba.Add(&roachpb.RangeLookupRequest{ Span: roachpb.Span{ // key is a meta key, so it's guaranteed not local-prefixed. Key: key.AsRawKey(), }, MaxRanges: 1, ConsiderIntents: considerIntents, Reverse: useReverseScan, }) br, pErr := ls.Send(context.Background(), ba) if pErr != nil { return nil, pErr } return br.Responses[0].GetInner().(*roachpb.RangeLookupResponse).Ranges, nil }
// tryAsyncAbort (synchronously) grabs a copy of the txn proto and the intents // (which it then clears from txnMeta), and asynchronously tries to abort the // transaction. func (tc *TxnCoordSender) tryAsyncAbort(txnID uuid.UUID) { tc.Lock() txnMeta := tc.txns[txnID] // Clone the intents and the txn to avoid data races. txnMeta.keys = append([]roachpb.Span(nil), txnMeta.keys...) roachpb.MergeSpans(&txnMeta.keys) intentSpans := txnMeta.keys txnMeta.keys = nil txn := txnMeta.txn.Clone() tc.Unlock() // Since we don't hold the lock continuously, it's possible that two aborts // raced here. That's fine (and probably better than the alternative, which // is missing new intents sometimes). if txn.Status != roachpb.PENDING { return } ba := roachpb.BatchRequest{} ba.Txn = &txn et := &roachpb.EndTransactionRequest{ Span: roachpb.Span{ Key: txn.Key, }, Commit: false, IntentSpans: intentSpans, } ba.Add(et) if err := tc.stopper.RunAsyncTask(func() { // Use the wrapped sender since the normal Sender does not allow // clients to specify intents. // TODO(tschottdorf): not using the existing context here since that // leads to use-after-finish of the contained trace. Should fork off // before the goroutine. if _, pErr := tc.wrapped.Send(context.Background(), ba); pErr != nil { if log.V(1) { log.Warningf("abort due to inactivity failed for %s: %s ", txn, pErr) } } }); err != nil { log.Warning(err) } }
func (tc *TxnCoordSender) heartbeat(ctx context.Context, txnID uuid.UUID) bool { tc.Lock() txnMeta := tc.txns[txnID] txn := txnMeta.txn.Clone() tc.Unlock() // Before we send a heartbeat, determine whether this transaction should be // considered abandoned. If so, exit heartbeat. If ctx.Done() is not nil, then // it is a cancellable Context and we skip this check and use the ctx lifetime // instead of a timeout. if ctx.Done() == nil && txnMeta.hasClientAbandonedCoord(tc.clock.PhysicalNow()) { tc.clientHasAbandoned(txnID) return false } ba := roachpb.BatchRequest{} ba.Txn = &txn hb := &roachpb.HeartbeatTxnRequest{ Now: tc.clock.Now(), } hb.Key = txn.Key ba.Add(hb) log.Trace(ctx, "heartbeat") _, err := tc.wrapped.Send(ctx, ba) // If the transaction is not in pending state, then we can stop // the heartbeat. It's either aborted or committed, and we resolve // write intents accordingly. if err != nil { log.Warningf("heartbeat to %s failed: %s", txn, err) } // TODO(bdarnell): once we have gotten a heartbeat response with // Status != PENDING, future heartbeats are useless. However, we // need to continue the heartbeatLoop until the client either // commits or abandons the transaction. We could save a little // pointless work by restructuring this loop to stop sending // heartbeats between the time that the transaction is aborted and // the client finds out. Furthermore, we could use this information // to send TransactionAbortedErrors to the client so it can restart // immediately instead of running until its EndTransaction. return true }
func TestStoreExecuteNoop(t *testing.T) { defer leaktest.AfterTest(t) store, _, stopper := createTestStore(t) defer stopper.Stop() ba := roachpb.BatchRequest{} ba.Key = nil // intentional ba.RangeID = 1 ba.Replica = roachpb.ReplicaDescriptor{StoreID: store.StoreID()} ba.Add(&roachpb.GetRequest{RequestHeader: roachpb.RequestHeader{Key: roachpb.Key("a")}}) ba.Add(&roachpb.NoopRequest{}) br, pErr := store.Send(context.Background(), ba) if pErr != nil { t.Error(pErr) } reply := br.Responses[1].GetInner() if _, ok := reply.(*roachpb.NoopResponse); !ok { t.Errorf("expected *roachpb.NoopResponse, got %T", reply) } }
// send runs the specified calls synchronously in a single batch and returns // any errors. Returns a nil response for empty input (no requests). func (db *DB) send(reqs ...roachpb.Request) (*roachpb.BatchResponse, *roachpb.Error) { if len(reqs) == 0 { return nil, nil } ba := roachpb.BatchRequest{} ba.Add(reqs...) if ba.UserPriority == 0 && db.userPriority != 1 { ba.UserPriority = db.userPriority } br, pErr := db.sender.Send(context.TODO(), ba) if pErr != nil { if log.V(1) { log.Infof("failed batch: %s", pErr) } return nil, pErr } return br, nil }
// send runs the specified calls synchronously in a single batch and // returns any errors. func (db *DB) send(reqs ...roachpb.Request) (*roachpb.BatchResponse, *roachpb.Error) { if len(reqs) == 0 { return &roachpb.BatchResponse{}, nil } ba := roachpb.BatchRequest{} ba.Add(reqs...) if ba.UserPriority == nil && db.userPriority != 0 { ba.UserPriority = proto.Int32(db.userPriority) } resetClientCmdID(&ba) br, pErr := db.sender.Send(context.TODO(), ba) if pErr != nil { if log.V(1) { log.Infof("failed batch: %s", pErr) } return nil, pErr } return br, nil }
// TestTxnCoordSenderErrorWithIntent validates that if a transactional request // returns an error but also indicates a Writing transaction, the coordinator // tracks it just like a successful request. func TestTxnCoordSenderErrorWithIntent(t *testing.T) { defer leaktest.AfterTest(t) stopper := stop.NewStopper() manual := hlc.NewManualClock(0) clock := hlc.NewClock(manual.UnixNano) clock.SetMaxOffset(20) ts := NewTxnCoordSender(senderFn(func(_ context.Context, ba roachpb.BatchRequest) (*roachpb.BatchResponse, *roachpb.Error) { txn := ba.Txn.Clone() txn.Writing = true pErr := roachpb.NewError(roachpb.NewTransactionRetryError()) pErr.SetTxn(txn) return nil, pErr }), clock, false, nil, stopper) defer stopper.Stop() var ba roachpb.BatchRequest key := roachpb.Key("test") ba.Add(&roachpb.BeginTransactionRequest{Span: roachpb.Span{Key: key}}) ba.Add(&roachpb.PutRequest{Span: roachpb.Span{Key: key}}) ba.Add(&roachpb.EndTransactionRequest{}) ba.Txn = &roachpb.Transaction{Name: "test"} if _, pErr := ts.Send(context.Background(), ba); !testutils.IsPError(pErr, "retry txn") { t.Fatalf("unexpected error: %v", pErr) } defer teardownHeartbeats(ts) ts.Lock() defer ts.Unlock() if len(ts.txns) != 1 { t.Fatalf("expected transaction to be tracked") } }
// TestTxnCoordSenderSingleRoundtripTxn checks that a batch which completely // holds the writing portion of a Txn (including EndTransaction) does not // launch a heartbeat goroutine at all. func TestTxnCoordSenderSingleRoundtripTxn(t *testing.T) { defer leaktest.AfterTest(t)() stopper := stop.NewStopper() manual := hlc.NewManualClock(0) clock := hlc.NewClock(manual.UnixNano) clock.SetMaxOffset(20) ts := NewTxnCoordSender(senderFn(func(_ context.Context, ba roachpb.BatchRequest) (*roachpb.BatchResponse, *roachpb.Error) { br := ba.CreateReply() txnClone := ba.Txn.Clone() br.Txn = &txnClone br.Txn.Writing = true return br, nil }), clock, false, tracing.NewTracer(), stopper, NewTxnMetrics(metric.NewRegistry())) // Stop the stopper manually, prior to trying the transaction. This has the // effect of returning a NodeUnavailableError for any attempts at launching // a heartbeat goroutine. stopper.Stop() var ba roachpb.BatchRequest key := roachpb.Key("test") ba.Add(&roachpb.BeginTransactionRequest{Span: roachpb.Span{Key: key}}) ba.Add(&roachpb.PutRequest{Span: roachpb.Span{Key: key}}) ba.Add(&roachpb.EndTransactionRequest{}) ba.Txn = &roachpb.Transaction{Name: "test"} _, pErr := ts.Send(context.Background(), ba) if pErr != nil { t.Fatal(pErr) } }
// TestTxnCoordSenderErrorWithIntent validates that if a transactional request // returns an error but also indicates a Writing transaction, the coordinator // tracks it just like a successful request. func TestTxnCoordSenderErrorWithIntent(t *testing.T) { defer leaktest.AfterTest(t)() stopper := stop.NewStopper() defer stopper.Stop() manual := hlc.NewManualClock(0) clock := hlc.NewClock(manual.UnixNano) clock.SetMaxOffset(20) testCases := []struct { roachpb.Error errMsg string }{ {*roachpb.NewError(roachpb.NewTransactionRetryError()), "retry txn"}, {*roachpb.NewError(roachpb.NewTransactionPushError(roachpb.Transaction{ TxnMeta: enginepb.TxnMeta{ ID: uuid.NewV4(), }})), "failed to push"}, {*roachpb.NewErrorf("testError"), "testError"}, } for i, test := range testCases { func() { senderFunc := func(_ context.Context, ba roachpb.BatchRequest) (*roachpb.BatchResponse, *roachpb.Error) { txn := ba.Txn.Clone() txn.Writing = true pErr := &roachpb.Error{} *pErr = test.Error pErr.SetTxn(&txn) return nil, pErr } ctx := tracing.WithTracer(context.Background(), tracing.NewTracer()) ts := NewTxnCoordSender(ctx, senderFn(senderFunc), clock, false, stopper, MakeTxnMetrics()) var ba roachpb.BatchRequest key := roachpb.Key("test") ba.Add(&roachpb.BeginTransactionRequest{Span: roachpb.Span{Key: key}}) ba.Add(&roachpb.PutRequest{Span: roachpb.Span{Key: key}}) ba.Add(&roachpb.EndTransactionRequest{}) ba.Txn = &roachpb.Transaction{Name: "test"} _, pErr := ts.Send(context.Background(), ba) if !testutils.IsPError(pErr, test.errMsg) { t.Errorf("%d: error did not match %s: %v", i, test.errMsg, pErr) } defer teardownHeartbeats(ts) ts.Lock() defer ts.Unlock() if len(ts.txns) != 1 { t.Errorf("%d: expected transaction to be tracked", i) } }() } }
// TestMultiRangeSplitEndTransaction verifies that when a chunk of batch looks // like it's going to be dispatched to more than one range, it will be split // up if it it contains EndTransaction. func TestMultiRangeSplitEndTransaction(t *testing.T) { defer leaktest.AfterTest(t) g, s := makeTestGossip(t) defer s() testCases := []struct { put1, put2, et roachpb.Key exp [][]roachpb.Method }{ { // Everything hits the first range, so we get a 1PC txn. roachpb.Key("a1"), roachpb.Key("a2"), roachpb.Key("a3"), [][]roachpb.Method{{roachpb.Put, roachpb.Put, roachpb.EndTransaction}}, }, { // Only EndTransaction hits the second range. roachpb.Key("a1"), roachpb.Key("a2"), roachpb.Key("b"), [][]roachpb.Method{{roachpb.Put, roachpb.Put}, {roachpb.EndTransaction}}, }, { // One write hits the second range, so EndTransaction has to be split off. // In this case, going in the usual order without splitting off // would actually be fine, but it doesn't seem worth optimizing at // this point. roachpb.Key("a1"), roachpb.Key("b1"), roachpb.Key("a1"), [][]roachpb.Method{{roachpb.Put, roachpb.Noop}, {roachpb.Noop, roachpb.Put}, {roachpb.EndTransaction}}, }, { // Both writes go to the second range, but not EndTransaction. roachpb.Key("b1"), roachpb.Key("b2"), roachpb.Key("a1"), [][]roachpb.Method{{roachpb.Put, roachpb.Put}, {roachpb.EndTransaction}}, }, } if err := g.SetNodeDescriptor(&roachpb.NodeDescriptor{NodeID: 1}); err != nil { t.Fatal(err) } nd := &roachpb.NodeDescriptor{ NodeID: roachpb.NodeID(1), Address: util.MakeUnresolvedAddr(testAddress.Network(), testAddress.String()), } if err := g.AddInfoProto(gossip.MakeNodeIDKey(roachpb.NodeID(1)), nd, time.Hour); err != nil { t.Fatal(err) } // Fill mockRangeDescriptorDB with two descriptors. var descriptor1 = roachpb.RangeDescriptor{ RangeID: 1, StartKey: roachpb.RKeyMin, EndKey: roachpb.RKey("b"), Replicas: []roachpb.ReplicaDescriptor{ { NodeID: 1, StoreID: 1, }, }, } var descriptor2 = roachpb.RangeDescriptor{ RangeID: 2, StartKey: roachpb.RKey("b"), EndKey: roachpb.RKeyMax, Replicas: []roachpb.ReplicaDescriptor{ { NodeID: 1, StoreID: 1, }, }, } descDB := mockRangeDescriptorDB(func(key roachpb.RKey, _, _ bool) ([]roachpb.RangeDescriptor, *roachpb.Error) { desc := descriptor1 if !key.Less(roachpb.RKey("b")) { desc = descriptor2 } return []roachpb.RangeDescriptor{desc}, nil }) for _, test := range testCases { var act [][]roachpb.Method var testFn rpcSendFn = func(_ rpc.Options, method string, addrs []net.Addr, ga func(addr net.Addr) proto.Message, _ func() proto.Message, _ *rpc.Context) ([]proto.Message, error) { ba := ga(testAddress).(*roachpb.BatchRequest) var cur []roachpb.Method for _, union := range ba.Requests { cur = append(cur, union.GetInner().Method()) } act = append(act, cur) return []proto.Message{ba.CreateReply()}, nil } ctx := &DistSenderContext{ RPCSend: testFn, RangeDescriptorDB: descDB, } ds := NewDistSender(ctx, g) // Send a batch request containing two puts. var ba roachpb.BatchRequest ba.Txn = &roachpb.Transaction{Name: "test"} val := roachpb.MakeValueFromString("val") ba.Add(roachpb.NewPut(roachpb.Key(test.put1), val).(*roachpb.PutRequest)) ba.Add(roachpb.NewPut(roachpb.Key(test.put2), val).(*roachpb.PutRequest)) ba.Add(&roachpb.EndTransactionRequest{Span: roachpb.Span{Key: test.et}}) _, pErr := ds.Send(context.Background(), ba) if err := pErr.GoError(); err != nil { t.Fatal(err) } if !reflect.DeepEqual(test.exp, act) { t.Fatalf("expected %v, got %v", test.exp, act) } } }
// TestTruncateWithSpanAndDescriptor verifies that a batch request is truncated with a // range span and the range of a descriptor found in cache. func TestTruncateWithSpanAndDescriptor(t *testing.T) { defer leaktest.AfterTest(t) g, s := makeTestGossip(t) defer s() g.SetNodeID(1) if err := g.SetNodeDescriptor(&roachpb.NodeDescriptor{NodeID: 1}); err != nil { t.Fatal(err) } nd := &roachpb.NodeDescriptor{ NodeID: roachpb.NodeID(1), Address: util.MakeUnresolvedAddr(testAddress.Network(), testAddress.String()), } if err := g.AddInfoProto(gossip.MakeNodeIDKey(roachpb.NodeID(1)), nd, time.Hour); err != nil { t.Fatal(err) } // Fill mockRangeDescriptorDB with two descriptors. When a // range descriptor is looked up by key "b", return the second // descriptor whose range is ["a", "c") and partially overlaps // with the first descriptor's range. var descriptor1 = roachpb.RangeDescriptor{ RangeID: 1, StartKey: roachpb.RKeyMin, EndKey: roachpb.RKey("b"), Replicas: []roachpb.ReplicaDescriptor{ { NodeID: 1, StoreID: 1, }, }, } var descriptor2 = roachpb.RangeDescriptor{ RangeID: 2, StartKey: roachpb.RKey("a"), EndKey: roachpb.RKey("c"), Replicas: []roachpb.ReplicaDescriptor{ { NodeID: 1, StoreID: 1, }, }, } descDB := mockRangeDescriptorDB(func(key roachpb.RKey, _, _ bool) ([]roachpb.RangeDescriptor, *roachpb.Error) { desc := descriptor1 if key.Equal(roachpb.RKey("b")) { desc = descriptor2 } return []roachpb.RangeDescriptor{desc}, nil }) // Define our rpcSend stub which checks the span of the batch // requests. The first request should be the point request on // "a". The second request should be on "b". first := true var testFn rpcSendFn = func(_ rpc.Options, method string, addrs []net.Addr, getArgs func(addr net.Addr) proto.Message, getReply func() proto.Message, _ *rpc.Context) ([]proto.Message, error) { if method != "Node.Batch" { return nil, util.Errorf("unexpected method %v", method) } ba := getArgs(testAddress).(*roachpb.BatchRequest) rs := keys.Range(*ba) if first { if !(rs.Key.Equal(roachpb.RKey("a")) && rs.EndKey.Equal(roachpb.RKey("a").Next())) { t.Errorf("Unexpected span [%s,%s)", rs.Key, rs.EndKey) } first = false } else { if !(rs.Key.Equal(roachpb.RKey("b")) && rs.EndKey.Equal(roachpb.RKey("b").Next())) { t.Errorf("Unexpected span [%s,%s)", rs.Key, rs.EndKey) } } batchReply := getReply().(*roachpb.BatchResponse) reply := &roachpb.PutResponse{} batchReply.Add(reply) return []proto.Message{batchReply}, nil } ctx := &DistSenderContext{ RPCSend: testFn, RangeDescriptorDB: descDB, } ds := NewDistSender(ctx, g) // Send a batch request contains two puts. In the first // attempt, the range of the descriptor found in the cache is // ["a", "b"). The request is truncated to contain only the put // on "a". // // In the second attempt, The range of the descriptor found in // the cache is ["a", c"), but the put on "a" will not be // resent. The request is truncated to contain only the put on "b". ba := roachpb.BatchRequest{} ba.Txn = &roachpb.Transaction{Name: "test"} val := roachpb.MakeValueFromString("val") ba.Add(roachpb.NewPut(keys.RangeTreeNodeKey(roachpb.RKey("a")), val).(*roachpb.PutRequest)) ba.Add(roachpb.NewPut(keys.RangeTreeNodeKey(roachpb.RKey("b")), val).(*roachpb.PutRequest)) _, pErr := ds.Send(context.Background(), ba) if err := pErr.GoError(); err != nil { t.Fatal(err) } }
func TestTruncate(t *testing.T) { defer leaktest.AfterTest(t)() loc := func(s string) string { return string(keys.RangeDescriptorKey(roachpb.RKey(s))) } locPrefix := func(s string) string { return string(keys.MakeRangeKeyPrefix(roachpb.RKey(s))) } testCases := []struct { keys [][2]string expKeys [][2]string from, to string desc [2]string // optional, defaults to {from,to} err string }{ { // Keys inside of active range. keys: [][2]string{{"a", "q"}, {"c"}, {"b, e"}, {"q"}}, expKeys: [][2]string{{"a", "q"}, {"c"}, {"b, e"}, {"q"}}, from: "a", to: "q\x00", }, { // Keys outside of active range. keys: [][2]string{{"a"}, {"a", "b"}, {"q"}, {"q", "z"}}, expKeys: [][2]string{{}, {}, {}, {}}, from: "b", to: "q", }, { // Range-local keys inside of active range. keys: [][2]string{{loc("b")}, {loc("c")}}, expKeys: [][2]string{{loc("b")}, {loc("c")}}, from: "b", to: "e", }, { // Range-local key outside of active range. keys: [][2]string{{loc("a")}}, expKeys: [][2]string{{}}, from: "b", to: "e", }, { // Range-local range contained in active range. keys: [][2]string{{loc("b"), loc("e") + "\x00"}}, expKeys: [][2]string{{loc("b"), loc("e") + "\x00"}}, from: "b", to: "e\x00", }, { // Range-local range not contained in active range. keys: [][2]string{{loc("a"), loc("b")}}, expKeys: [][2]string{{}}, from: "c", to: "e", }, { // Range-local range not contained in active range. keys: [][2]string{{loc("a"), locPrefix("b")}, {loc("e"), loc("f")}}, expKeys: [][2]string{{}, {}}, from: "b", to: "e", }, { // Range-local range partially contained in active range. keys: [][2]string{{loc("a"), loc("b")}}, expKeys: [][2]string{{loc("a"), locPrefix("b")}}, from: "a", to: "b", }, { // Range-local range partially contained in active range. keys: [][2]string{{loc("a"), loc("b")}}, expKeys: [][2]string{{locPrefix("b"), loc("b")}}, from: "b", to: "e", }, { // Range-local range contained in active range. keys: [][2]string{{locPrefix("b"), loc("b")}}, expKeys: [][2]string{{locPrefix("b"), loc("b")}}, from: "b", to: "c", }, { // Mixed range-local vs global key range. keys: [][2]string{{loc("c"), "d\x00"}}, from: "b", to: "e", err: "local key mixed with global key", }, { // Key range touching and intersecting active range. keys: [][2]string{{"a", "b"}, {"a", "c"}, {"p", "q"}, {"p", "r"}, {"a", "z"}}, expKeys: [][2]string{{}, {"b", "c"}, {"p", "q"}, {"p", "q"}, {"b", "q"}}, from: "b", to: "q", }, // Active key range is intersection of descriptor and [from,to). { keys: [][2]string{{"c", "q"}}, expKeys: [][2]string{{"d", "p"}}, from: "a", to: "z", desc: [2]string{"d", "p"}, }, { keys: [][2]string{{"c", "q"}}, expKeys: [][2]string{{"d", "p"}}, from: "d", to: "p", desc: [2]string{"a", "z"}, }, } for i, test := range testCases { goldenOriginal := roachpb.BatchRequest{} for _, ks := range test.keys { if len(ks[1]) > 0 { goldenOriginal.Add(&roachpb.ResolveIntentRangeRequest{ Span: roachpb.Span{Key: roachpb.Key(ks[0]), EndKey: roachpb.Key(ks[1])}, IntentTxn: roachpb.TxnMeta{ID: uuid.NewV4()}, }) } else { goldenOriginal.Add(&roachpb.GetRequest{ Span: roachpb.Span{Key: roachpb.Key(ks[0])}, }) } } original := roachpb.BatchRequest{Requests: make([]roachpb.RequestUnion, len(goldenOriginal.Requests))} for i, request := range goldenOriginal.Requests { original.Requests[i].SetValue(request.GetInner().ShallowCopy()) } desc := &roachpb.RangeDescriptor{ StartKey: roachpb.RKey(test.desc[0]), EndKey: roachpb.RKey(test.desc[1]), } if len(desc.StartKey) == 0 { desc.StartKey = roachpb.RKey(test.from) } if len(desc.EndKey) == 0 { desc.EndKey = roachpb.RKey(test.to) } rs := roachpb.RSpan{Key: roachpb.RKey(test.from), EndKey: roachpb.RKey(test.to)} rs, err := rs.Intersect(desc) if err != nil { t.Errorf("%d: intersection failure: %v", i, err) continue } ba, num, err := truncate(original, rs) if err != nil || test.err != "" { if test.err == "" || !testutils.IsError(err, test.err) { t.Errorf("%d: %v (expected: %s)", i, err, test.err) } continue } var reqs int for j, arg := range ba.Requests { req := arg.GetInner() if h := req.Header(); !bytes.Equal(h.Key, roachpb.Key(test.expKeys[j][0])) || !bytes.Equal(h.EndKey, roachpb.Key(test.expKeys[j][1])) { t.Errorf("%d.%d: range mismatch: actual [%q,%q), wanted [%q,%q)", i, j, h.Key, h.EndKey, test.expKeys[j][0], test.expKeys[j][1]) } else if _, ok := req.(*roachpb.NoopRequest); ok != (len(h.Key) == 0) { t.Errorf("%d.%d: expected NoopRequest, got %T", i, j, req) } else if len(h.Key) != 0 { reqs++ } } if reqs != num { t.Errorf("%d: counted %d requests, but truncation indicated %d", i, reqs, num) } if !reflect.DeepEqual(original, goldenOriginal) { t.Errorf("%d: truncation mutated original:\nexpected: %s\nactual: %s", i, goldenOriginal, original) } } }
// TestTxnCoordSenderHeartbeat verifies periodic heartbeat of the // transaction record. func TestTxnCoordSenderHeartbeat(t *testing.T) { defer leaktest.AfterTest(t)() s, sender := createTestDB(t) defer s.Stop() defer teardownHeartbeats(sender) // Set heartbeat interval to 1ms for testing. sender.heartbeatInterval = 1 * time.Millisecond initialTxn := client.NewTxn(context.Background(), *s.DB) if err := initialTxn.Put(roachpb.Key("a"), []byte("value")); err != nil { t.Fatal(err) } // Verify 3 heartbeats. var heartbeatTS roachpb.Timestamp for i := 0; i < 3; i++ { util.SucceedsSoon(t, func() error { txn, pErr := getTxn(sender, &initialTxn.Proto) if pErr != nil { t.Fatal(pErr) } // Advance clock by 1ns. // Locking the TxnCoordSender to prevent a data race. sender.Lock() s.Manual.Increment(1) sender.Unlock() if txn.LastHeartbeat != nil && heartbeatTS.Less(*txn.LastHeartbeat) { heartbeatTS = *txn.LastHeartbeat return nil } return util.Errorf("expected heartbeat") }) } // Sneakily send an ABORT right to DistSender (bypassing TxnCoordSender). { var ba roachpb.BatchRequest ba.Add(&roachpb.EndTransactionRequest{ Commit: false, Span: roachpb.Span{Key: initialTxn.Proto.Key}, }) ba.Txn = &initialTxn.Proto if _, pErr := sender.wrapped.Send(context.Background(), ba); pErr != nil { t.Fatal(pErr) } } util.SucceedsSoon(t, func() error { sender.Lock() defer sender.Unlock() if txnMeta, ok := sender.txns[*initialTxn.Proto.ID]; !ok { t.Fatal("transaction unregistered prematurely") } else if txnMeta.txn.Status != roachpb.ABORTED { return fmt.Errorf("transaction is not aborted") } return nil }) // Trying to do something else should give us a TransactionAbortedError. _, err := initialTxn.Get("a") assertTransactionAbortedError(t, err) }
// TestSequenceUpdateOnMultiRangeQueryLoop reproduces #3206 and // verifies that the sequence is updated in the DistSender // multi-range-query loop. // // More specifically, the issue was that DistSender might send // multiple batch requests to the same replica when it finds a // post-split range descriptor in the cache while the split has not // yet been fully completed. By giving a higher sequence to the second // request, we can avoid an infinite txn restart error (otherwise // caused by hitting the sequence cache). func TestSequenceUpdateOnMultiRangeQueryLoop(t *testing.T) { defer leaktest.AfterTest(t) g, s := makeTestGossip(t) defer s() if err := g.SetNodeDescriptor(&roachpb.NodeDescriptor{NodeID: 1}); err != nil { t.Fatal(err) } nd := &roachpb.NodeDescriptor{ NodeID: roachpb.NodeID(1), Address: util.MakeUnresolvedAddr(testAddress.Network(), testAddress.String()), } if err := g.AddInfoProto(gossip.MakeNodeIDKey(roachpb.NodeID(1)), nd, time.Hour); err != nil { t.Fatal(err) } // Fill mockRangeDescriptorDB with two descriptors. var descriptor1 = roachpb.RangeDescriptor{ RangeID: 1, StartKey: roachpb.RKeyMin, EndKey: roachpb.RKey("b"), Replicas: []roachpb.ReplicaDescriptor{ { NodeID: 1, StoreID: 1, }, }, } var descriptor2 = roachpb.RangeDescriptor{ RangeID: 2, StartKey: roachpb.RKey("b"), EndKey: roachpb.RKey("c"), Replicas: []roachpb.ReplicaDescriptor{ { NodeID: 1, StoreID: 1, }, }, } descDB := mockRangeDescriptorDB(func(key roachpb.RKey, _, _ bool) ([]roachpb.RangeDescriptor, *roachpb.Error) { desc := descriptor1 if key.Equal(roachpb.RKey("b")) { desc = descriptor2 } return []roachpb.RangeDescriptor{desc}, nil }) // Define our rpcSend stub which checks the span of the batch // requests. The first request should be the point request on // "a". The second request should be on "b". The sequence of the // second request will be incremented by one from that of the // first request. first := true var firstSequence uint32 var testFn rpcSendFn = func(_ rpc.Options, method string, addrs []net.Addr, getArgs func(addr net.Addr) proto.Message, getReply func() proto.Message, _ *rpc.Context) ([]proto.Message, error) { if method != "Node.Batch" { return nil, util.Errorf("unexpected method %v", method) } ba := getArgs(testAddress).(*roachpb.BatchRequest) rs := keys.Range(*ba) if first { if !(rs.Key.Equal(roachpb.RKey("a")) && rs.EndKey.Equal(roachpb.RKey("a").Next())) { t.Errorf("unexpected span [%s,%s)", rs.Key, rs.EndKey) } first = false firstSequence = ba.Txn.Sequence } else { if !(rs.Key.Equal(roachpb.RKey("b")) && rs.EndKey.Equal(roachpb.RKey("b").Next())) { t.Errorf("unexpected span [%s,%s)", rs.Key, rs.EndKey) } if ba.Txn.Sequence != firstSequence+1 { t.Errorf("unexpected sequence; expected %d, but got %d", firstSequence+1, ba.Txn.Sequence) } } return []proto.Message{ba.CreateReply()}, nil } ctx := &DistSenderContext{ RPCSend: testFn, RangeDescriptorDB: descDB, } ds := NewDistSender(ctx, g) // Send a batch request containing two puts. var ba roachpb.BatchRequest ba.Txn = &roachpb.Transaction{Name: "test"} val := roachpb.MakeValueFromString("val") ba.Add(roachpb.NewPut(roachpb.Key("a"), val).(*roachpb.PutRequest)) ba.Add(roachpb.NewPut(roachpb.Key("b"), val).(*roachpb.PutRequest)) _, pErr := ds.Send(context.Background(), ba) if err := pErr.GoError(); err != nil { t.Fatal(err) } }
// resolveIntents resolves the given intents. For those which are // local to the range, we submit directly to the local Raft instance; // all non-local intents are resolved asynchronously in a batch. If // `wait` is true, all operations are carried out synchronously and an // error is returned. Otherwise, the call returns without error as // soon as all local resolve commands have been **proposed** (not // executed). This ensures that if a waiting client retries // immediately after calling this function, it will not hit the same // intents again. func (ir *intentResolver) resolveIntents(ctx context.Context, r *Replica, intents []roachpb.Intent, wait bool, poison bool) error { // We're doing async stuff below; those need new traces. ctx, cleanup := tracing.EnsureContext(ctx, ir.store.Tracer()) defer cleanup() log.Trace(ctx, fmt.Sprintf("resolving intents [wait=%t]", wait)) var reqsRemote []roachpb.Request baLocal := roachpb.BatchRequest{} baLocal.Timestamp = ir.store.Clock().Now() for i := range intents { intent := intents[i] // avoids a race in `i, intent := range ...` var resolveArgs roachpb.Request var local bool // whether this intent lives on this Range { if len(intent.EndKey) == 0 { resolveArgs = &roachpb.ResolveIntentRequest{ Span: intent.Span, IntentTxn: intent.Txn, Status: intent.Status, Poison: poison, } local = r.ContainsKey(intent.Key) } else { resolveArgs = &roachpb.ResolveIntentRangeRequest{ Span: intent.Span, IntentTxn: intent.Txn, Status: intent.Status, Poison: poison, } local = r.ContainsKeyRange(intent.Key, intent.EndKey) } } // If the intent isn't (completely) local, we'll need to send an external request. // We'll batch them all up and send at the end. if local { baLocal.Add(resolveArgs) } else { reqsRemote = append(reqsRemote, resolveArgs) } } // The local batch goes directly to Raft. var wg sync.WaitGroup if len(baLocal.Requests) > 0 { action := func() error { // Trace this under the ID of the intent owner. // Create a new span though, since we do not want to pass a span // between goroutines or we risk use-after-finish. sp := r.store.Tracer().StartSpan("resolve intents") defer sp.Finish() ctx = opentracing.ContextWithSpan(ctx, sp) // Always operate with a timeout when resolving intents: this // prevents rare shutdown timeouts in tests. ctxWithTimeout, cancel := context.WithTimeout(ctx, base.NetworkTimeout) defer cancel() _, pErr := r.addWriteCmd(ctxWithTimeout, baLocal, &wg) return pErr.GoError() } wg.Add(1) if wait || !r.store.Stopper().RunLimitedAsyncTask(ir.sem, func() { if err := action(); err != nil { log.Warningf("unable to resolve local intents; %s", err) } }) { // Still run the task when draining. Our caller already has a task and // going async here again is merely for performance, but some intents // need to be resolved because they might block other tasks. See #1684. // Note that handleSkippedIntents has a TODO in case #1684 comes back. if err := action(); err != nil { return err } } } // Resolve all of the intents which aren't local to the Range. if len(reqsRemote) > 0 { b := &client.Batch{} b.InternalAddRequest(reqsRemote...) action := func() error { // TODO(tschottdorf): no tracing here yet. return r.store.DB().Run(b).GoError() } if wait || !r.store.Stopper().RunLimitedAsyncTask(ir.sem, func() { if err := action(); err != nil { log.Warningf("unable to resolve external intents: %s", err) } }) { // As with local intents, try async to not keep the caller waiting, but // when draining just go ahead and do it synchronously. See #1684. if err := action(); err != nil { return err } } } // Wait until the local ResolveIntents batch has been submitted to // raft. No-op if all were non-local. wg.Wait() return nil }