// +build kvdb_etcd package etcd import ( "context" "sync" "sync/atomic" "testing" "time" "github.com/stretchr/testify/require" ) // TestCommitQueue tests that non-conflicting transactions commit concurrently, // while conflicting transactions are queued up. func TestCommitQueue(t *testing.T) { // The duration of each commit. const commitDuration = time.Millisecond * 500 const numCommits = 4 var wg sync.WaitGroup commits := make([]string, numCommits) idx := int32(-1) commit := func(tag string, sleep bool) func() { return func() { defer wg.Done() // Update our log of commit order. Avoid blocking // by preallocating the commit log and increasing // the log index atomically. i := atomic.AddInt32(&idx, 1) commits[i] = tag if sleep { time.Sleep(commitDuration) } } } // Helper function to create a read set from the passed keys. makeReadSet := func(keys []string) readSet { rs := make(map[string]stmGet) for _, key := range keys { rs[key] = stmGet{} } return rs } // Helper function to create a write set from the passed keys. makeWriteSet := func(keys []string) writeSet { ws := make(map[string]stmPut) for _, key := range keys { ws[key] = stmPut{} } return ws } ctx := context.Background() ctx, cancel := context.WithCancel(ctx) q := NewCommitQueue(ctx) defer q.Wait() defer cancel() wg.Add(numCommits) t1 := time.Now() // Tx1: reads: key1, key2, writes: key3, conflict: none q.Add( commit("free", true), makeReadSet([]string{"key1", "key2"}), makeWriteSet([]string{"key3"}), ) // Tx2: reads: key1, key2, writes: key3, conflict: Tx1 q.Add( commit("blocked1", false), makeReadSet([]string{"key1", "key2"}), makeWriteSet([]string{"key3"}), ) // Tx3: reads: key1, writes: key4, conflict: none q.Add( commit("free", true), makeReadSet([]string{"key1", "key2"}), makeWriteSet([]string{"key4"}), ) // Tx4: reads: key2, writes: key4 conflict: Tx3 q.Add( commit("blocked2", false), makeReadSet([]string{"key2"}), makeWriteSet([]string{"key4"}), ) // Wait for all commits. wg.Wait() t2 := time.Now() // Expected total execution time: delta. // 2 * commitDuration <= delta < 3 * commitDuration delta := t2.Sub(t1) require.LessOrEqual(t, int64(commitDuration*2), int64(delta)) require.Greater(t, int64(commitDuration*3), int64(delta)) // Expect that the non-conflicting "free" transactions are executed // before the blocking ones, and the blocking ones are executed in // the order of addition. require.Equal(t, []string{"free", "free", "blocked1", "blocked2"}, commits, ) }