github.com/daeuniverse/quic-go@v0.0.0-20240413031024-943f218e0810/streams_map_outgoing_test.go (about) 1 package quic 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "sort" 8 "sync" 9 "time" 10 11 "golang.org/x/exp/rand" 12 13 "github.com/daeuniverse/quic-go/internal/protocol" 14 "github.com/daeuniverse/quic-go/internal/wire" 15 16 . "github.com/onsi/ginkgo/v2" 17 . "github.com/onsi/gomega" 18 "go.uber.org/mock/gomock" 19 ) 20 21 var _ = Describe("Streams Map (outgoing)", func() { 22 var ( 23 m *outgoingStreamsMap[*mockGenericStream] 24 newStr func(num protocol.StreamNum) *mockGenericStream 25 mockSender *MockStreamSender 26 ) 27 28 const streamType = 42 29 30 // waitForEnqueued waits until there are n go routines waiting on OpenStreamSync() 31 waitForEnqueued := func(n int) { 32 Eventually(func() int { 33 m.mutex.Lock() 34 defer m.mutex.Unlock() 35 return len(m.openQueue) 36 }, scaleDuration(100*time.Millisecond), scaleDuration(10*time.Microsecond)).Should(Equal(n)) 37 } 38 39 BeforeEach(func() { 40 newStr = func(num protocol.StreamNum) *mockGenericStream { 41 return &mockGenericStream{num: num} 42 } 43 mockSender = NewMockStreamSender(mockCtrl) 44 m = newOutgoingStreamsMap[*mockGenericStream](streamType, newStr, mockSender.queueControlFrame) 45 }) 46 47 Context("no stream ID limit", func() { 48 BeforeEach(func() { 49 m.SetMaxStream(0xffffffff) 50 }) 51 52 It("opens streams", func() { 53 str, err := m.OpenStream() 54 Expect(err).ToNot(HaveOccurred()) 55 Expect(str.num).To(Equal(protocol.StreamNum(1))) 56 str, err = m.OpenStream() 57 Expect(err).ToNot(HaveOccurred()) 58 Expect(str.num).To(Equal(protocol.StreamNum(2))) 59 }) 60 61 It("doesn't open streams after it has been closed", func() { 62 testErr := errors.New("close") 63 m.CloseWithError(testErr) 64 _, err := m.OpenStream() 65 Expect(err).To(MatchError(testErr)) 66 }) 67 68 It("gets streams", func() { 69 _, err := m.OpenStream() 70 Expect(err).ToNot(HaveOccurred()) 71 str, err := m.GetStream(1) 72 Expect(err).ToNot(HaveOccurred()) 73 Expect(str.num).To(Equal(protocol.StreamNum(1))) 74 }) 75 76 It("errors when trying to get a stream that has not yet been opened", func() { 77 _, err := m.GetStream(1) 78 Expect(err).To(HaveOccurred()) 79 Expect(err.(streamError).TestError()).To(MatchError("peer attempted to open stream 1")) 80 }) 81 82 It("deletes streams", func() { 83 _, err := m.OpenStream() 84 Expect(err).ToNot(HaveOccurred()) 85 Expect(m.DeleteStream(1)).To(Succeed()) 86 Expect(err).ToNot(HaveOccurred()) 87 str, err := m.GetStream(1) 88 Expect(err).ToNot(HaveOccurred()) 89 Expect(str).To(BeNil()) 90 }) 91 92 It("errors when deleting a non-existing stream", func() { 93 err := m.DeleteStream(1337) 94 Expect(err).To(HaveOccurred()) 95 Expect(err.(streamError).TestError()).To(MatchError("tried to delete unknown outgoing stream 1337")) 96 }) 97 98 It("errors when deleting a stream twice", func() { 99 _, err := m.OpenStream() // opens firstNewStream 100 Expect(err).ToNot(HaveOccurred()) 101 Expect(m.DeleteStream(1)).To(Succeed()) 102 err = m.DeleteStream(1) 103 Expect(err).To(HaveOccurred()) 104 Expect(err.(streamError).TestError()).To(MatchError("tried to delete unknown outgoing stream 1")) 105 }) 106 107 It("closes all streams when CloseWithError is called", func() { 108 str1, err := m.OpenStream() 109 Expect(err).ToNot(HaveOccurred()) 110 str2, err := m.OpenStream() 111 Expect(err).ToNot(HaveOccurred()) 112 testErr := errors.New("test err") 113 m.CloseWithError(testErr) 114 Expect(str1.closed).To(BeTrue()) 115 Expect(str1.closeErr).To(MatchError(testErr)) 116 Expect(str2.closed).To(BeTrue()) 117 Expect(str2.closeErr).To(MatchError(testErr)) 118 }) 119 120 It("updates the send window", func() { 121 str1, err := m.OpenStream() 122 Expect(err).ToNot(HaveOccurred()) 123 str2, err := m.OpenStream() 124 Expect(err).ToNot(HaveOccurred()) 125 m.UpdateSendWindow(1337) 126 Expect(str1.sendWindow).To(BeEquivalentTo(1337)) 127 Expect(str2.sendWindow).To(BeEquivalentTo(1337)) 128 }) 129 }) 130 131 Context("with stream ID limits", func() { 132 It("errors when no stream can be opened immediately", func() { 133 mockSender.EXPECT().queueControlFrame(gomock.Any()) 134 _, err := m.OpenStream() 135 expectTooManyStreamsError(err) 136 }) 137 138 It("returns immediately when called with a canceled context", func() { 139 ctx, cancel := context.WithCancel(context.Background()) 140 cancel() 141 _, err := m.OpenStreamSync(ctx) 142 Expect(err).To(MatchError("context canceled")) 143 }) 144 145 It("blocks until a stream can be opened synchronously", func() { 146 mockSender.EXPECT().queueControlFrame(gomock.Any()) 147 done := make(chan struct{}) 148 go func() { 149 defer GinkgoRecover() 150 str, err := m.OpenStreamSync(context.Background()) 151 Expect(err).ToNot(HaveOccurred()) 152 Expect(str.num).To(Equal(protocol.StreamNum(1))) 153 close(done) 154 }() 155 waitForEnqueued(1) 156 157 m.SetMaxStream(1) 158 Eventually(done).Should(BeClosed()) 159 }) 160 161 It("unblocks when the context is canceled", func() { 162 mockSender.EXPECT().queueControlFrame(gomock.Any()) 163 ctx, cancel := context.WithCancel(context.Background()) 164 done := make(chan struct{}) 165 go func() { 166 defer GinkgoRecover() 167 _, err := m.OpenStreamSync(ctx) 168 Expect(err).To(MatchError("context canceled")) 169 close(done) 170 }() 171 waitForEnqueued(1) 172 173 cancel() 174 Eventually(done).Should(BeClosed()) 175 176 // make sure that the next stream opened is stream 1 177 m.SetMaxStream(1000) 178 str, err := m.OpenStream() 179 Expect(err).ToNot(HaveOccurred()) 180 Expect(str.num).To(Equal(protocol.StreamNum(1))) 181 }) 182 183 It("opens streams in the right order", func() { 184 mockSender.EXPECT().queueControlFrame(gomock.Any()).AnyTimes() 185 done1 := make(chan struct{}) 186 go func() { 187 defer GinkgoRecover() 188 str, err := m.OpenStreamSync(context.Background()) 189 Expect(err).ToNot(HaveOccurred()) 190 Expect(str.num).To(Equal(protocol.StreamNum(1))) 191 close(done1) 192 }() 193 waitForEnqueued(1) 194 195 done2 := make(chan struct{}) 196 go func() { 197 defer GinkgoRecover() 198 str, err := m.OpenStreamSync(context.Background()) 199 Expect(err).ToNot(HaveOccurred()) 200 Expect(str.num).To(Equal(protocol.StreamNum(2))) 201 close(done2) 202 }() 203 waitForEnqueued(2) 204 205 m.SetMaxStream(1) 206 Eventually(done1).Should(BeClosed()) 207 Consistently(done2).ShouldNot(BeClosed()) 208 m.SetMaxStream(2) 209 Eventually(done2).Should(BeClosed()) 210 }) 211 212 It("opens streams in the right order, when one of the contexts is canceled", func() { 213 mockSender.EXPECT().queueControlFrame(gomock.Any()).AnyTimes() 214 done1 := make(chan struct{}) 215 go func() { 216 defer GinkgoRecover() 217 str, err := m.OpenStreamSync(context.Background()) 218 Expect(err).ToNot(HaveOccurred()) 219 Expect(str.num).To(Equal(protocol.StreamNum(1))) 220 close(done1) 221 }() 222 waitForEnqueued(1) 223 224 done2 := make(chan struct{}) 225 ctx, cancel := context.WithCancel(context.Background()) 226 go func() { 227 defer GinkgoRecover() 228 _, err := m.OpenStreamSync(ctx) 229 Expect(err).To(MatchError(context.Canceled)) 230 close(done2) 231 }() 232 waitForEnqueued(2) 233 234 done3 := make(chan struct{}) 235 go func() { 236 defer GinkgoRecover() 237 str, err := m.OpenStreamSync(context.Background()) 238 Expect(err).ToNot(HaveOccurred()) 239 Expect(str.num).To(Equal(protocol.StreamNum(2))) 240 close(done3) 241 }() 242 waitForEnqueued(3) 243 244 cancel() 245 Eventually(done2).Should(BeClosed()) 246 m.SetMaxStream(1000) 247 Eventually(done1).Should(BeClosed()) 248 Eventually(done3).Should(BeClosed()) 249 }) 250 251 It("unblocks multiple OpenStreamSync calls at the same time", func() { 252 mockSender.EXPECT().queueControlFrame(gomock.Any()).AnyTimes() 253 done := make(chan struct{}) 254 go func() { 255 defer GinkgoRecover() 256 _, err := m.OpenStreamSync(context.Background()) 257 Expect(err).ToNot(HaveOccurred()) 258 done <- struct{}{} 259 }() 260 go func() { 261 defer GinkgoRecover() 262 _, err := m.OpenStreamSync(context.Background()) 263 Expect(err).ToNot(HaveOccurred()) 264 done <- struct{}{} 265 }() 266 waitForEnqueued(2) 267 go func() { 268 defer GinkgoRecover() 269 _, err := m.OpenStreamSync(context.Background()) 270 Expect(err).To(MatchError("test done")) 271 done <- struct{}{} 272 }() 273 waitForEnqueued(3) 274 275 m.SetMaxStream(2) 276 Eventually(done).Should(Receive()) 277 Eventually(done).Should(Receive()) 278 Consistently(done).ShouldNot(Receive()) 279 280 m.CloseWithError(errors.New("test done")) 281 Eventually(done).Should(Receive()) 282 }) 283 284 It("returns an error for OpenStream while an OpenStreamSync call is blocking", func() { 285 mockSender.EXPECT().queueControlFrame(gomock.Any()).MaxTimes(2) 286 openedSync := make(chan struct{}) 287 go func() { 288 defer GinkgoRecover() 289 str, err := m.OpenStreamSync(context.Background()) 290 Expect(err).ToNot(HaveOccurred()) 291 Expect(str.num).To(Equal(protocol.StreamNum(1))) 292 close(openedSync) 293 }() 294 waitForEnqueued(1) 295 296 start := make(chan struct{}) 297 openend := make(chan struct{}) 298 go func() { 299 defer GinkgoRecover() 300 var hasStarted bool 301 for { 302 str, err := m.OpenStream() 303 if err == nil { 304 Expect(str.num).To(Equal(protocol.StreamNum(2))) 305 close(openend) 306 return 307 } 308 expectTooManyStreamsError(err) 309 if !hasStarted { 310 close(start) 311 hasStarted = true 312 } 313 } 314 }() 315 316 Eventually(start).Should(BeClosed()) 317 m.SetMaxStream(1) 318 Eventually(openedSync).Should(BeClosed()) 319 Consistently(openend).ShouldNot(BeClosed()) 320 m.SetMaxStream(2) 321 Eventually(openend).Should(BeClosed()) 322 }) 323 324 It("stops opening synchronously when it is closed", func() { 325 mockSender.EXPECT().queueControlFrame(gomock.Any()) 326 testErr := errors.New("test error") 327 done := make(chan struct{}) 328 go func() { 329 defer GinkgoRecover() 330 _, err := m.OpenStreamSync(context.Background()) 331 Expect(err).To(MatchError(testErr)) 332 close(done) 333 }() 334 335 Consistently(done).ShouldNot(BeClosed()) 336 m.CloseWithError(testErr) 337 Eventually(done).Should(BeClosed()) 338 }) 339 340 It("doesn't reduce the stream limit", func() { 341 m.SetMaxStream(2) 342 m.SetMaxStream(1) 343 _, err := m.OpenStream() 344 Expect(err).ToNot(HaveOccurred()) 345 str, err := m.OpenStream() 346 Expect(err).ToNot(HaveOccurred()) 347 Expect(str.num).To(Equal(protocol.StreamNum(2))) 348 }) 349 350 It("queues a STREAMS_BLOCKED frame if no stream can be opened", func() { 351 m.SetMaxStream(6) 352 // open the 6 allowed streams 353 for i := 0; i < 6; i++ { 354 _, err := m.OpenStream() 355 Expect(err).ToNot(HaveOccurred()) 356 } 357 358 mockSender.EXPECT().queueControlFrame(gomock.Any()).Do(func(f wire.Frame) { 359 bf := f.(*wire.StreamsBlockedFrame) 360 Expect(bf.Type).To(BeEquivalentTo(streamType)) 361 Expect(bf.StreamLimit).To(BeEquivalentTo(6)) 362 }) 363 _, err := m.OpenStream() 364 Expect(err).To(HaveOccurred()) 365 Expect(err.Error()).To(Equal(errTooManyOpenStreams.Error())) 366 }) 367 368 It("only sends one STREAMS_BLOCKED frame for one stream ID", func() { 369 m.SetMaxStream(1) 370 mockSender.EXPECT().queueControlFrame(gomock.Any()).Do(func(f wire.Frame) { 371 Expect(f.(*wire.StreamsBlockedFrame).StreamLimit).To(BeEquivalentTo(1)) 372 }) 373 _, err := m.OpenStream() 374 Expect(err).ToNot(HaveOccurred()) 375 // try to open a stream twice, but expect only one STREAMS_BLOCKED to be sent 376 _, err = m.OpenStream() 377 expectTooManyStreamsError(err) 378 _, err = m.OpenStream() 379 expectTooManyStreamsError(err) 380 }) 381 382 It("queues a STREAMS_BLOCKED frame when there more streams waiting for OpenStreamSync than MAX_STREAMS allows", func() { 383 mockSender.EXPECT().queueControlFrame(gomock.Any()).Do(func(f wire.Frame) { 384 Expect(f.(*wire.StreamsBlockedFrame).StreamLimit).To(BeEquivalentTo(0)) 385 }) 386 done := make(chan struct{}, 2) 387 go func() { 388 defer GinkgoRecover() 389 _, err := m.OpenStreamSync(context.Background()) 390 Expect(err).ToNot(HaveOccurred()) 391 done <- struct{}{} 392 }() 393 go func() { 394 defer GinkgoRecover() 395 _, err := m.OpenStreamSync(context.Background()) 396 Expect(err).ToNot(HaveOccurred()) 397 done <- struct{}{} 398 }() 399 waitForEnqueued(2) 400 401 mockSender.EXPECT().queueControlFrame(gomock.Any()).Do(func(f wire.Frame) { 402 Expect(f.(*wire.StreamsBlockedFrame).StreamLimit).To(BeEquivalentTo(1)) 403 }) 404 m.SetMaxStream(1) 405 Eventually(done).Should(Receive()) 406 Consistently(done).ShouldNot(Receive()) 407 m.SetMaxStream(2) 408 Eventually(done).Should(Receive()) 409 }) 410 }) 411 412 Context("randomized tests", func() { 413 It("opens streams", func() { 414 rand.Seed(uint64(GinkgoRandomSeed())) 415 const n = 100 416 fmt.Fprintf(GinkgoWriter, "Opening %d streams concurrently.\n", n) 417 418 var blockedAt []protocol.StreamNum 419 mockSender.EXPECT().queueControlFrame(gomock.Any()).Do(func(f wire.Frame) { 420 blockedAt = append(blockedAt, f.(*wire.StreamsBlockedFrame).StreamLimit) 421 }).AnyTimes() 422 done := make(map[int]chan struct{}) 423 for i := 1; i <= n; i++ { 424 c := make(chan struct{}) 425 done[i] = c 426 427 go func(doneChan chan struct{}, id protocol.StreamNum) { 428 defer GinkgoRecover() 429 defer close(doneChan) 430 str, err := m.OpenStreamSync(context.Background()) 431 Expect(err).ToNot(HaveOccurred()) 432 Expect(str.num).To(Equal(id)) 433 }(c, protocol.StreamNum(i)) 434 waitForEnqueued(i) 435 } 436 437 var limit int 438 limits := []protocol.StreamNum{0} 439 for limit < n { 440 limit += rand.Intn(n/5) + 1 441 if limit <= n { 442 limits = append(limits, protocol.StreamNum(limit)) 443 } 444 fmt.Fprintf(GinkgoWriter, "Setting stream limit to %d.\n", limit) 445 m.SetMaxStream(protocol.StreamNum(limit)) 446 for i := 1; i <= n; i++ { 447 if i <= limit { 448 Eventually(done[i]).Should(BeClosed()) 449 } else { 450 Expect(done[i]).ToNot(BeClosed()) 451 } 452 } 453 str, err := m.OpenStream() 454 if limit <= n { 455 Expect(err).To(HaveOccurred()) 456 Expect(err.Error()).To(Equal(errTooManyOpenStreams.Error())) 457 } else { 458 Expect(str.num).To(Equal(protocol.StreamNum(n + 1))) 459 } 460 } 461 Expect(blockedAt).To(Equal(limits)) 462 }) 463 464 It("opens streams, when some of them are getting canceled", func() { 465 rand.Seed(uint64(GinkgoRandomSeed())) 466 const n = 100 467 fmt.Fprintf(GinkgoWriter, "Opening %d streams concurrently.\n", n) 468 469 var blockedAt []protocol.StreamNum 470 mockSender.EXPECT().queueControlFrame(gomock.Any()).Do(func(f wire.Frame) { 471 blockedAt = append(blockedAt, f.(*wire.StreamsBlockedFrame).StreamLimit) 472 }).AnyTimes() 473 474 ctx, cancel := context.WithCancel(context.Background()) 475 streamsToCancel := make(map[protocol.StreamNum]struct{}) // used as a set 476 for i := 0; i < 10; i++ { 477 id := protocol.StreamNum(rand.Intn(n) + 1) 478 fmt.Fprintf(GinkgoWriter, "Canceling stream %d.\n", id) 479 streamsToCancel[id] = struct{}{} 480 } 481 482 streamWillBeCanceled := func(id protocol.StreamNum) bool { 483 _, ok := streamsToCancel[id] 484 return ok 485 } 486 487 var streamIDs []int 488 var mutex sync.Mutex 489 done := make(map[int]chan struct{}) 490 for i := 1; i <= n; i++ { 491 c := make(chan struct{}) 492 done[i] = c 493 494 go func(doneChan chan struct{}, id protocol.StreamNum) { 495 defer GinkgoRecover() 496 defer close(doneChan) 497 cont := context.Background() 498 if streamWillBeCanceled(id) { 499 cont = ctx 500 } 501 str, err := m.OpenStreamSync(cont) 502 if streamWillBeCanceled(id) { 503 Expect(err).To(MatchError(context.Canceled)) 504 return 505 } 506 Expect(err).ToNot(HaveOccurred()) 507 mutex.Lock() 508 streamIDs = append(streamIDs, int(str.num)) 509 mutex.Unlock() 510 }(c, protocol.StreamNum(i)) 511 waitForEnqueued(i) 512 } 513 514 cancel() 515 for id := range streamsToCancel { 516 Eventually(done[int(id)]).Should(BeClosed()) 517 } 518 var limit int 519 numStreams := n - len(streamsToCancel) 520 var limits []protocol.StreamNum 521 for limit < numStreams { 522 limits = append(limits, protocol.StreamNum(limit)) 523 limit += rand.Intn(n/5) + 1 524 fmt.Fprintf(GinkgoWriter, "Setting stream limit to %d.\n", limit) 525 m.SetMaxStream(protocol.StreamNum(limit)) 526 l := limit 527 if l > numStreams { 528 l = numStreams 529 } 530 Eventually(func() int { 531 mutex.Lock() 532 defer mutex.Unlock() 533 return len(streamIDs) 534 }).Should(Equal(l)) 535 // check that all stream IDs were used 536 Expect(streamIDs).To(HaveLen(l)) 537 sort.Ints(streamIDs) 538 for i := 0; i < l; i++ { 539 Expect(streamIDs[i]).To(Equal(i + 1)) 540 } 541 } 542 Expect(blockedAt).To(Equal(limits)) 543 }) 544 }) 545 })