github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/nomad/stream/event_buffer_test.go (about) 1 package stream 2 3 import ( 4 "context" 5 "fmt" 6 "math/rand" 7 "testing" 8 "time" 9 10 "github.com/hashicorp/nomad/ci" 11 "github.com/hashicorp/nomad/nomad/structs" 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/require" 14 ) 15 16 func TestEventBufferFuzz(t *testing.T) { 17 ci.Parallel(t) 18 19 nReaders := 1000 20 nMessages := 1000 21 22 b := newEventBuffer(1000) 23 24 // Start a write goroutine that will publish 10000 messages with sequential 25 // indexes and some jitter in timing (to allow clients to "catch up" and block 26 // waiting for updates). 27 go func() { 28 seed := time.Now().UnixNano() 29 t.Logf("Using seed %d", seed) 30 // z is a Zipfian distribution that gives us a number of milliseconds to 31 // sleep which are mostly low - near zero but occasionally spike up to near 32 // 100. 33 z := rand.NewZipf(rand.New(rand.NewSource(seed)), 1.5, 1.5, 50) 34 35 for i := 0; i < nMessages; i++ { 36 // Event content is arbitrary and not valid for our use of buffers in 37 // streaming - here we only care about the semantics of the buffer. 38 e := structs.Event{ 39 Index: uint64(i), // Indexes should be contiguous 40 } 41 b.Append(&structs.Events{Index: uint64(i), Events: []structs.Event{e}}) 42 // Sleep sometimes for a while to let some subscribers catch up 43 wait := time.Duration(z.Uint64()) * time.Millisecond 44 time.Sleep(wait) 45 } 46 }() 47 48 // Run n subscribers following and verifying 49 errCh := make(chan error, nReaders) 50 51 // Load head here so all subscribers start from the same point or they might 52 // not run until several appends have already happened. 53 head := b.Head() 54 55 for i := 0; i < nReaders; i++ { 56 go func(i int) { 57 expect := uint64(0) 58 item := head 59 var err error 60 for { 61 item, err = item.Next(context.Background(), nil) 62 if err != nil { 63 errCh <- fmt.Errorf("subscriber %05d failed getting next %d: %s", i, 64 expect, err) 65 return 66 } 67 if item.Events.Events[0].Index != expect { 68 errCh <- fmt.Errorf("subscriber %05d got bad event want=%d, got=%d", i, 69 expect, item.Events.Events[0].Index) 70 return 71 } 72 expect++ 73 if expect == uint64(nMessages) { 74 // Succeeded 75 errCh <- nil 76 return 77 } 78 } 79 }(i) 80 } 81 82 // Wait for all readers to finish one way or other 83 for i := 0; i < nReaders; i++ { 84 err := <-errCh 85 assert.NoError(t, err) 86 } 87 } 88 89 func TestEventBuffer_Slow_Reader(t *testing.T) { 90 ci.Parallel(t) 91 92 b := newEventBuffer(10) 93 94 for i := 1; i < 11; i++ { 95 e := structs.Event{ 96 Index: uint64(i), // Indexes should be contiguous 97 } 98 b.Append(&structs.Events{Index: uint64(i), Events: []structs.Event{e}}) 99 } 100 101 require.Equal(t, 10, b.Len()) 102 103 head := b.Head() 104 105 for i := 10; i < 15; i++ { 106 e := structs.Event{ 107 Index: uint64(i), // Indexes should be contiguous 108 } 109 b.Append(&structs.Events{Index: uint64(i), Events: []structs.Event{e}}) 110 } 111 112 // Ensure the slow reader errors to handle dropped events and 113 // fetch latest head 114 ev, err := head.Next(context.Background(), nil) 115 require.Error(t, err) 116 require.Nil(t, ev) 117 118 newHead := b.Head() 119 require.Equal(t, 5, int(newHead.Events.Index)) 120 } 121 122 func TestEventBuffer_Size(t *testing.T) { 123 ci.Parallel(t) 124 125 b := newEventBuffer(100) 126 127 for i := 0; i < 10; i++ { 128 e := structs.Event{ 129 Index: uint64(i), // Indexes should be contiguous 130 } 131 b.Append(&structs.Events{Index: uint64(i), Events: []structs.Event{e}}) 132 } 133 134 require.Equal(t, 10, b.Len()) 135 } 136 137 func TestEventBuffer_MaxSize(t *testing.T) { 138 ci.Parallel(t) 139 140 b := newEventBuffer(10) 141 142 var events []structs.Event 143 for i := 0; i < 100; i++ { 144 events = append(events, structs.Event{}) 145 } 146 147 b.Append(&structs.Events{Index: uint64(1), Events: events}) 148 require.Equal(t, 1, b.Len()) 149 } 150 151 // TestEventBuffer_Emptying_Buffer tests the behavior when all items 152 // are removed, the event buffer should advance its head down to the last message 153 // and insert a placeholder sentinel value. 154 func TestEventBuffer_Emptying_Buffer(t *testing.T) { 155 ci.Parallel(t) 156 157 b := newEventBuffer(10) 158 159 for i := 0; i < 10; i++ { 160 e := structs.Event{ 161 Index: uint64(i), // Indexes should be contiguous 162 } 163 b.Append(&structs.Events{Index: uint64(i), Events: []structs.Event{e}}) 164 } 165 166 require.Equal(t, 10, int(b.Len())) 167 168 // empty the buffer, which will bring the event buffer down 169 // to a single sentinel value 170 for i := 0; i < 16; i++ { 171 b.advanceHead() 172 } 173 174 // head and tail are now a sentinel value 175 head := b.Head() 176 tail := b.Tail() 177 require.Equal(t, 0, int(head.Events.Index)) 178 require.Equal(t, 0, b.Len()) 179 require.Equal(t, head, tail) 180 181 e := structs.Event{ 182 Index: uint64(100), 183 } 184 b.Append(&structs.Events{Index: uint64(100), Events: []structs.Event{e}}) 185 186 ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(1*time.Second)) 187 defer cancel() 188 189 next, err := head.Next(ctx, make(chan struct{})) 190 require.NoError(t, err) 191 require.NotNil(t, next) 192 require.Equal(t, uint64(100), next.Events.Index) 193 194 } 195 196 func TestEventBuffer_StartAt_CurrentIdx_Past_Start(t *testing.T) { 197 ci.Parallel(t) 198 199 cases := []struct { 200 desc string 201 req uint64 202 expected uint64 203 offset int 204 }{ 205 { 206 desc: "requested index less than head receives head", 207 req: 10, 208 expected: 11, 209 offset: 1, 210 }, 211 { 212 desc: "requested exact match head", 213 req: 11, 214 expected: 11, 215 offset: 0, 216 }, 217 { 218 desc: "requested exact match", 219 req: 42, 220 expected: 42, 221 offset: 0, 222 }, 223 { 224 desc: "requested index greater than tail receives tail", 225 req: 500, 226 expected: 100, 227 offset: 400, 228 }, 229 } 230 231 // buffer starts at index 11 goes to 100 232 b := newEventBuffer(100) 233 234 for i := 11; i <= 100; i++ { 235 e := structs.Event{ 236 Index: uint64(i), // Indexes should be contiguous 237 } 238 b.Append(&structs.Events{Index: uint64(i), Events: []structs.Event{e}}) 239 } 240 241 for _, tc := range cases { 242 t.Run(tc.desc, func(t *testing.T) { 243 got, offset := b.StartAtClosest(tc.req) 244 require.Equal(t, int(tc.expected), int(got.Events.Index)) 245 require.Equal(t, tc.offset, offset) 246 }) 247 } 248 }