github.com/lfch/etcd-io/tests/v3@v3.0.0-20221004140520-eac99acd3e9d/integration/clientv3/experimental/recipes/v3_queue_test.go (about)

     1  // Copyright 2016 The etcd Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package recipes_test
    16  
    17  import (
    18  	"fmt"
    19  	"math/rand"
    20  	"sync/atomic"
    21  	"testing"
    22  
    23  	recipe "github.com/lfch/etcd-io/client/v3/experimental/recipes"
    24  	integration2 "github.com/lfch/etcd-io/tests/v3/framework/integration"
    25  )
    26  
    27  const (
    28  	manyQueueClients    = 3
    29  	queueItemsPerClient = 2
    30  )
    31  
    32  // TestQueueOneReaderOneWriter confirms the queue is FIFO
    33  func TestQueueOneReaderOneWriter(t *testing.T) {
    34  	integration2.BeforeTest(t)
    35  
    36  	clus := integration2.NewCluster(t, &integration2.ClusterConfig{Size: 1})
    37  	defer clus.Terminate(t)
    38  
    39  	done := make(chan struct{})
    40  	defer func() {
    41  		<-done
    42  	}()
    43  	go func() {
    44  		defer func() {
    45  			done <- struct{}{}
    46  		}()
    47  		etcdc := clus.RandClient()
    48  		q := recipe.NewQueue(etcdc, "testq")
    49  		for i := 0; i < 5; i++ {
    50  			if err := q.Enqueue(fmt.Sprintf("%d", i)); err != nil {
    51  				t.Errorf("error enqueuing (%v)", err)
    52  			}
    53  		}
    54  	}()
    55  
    56  	etcdc := clus.RandClient()
    57  	q := recipe.NewQueue(etcdc, "testq")
    58  	for i := 0; i < 5; i++ {
    59  		s, err := q.Dequeue()
    60  		if err != nil {
    61  			t.Fatalf("error dequeueing (%v)", err)
    62  		}
    63  		if s != fmt.Sprintf("%d", i) {
    64  			t.Fatalf("expected dequeue value %v, got %v", s, i)
    65  		}
    66  	}
    67  }
    68  
    69  func TestQueueManyReaderOneWriter(t *testing.T) {
    70  	testQueueNReaderMWriter(t, manyQueueClients, 1)
    71  }
    72  
    73  func TestQueueOneReaderManyWriter(t *testing.T) {
    74  	testQueueNReaderMWriter(t, 1, manyQueueClients)
    75  }
    76  
    77  func TestQueueManyReaderManyWriter(t *testing.T) {
    78  	testQueueNReaderMWriter(t, manyQueueClients, manyQueueClients)
    79  }
    80  
    81  // BenchmarkQueue benchmarks Queues using many/many readers/writers
    82  func BenchmarkQueue(b *testing.B) {
    83  	integration2.BeforeTest(b)
    84  
    85  	// XXX switch tests to use TB interface
    86  	clus := integration2.NewCluster(nil, &integration2.ClusterConfig{Size: 3})
    87  	defer clus.Terminate(nil)
    88  	for i := 0; i < b.N; i++ {
    89  		testQueueNReaderMWriter(nil, manyQueueClients, manyQueueClients)
    90  	}
    91  }
    92  
    93  // TestPrQueueOneReaderOneWriter tests whether priority queues respect priorities.
    94  func TestPrQueueOneReaderOneWriter(t *testing.T) {
    95  	integration2.BeforeTest(t)
    96  
    97  	clus := integration2.NewCluster(t, &integration2.ClusterConfig{Size: 1})
    98  	defer clus.Terminate(t)
    99  
   100  	// write out five items with random priority
   101  	etcdc := clus.RandClient()
   102  	q := recipe.NewPriorityQueue(etcdc, "testprq")
   103  	for i := 0; i < 5; i++ {
   104  		// [0, 2] priority for priority collision to test seq keys
   105  		pr := uint16(rand.Intn(3))
   106  		if err := q.Enqueue(fmt.Sprintf("%d", pr), pr); err != nil {
   107  			t.Fatalf("error enqueuing (%v)", err)
   108  		}
   109  	}
   110  
   111  	// read back items; confirm priority order is respected
   112  	lastPr := -1
   113  	for i := 0; i < 5; i++ {
   114  		s, err := q.Dequeue()
   115  		if err != nil {
   116  			t.Fatalf("error dequeueing (%v)", err)
   117  		}
   118  		curPr := 0
   119  		if _, err := fmt.Sscanf(s, "%d", &curPr); err != nil {
   120  			t.Fatalf(`error parsing item "%s" (%v)`, s, err)
   121  		}
   122  		if lastPr > curPr {
   123  			t.Fatalf("expected priority %v > %v", curPr, lastPr)
   124  		}
   125  	}
   126  }
   127  
   128  func TestPrQueueManyReaderManyWriter(t *testing.T) {
   129  	integration2.BeforeTest(t)
   130  
   131  	clus := integration2.NewCluster(t, &integration2.ClusterConfig{Size: 3})
   132  	defer clus.Terminate(t)
   133  	rqs := newPriorityQueues(clus, manyQueueClients)
   134  	wqs := newPriorityQueues(clus, manyQueueClients)
   135  	testReadersWriters(t, rqs, wqs)
   136  }
   137  
   138  // BenchmarkQueue benchmarks Queues using n/n readers/writers
   139  func BenchmarkPrQueueOneReaderOneWriter(b *testing.B) {
   140  	integration2.BeforeTest(b)
   141  
   142  	// XXX switch tests to use TB interface
   143  	clus := integration2.NewCluster(nil, &integration2.ClusterConfig{Size: 3})
   144  	defer clus.Terminate(nil)
   145  	rqs := newPriorityQueues(clus, 1)
   146  	wqs := newPriorityQueues(clus, 1)
   147  	for i := 0; i < b.N; i++ {
   148  		testReadersWriters(nil, rqs, wqs)
   149  	}
   150  }
   151  
   152  func testQueueNReaderMWriter(t *testing.T, n int, m int) {
   153  	integration2.BeforeTest(t)
   154  	clus := integration2.NewCluster(t, &integration2.ClusterConfig{Size: 3})
   155  	defer clus.Terminate(t)
   156  	testReadersWriters(t, newQueues(clus, n), newQueues(clus, m))
   157  }
   158  
   159  func newQueues(clus *integration2.Cluster, n int) (qs []testQueue) {
   160  	for i := 0; i < n; i++ {
   161  		etcdc := clus.RandClient()
   162  		qs = append(qs, recipe.NewQueue(etcdc, "q"))
   163  	}
   164  	return qs
   165  }
   166  
   167  func newPriorityQueues(clus *integration2.Cluster, n int) (qs []testQueue) {
   168  	for i := 0; i < n; i++ {
   169  		etcdc := clus.RandClient()
   170  		q := &flatPriorityQueue{recipe.NewPriorityQueue(etcdc, "prq")}
   171  		qs = append(qs, q)
   172  	}
   173  	return qs
   174  }
   175  
   176  func testReadersWriters(t *testing.T, rqs []testQueue, wqs []testQueue) {
   177  	rerrc := make(chan error)
   178  	werrc := make(chan error)
   179  	manyWriters(wqs, queueItemsPerClient, werrc)
   180  	manyReaders(rqs, len(wqs)*queueItemsPerClient, rerrc)
   181  	for range wqs {
   182  		if err := <-werrc; err != nil {
   183  			t.Errorf("error writing (%v)", err)
   184  		}
   185  	}
   186  	for range rqs {
   187  		if err := <-rerrc; err != nil {
   188  			t.Errorf("error reading (%v)", err)
   189  		}
   190  	}
   191  }
   192  
   193  func manyReaders(qs []testQueue, totalReads int, errc chan<- error) {
   194  	var rxReads int32
   195  	for _, q := range qs {
   196  		go func(q testQueue) {
   197  			for {
   198  				total := atomic.AddInt32(&rxReads, 1)
   199  				if int(total) > totalReads {
   200  					break
   201  				}
   202  				if _, err := q.Dequeue(); err != nil {
   203  					errc <- err
   204  					return
   205  				}
   206  			}
   207  			errc <- nil
   208  		}(q)
   209  	}
   210  }
   211  
   212  func manyWriters(qs []testQueue, writesEach int, errc chan<- error) {
   213  	for _, q := range qs {
   214  		go func(q testQueue) {
   215  			for j := 0; j < writesEach; j++ {
   216  				if err := q.Enqueue("foo"); err != nil {
   217  					errc <- err
   218  					return
   219  				}
   220  			}
   221  			errc <- nil
   222  		}(q)
   223  	}
   224  }
   225  
   226  type testQueue interface {
   227  	Enqueue(val string) error
   228  	Dequeue() (string, error)
   229  }
   230  
   231  type flatPriorityQueue struct{ *recipe.PriorityQueue }
   232  
   233  func (q *flatPriorityQueue) Enqueue(val string) error {
   234  	// randomized to stress dequeuing logic; order isn't important
   235  	return q.PriorityQueue.Enqueue(val, uint16(rand.Intn(2)))
   236  }
   237  func (q *flatPriorityQueue) Dequeue() (string, error) {
   238  	return q.PriorityQueue.Dequeue()
   239  }