github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvserver/single_key_test.go (about) 1 // Copyright 2015 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_test 12 13 import ( 14 "context" 15 "sync/atomic" 16 "testing" 17 "time" 18 19 "github.com/cockroachdb/cockroach/pkg/base" 20 "github.com/cockroachdb/cockroach/pkg/kv" 21 "github.com/cockroachdb/cockroach/pkg/testutils/testcluster" 22 "github.com/cockroachdb/cockroach/pkg/util/leaktest" 23 "github.com/cockroachdb/cockroach/pkg/util/log" 24 "github.com/cockroachdb/cockroach/pkg/util/timeutil" 25 "github.com/cockroachdb/errors" 26 ) 27 28 // TestSingleKey stresses the transaction retry machinery by starting 29 // up an N node cluster and running N workers that are all 30 // incrementing the value associated with a single key. 31 func TestSingleKey(t *testing.T) { 32 defer leaktest.AfterTest(t)() 33 34 if testing.Short() { 35 t.Skip("short flag") 36 } 37 38 const num = 3 39 tc := testcluster.StartTestCluster(t, 3, 40 base.TestClusterArgs{ 41 ReplicationMode: base.ReplicationAuto, 42 }) 43 defer tc.Stopper().Stop(context.Background()) 44 ctx := context.Background() 45 46 // Initialize the value for our test key to zero. 47 const key = "test-key" 48 initDB := tc.Servers[0].DB() 49 if err := initDB.Put(ctx, key, 0); err != nil { 50 t.Fatal(err) 51 } 52 53 type result struct { 54 err error 55 maxLatency time.Duration 56 } 57 58 resultCh := make(chan result, num) 59 deadline := timeutil.Now().Add(5 * time.Second) 60 var expected int64 61 62 // Start up num workers each reading and writing the same 63 // key. Each worker is configured to talk to a different node in the 64 // cluster. 65 for i := 0; i < num; i++ { 66 db := tc.Servers[i].DB() 67 go func() { 68 var r result 69 for timeutil.Now().Before(deadline) { 70 start := timeutil.Now() 71 err := db.Txn(ctx, func(ctx context.Context, txn *kv.Txn) error { 72 minExp := atomic.LoadInt64(&expected) 73 r, err := txn.Get(ctx, key) 74 if err != nil { 75 return err 76 } 77 b := txn.NewBatch() 78 v := r.ValueInt() 79 b.Put(key, v+1) 80 err = txn.CommitInBatch(ctx, b) 81 // Atomic updates after the fact mean that we should read 82 // exp or larger (since concurrent writers might have 83 // committed but not yet performed their atomic update). 84 if err == nil && v < minExp { 85 return errors.Errorf("unexpected read: %d, expected >= %d", v, minExp) 86 } 87 return err 88 }) 89 if err != nil { 90 resultCh <- result{err: err} 91 return 92 } 93 atomic.AddInt64(&expected, 1) 94 latency := timeutil.Since(start) 95 if r.maxLatency < latency { 96 r.maxLatency = latency 97 } 98 } 99 resultCh <- r 100 }() 101 } 102 103 // Verify that none of the workers encountered an error. 104 var results []result 105 for len(results) < num { 106 select { 107 case <-tc.Stopper().ShouldStop(): 108 t.Fatalf("interrupted") 109 case r := <-resultCh: 110 if r.err != nil { 111 t.Fatal(r.err) 112 } 113 results = append(results, r) 114 case <-time.After(1 * time.Second): 115 // Periodically print out progress so that we know the test is still 116 // running. 117 log.Infof(ctx, "%d", atomic.LoadInt64(&expected)) 118 } 119 } 120 121 // Verify the resulting value stored at the key is what we expect. 122 r, err := initDB.Get(ctx, key) 123 if err != nil { 124 t.Fatal(err) 125 } 126 v := r.ValueInt() 127 if expected != v { 128 t.Fatalf("expected %d, but found %d", expected, v) 129 } 130 var maxLatency []time.Duration 131 for _, r := range results { 132 maxLatency = append(maxLatency, r.maxLatency) 133 } 134 log.Infof(ctx, "%d increments: %s", v, maxLatency) 135 }