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  }