// TestRangeLookupWithOpenTransaction verifies that range lookups are // done in such a way (e.g. using inconsistent reads) that they // proceed in the event that a write intent is extant at the meta // index record being read. func TestRangeLookupWithOpenTransaction(t *testing.T) { defer leaktest.AfterTest(t)() s, _, _ := serverutils.StartServer(t, base.TestServerArgs{}) defer s.Stopper().Stop() db := createTestClient(t, s.Stopper(), s.ServingAddr()) // Create an intent on the meta1 record by writing directly to the // engine. key := testutils.MakeKey(keys.Meta1Prefix, roachpb.KeyMax) now := s.Clock().Now() txn := roachpb.NewTransaction("txn", roachpb.Key("foobar"), 0, enginepb.SERIALIZABLE, now, 0) if err := engine.MVCCPutProto( context.Background(), s.(*server.TestServer).Engines()[0], nil, key, now, txn, &roachpb.RangeDescriptor{}); err != nil { t.Fatal(err) } // Now, with an intent pending, attempt (asynchronously) to read // from an arbitrary key. This will cause the distributed sender to // do a range lookup, which will encounter the intent. We're // verifying here that the range lookup doesn't fail with a write // intent error. If it did, it would go into a deadloop attempting // to push the transaction, which in turn requires another range // lookup, etc, ad nauseam. if _, err := db.Get(context.TODO(), "a"); err != nil { t.Fatal(err) } }
// TestTxnMultipleCoord checks that a coordinator uses the Writing flag to // enforce that only one coordinator can be used for transactional writes. func TestTxnMultipleCoord(t *testing.T) { defer leaktest.AfterTest(t)() s, sender := createTestDB(t) defer s.Stop() testCases := []struct { args roachpb.Request writing bool ok bool }{ {roachpb.NewGet(roachpb.Key("a")), true, false}, {roachpb.NewGet(roachpb.Key("a")), false, true}, {roachpb.NewPut(roachpb.Key("a"), roachpb.Value{}), false, false}, // transactional write before begin {roachpb.NewPut(roachpb.Key("a"), roachpb.Value{}), true, false}, // must have switched coordinators } for i, tc := range testCases { txn := roachpb.NewTransaction("test", roachpb.Key("a"), 1, enginepb.SERIALIZABLE, s.Clock.Now(), s.Clock.MaxOffset().Nanoseconds()) txn.Writing = tc.writing reply, pErr := client.SendWrappedWith(context.Background(), sender, roachpb.Header{ Txn: txn, }, tc.args) if pErr == nil != tc.ok { t.Errorf("%d: %T (writing=%t): success_expected=%t, but got: %v", i, tc.args, tc.writing, tc.ok, pErr) } if pErr != nil { continue } txn = reply.Header().Txn // The transaction should come back rw if it started rw or if we just // wrote. isWrite := roachpb.IsTransactionWrite(tc.args) if (tc.writing || isWrite) != txn.Writing { t.Errorf("%d: unexpected writing state: %s", i, txn) } if !isWrite { continue } // Abort for clean shutdown. if _, pErr := client.SendWrappedWith(context.Background(), sender, roachpb.Header{ Txn: txn, }, &roachpb.EndTransactionRequest{ Commit: false, }); pErr != nil { t.Fatal(pErr) } } }
// maybeBeginTxn begins a new transaction if a txn has been specified // in the request but has a nil ID. The new transaction is initialized // using the name and isolation in the otherwise uninitialized txn. // The Priority, if non-zero is used as a minimum. // // No transactional writes are allowed unless preceded by a begin // transaction request within the same batch. The exception is if the // transaction is already in state txn.Writing=true. func (tc *TxnCoordSender) maybeBeginTxn(ba *roachpb.BatchRequest) error { if len(ba.Requests) == 0 { return errors.Errorf("empty batch with txn") } if ba.Txn.ID == nil { // Create transaction without a key. The key is set when a begin // transaction request is received. // The initial timestamp may be communicated by a higher layer. // If so, use that. Otherwise make up a new one. timestamp := ba.Txn.OrigTimestamp if timestamp == hlc.ZeroTimestamp { timestamp = tc.clock.Now() } newTxn := roachpb.NewTransaction(ba.Txn.Name, nil, ba.UserPriority, ba.Txn.Isolation, timestamp, tc.clock.MaxOffset().Nanoseconds()) // Use existing priority as a minimum. This is used on transaction // aborts to ratchet priority when creating successor transaction. if newTxn.Priority < ba.Txn.Priority { newTxn.Priority = ba.Txn.Priority } ba.Txn = newTxn } // Check for a begin transaction to set txn key based on the key of // the first transactional write. Also enforce that no transactional // writes occur before a begin transaction. var haveBeginTxn bool for _, req := range ba.Requests { args := req.GetInner() if bt, ok := args.(*roachpb.BeginTransactionRequest); ok { if haveBeginTxn || ba.Txn.Writing { return errors.Errorf("begin transaction requested twice in the same transaction: %s", ba.Txn) } haveBeginTxn = true if ba.Txn.Key == nil { ba.Txn.Key = bt.Key } } if roachpb.IsTransactionWrite(args) && !haveBeginTxn && !ba.Txn.Writing { return errors.Errorf("transactional write before begin transaction") } } return nil }
func TestRangeInfo(t *testing.T) { defer leaktest.AfterTest(t)() mtc := startMultiTestContext(t, 2) defer mtc.Stop() // Up-replicate to two replicas. mtc.replicateRange(mtc.stores[0].LookupReplica(roachpb.RKeyMin, nil).RangeID, 1) // Split the key space at key "a". splitKey := roachpb.RKey("a") splitArgs := adminSplitArgs(splitKey.AsRawKey(), splitKey.AsRawKey()) if _, pErr := client.SendWrapped( context.Background(), rg1(mtc.stores[0]), &splitArgs, ); pErr != nil { t.Fatal(pErr) } // Get the replicas for each side of the split. This is done within // a SucceedsSoon loop to ensure the split completes. var lhsReplica0, lhsReplica1, rhsReplica0, rhsReplica1 *storage.Replica util.SucceedsSoon(t, func() error { lhsReplica0 = mtc.stores[0].LookupReplica(roachpb.RKeyMin, nil) lhsReplica1 = mtc.stores[1].LookupReplica(roachpb.RKeyMin, nil) rhsReplica0 = mtc.stores[0].LookupReplica(splitKey, nil) rhsReplica1 = mtc.stores[1].LookupReplica(splitKey, nil) if lhsReplica0 == rhsReplica0 || lhsReplica1 == rhsReplica1 { return errors.Errorf("replicas not post-split %v, %v, %v, %v", lhsReplica0, rhsReplica0, rhsReplica0, rhsReplica1) } return nil }) lhsLease, _ := lhsReplica0.GetLease() rhsLease, _ := rhsReplica0.GetLease() // Verify range info is not set if unrequested. getArgs := getArgs(splitKey.AsRawKey()) reply, pErr := client.SendWrapped(context.Background(), mtc.distSenders[0], &getArgs) if pErr != nil { t.Fatal(pErr) } if len(reply.Header().RangeInfos) > 0 { t.Errorf("expected empty range infos if unrequested; got %v", reply.Header().RangeInfos) } // Verify range info on a get request. h := roachpb.Header{ ReturnRangeInfo: true, } reply, pErr = client.SendWrappedWith(context.Background(), mtc.distSenders[0], h, &getArgs) if pErr != nil { t.Fatal(pErr) } expRangeInfos := []roachpb.RangeInfo{ { Desc: *rhsReplica0.Desc(), Lease: *rhsLease, }, } if !reflect.DeepEqual(reply.Header().RangeInfos, expRangeInfos) { t.Errorf("on get reply, expected %+v; got %+v", expRangeInfos, reply.Header().RangeInfos) } // Verify range info on a put request. putArgs := putArgs(splitKey.AsRawKey(), []byte("foo")) reply, pErr = client.SendWrappedWith(context.Background(), mtc.distSenders[0], h, &putArgs) if pErr != nil { t.Fatal(pErr) } if !reflect.DeepEqual(reply.Header().RangeInfos, expRangeInfos) { t.Errorf("on put reply, expected %+v; got %+v", expRangeInfos, reply.Header().RangeInfos) } // Verify multiple range infos on a scan request. scanArgs := roachpb.ScanRequest{ Span: roachpb.Span{ Key: keys.SystemMax, EndKey: roachpb.KeyMax, }, } h.Txn = roachpb.NewTransaction("test", roachpb.KeyMin, 1, enginepb.SERIALIZABLE, mtc.clock.Now(), 0) reply, pErr = client.SendWrappedWith(context.Background(), mtc.distSenders[0], h, &scanArgs) if pErr != nil { t.Fatal(pErr) } expRangeInfos = []roachpb.RangeInfo{ { Desc: *lhsReplica0.Desc(), Lease: *lhsLease, }, { Desc: *rhsReplica0.Desc(), Lease: *rhsLease, }, } if !reflect.DeepEqual(reply.Header().RangeInfos, expRangeInfos) { t.Errorf("on scan reply, expected %+v; got %+v", expRangeInfos, reply.Header().RangeInfos) } // Verify multiple range infos and order on a reverse scan request. revScanArgs := roachpb.ReverseScanRequest{ Span: roachpb.Span{ Key: keys.SystemMax, EndKey: roachpb.KeyMax, }, } reply, pErr = client.SendWrappedWith(context.Background(), mtc.distSenders[0], h, &revScanArgs) if pErr != nil { t.Fatal(pErr) } expRangeInfos = []roachpb.RangeInfo{ { Desc: *rhsReplica0.Desc(), Lease: *rhsLease, }, { Desc: *lhsReplica0.Desc(), Lease: *lhsLease, }, } if !reflect.DeepEqual(reply.Header().RangeInfos, expRangeInfos) { t.Errorf("on reverse scan reply, expected %+v; got %+v", expRangeInfos, reply.Header().RangeInfos) } // Change lease holders for both ranges and re-scan. for _, r := range []*storage.Replica{lhsReplica1, rhsReplica1} { replDesc, err := r.GetReplicaDescriptor() if err != nil { t.Fatal(err) } if err = mtc.dbs[0].AdminTransferLease(context.TODO(), r.Desc().StartKey.AsRawKey(), replDesc.StoreID); err != nil { t.Fatalf("unable to transfer lease to replica %s: %s", r, err) } } reply, pErr = client.SendWrappedWith(context.Background(), mtc.distSenders[0], h, &scanArgs) if pErr != nil { t.Fatal(pErr) } lhsLease, _ = lhsReplica1.GetLease() rhsLease, _ = rhsReplica1.GetLease() expRangeInfos = []roachpb.RangeInfo{ { Desc: *lhsReplica1.Desc(), Lease: *lhsLease, }, { Desc: *rhsReplica1.Desc(), Lease: *rhsLease, }, } if !reflect.DeepEqual(reply.Header().RangeInfos, expRangeInfos) { t.Errorf("on scan reply, expected %+v; got %+v", expRangeInfos, reply.Header().RangeInfos) } }