func (hv *historyVerifier) runTxn(txnIdx int, priority int32, isolation proto.IsolationType, cmds []*cmd, db *client.KV, t *testing.T) error { var retry int txnName := fmt.Sprintf("txn%d", txnIdx) txnOpts := &client.TransactionOptions{ Name: txnName, Isolation: isolation, } err := db.RunTransaction(txnOpts, func(txn *client.KV) error { txn.UserPriority = -priority env := map[string]int64{} // TODO(spencer): restarts must create additional histories. They // look like: given the current partial history and a restart on // txn txnIdx, re-enumerate a set of all histories containing the // remaining commands from extant txns and all commands from this // restarted txn. // If this is attempt > 1, reset cmds so no waits. if retry++; retry == 2 { for _, c := range cmds { c.done() } } log.V(1).Infof("%s, retry=%d", txnName, retry) for i := range cmds { cmds[i].env = env if err := hv.runCmd(txn, txnIdx, retry, i, cmds, t); err != nil { return err } } return nil }) hv.wg.Done() return err }
// concurrentIncrements starts two Goroutines in parallel, both of which // read the integers stored at the other's key and add it onto their own. // It is checked that the outcome is serializable, i.e. exactly one of the // two Goroutines (the later write) sees the previous write by the other. func concurrentIncrements(kvClient *client.KV, t *testing.T) { // wgStart waits for all transactions to line up, wgEnd has the main // function wait for them to finish. var wgStart, wgEnd sync.WaitGroup wgStart.Add(2 + 1) wgEnd.Add(2) for i := 0; i < 2; i++ { go func(i int) { // Read the other key, write key i. readKey := []byte(fmt.Sprintf("value-%d", (i+1)%2)) writeKey := []byte(fmt.Sprintf("value-%d", i)) defer wgEnd.Done() wgStart.Done() // Wait until the other goroutines are running. wgStart.Wait() txnOpts := &client.TransactionOptions{ Name: fmt.Sprintf("test-%d", i), } if err := kvClient.RunTransaction(txnOpts, func(txn *client.KV) error { // Retrieve the other key. gr := &proto.GetResponse{} if err := txn.Call(proto.Get, proto.GetArgs(readKey), gr); err != nil { return err } otherValue := int64(0) if gr.Value != nil && gr.Value.Integer != nil { otherValue = *gr.Value.Integer } pr := &proto.IncrementResponse{} pa := proto.IncrementArgs(writeKey, 1+otherValue) if err := txn.Call(proto.Increment, pa, pr); err != nil { return err } return nil }); err != nil { t.Error(err) } }(i) } // Kick the goroutines loose. wgStart.Done() // Wait for the goroutines to finish. wgEnd.Wait() // Verify that both keys contain something and, more importantly, that // one key actually contains the value of the first writer and not only // its own. total := int64(0) results := []int64(nil) for i := 0; i < 2; i++ { readKey := []byte(fmt.Sprintf("value-%d", i)) gr := &proto.GetResponse{} if err := kvClient.Call(proto.Get, proto.GetArgs(readKey), gr); err != nil { log.Fatal(err) } if gr.Value == nil || gr.Value.Integer == nil { t.Fatalf("unexpected empty key: %v=%v", readKey, gr.Value) } total += *gr.Value.Integer results = append(results, *gr.Value.Integer) } // First writer should have 1, second one 2 if total != 3 { t.Fatalf("got unserializable values %v", results) } }