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