forked from ebhomengo/niki
1
0
Fork 0
niki/vendor/golang.org/x/net/http2/testsync.go

584 lines
7.7 KiB
Go
Raw Normal View History

2024-05-14 13:07:09 +00:00
// Copyright 2024 The Go Authors. All rights reserved.
2024-05-14 13:07:09 +00:00
// Use of this source code is governed by a BSD-style
2024-05-14 13:07:09 +00:00
// license that can be found in the LICENSE file.
2024-05-14 13:07:09 +00:00
package http2
import (
"context"
"sync"
"time"
)
// testSyncHooks coordinates goroutines in tests.
2024-05-14 13:07:09 +00:00
//
2024-05-14 13:07:09 +00:00
// For example, a call to ClientConn.RoundTrip involves several goroutines, including:
2024-05-14 13:07:09 +00:00
// - the goroutine running RoundTrip;
2024-05-14 13:07:09 +00:00
// - the clientStream.doRequest goroutine, which writes the request; and
2024-05-14 13:07:09 +00:00
// - the clientStream.readLoop goroutine, which reads the response.
2024-05-14 13:07:09 +00:00
//
2024-05-14 13:07:09 +00:00
// Using testSyncHooks, a test can start a RoundTrip and identify when all these goroutines
2024-05-14 13:07:09 +00:00
// are blocked waiting for some condition such as reading the Request.Body or waiting for
2024-05-14 13:07:09 +00:00
// flow control to become available.
2024-05-14 13:07:09 +00:00
//
2024-05-14 13:07:09 +00:00
// The testSyncHooks also manage timers and synthetic time in tests.
2024-05-14 13:07:09 +00:00
// This permits us to, for example, start a request and cause it to time out waiting for
2024-05-14 13:07:09 +00:00
// response headers without resorting to time.Sleep calls.
2024-05-14 13:07:09 +00:00
type testSyncHooks struct {
2024-05-14 13:07:09 +00:00
// active/inactive act as a mutex and condition variable.
2024-05-14 13:07:09 +00:00
//
2024-05-14 13:07:09 +00:00
// - neither chan contains a value: testSyncHooks is locked.
2024-05-14 13:07:09 +00:00
// - active contains a value: unlocked, and at least one goroutine is not blocked
2024-05-14 13:07:09 +00:00
// - inactive contains a value: unlocked, and all goroutines are blocked
active chan struct{}
2024-05-14 13:07:09 +00:00
inactive chan struct{}
// goroutine counts
total int // total goroutines
condwait map[*sync.Cond]int // blocked in sync.Cond.Wait
blocked []*testBlockedGoroutine // otherwise blocked
2024-05-14 13:07:09 +00:00
// fake time
now time.Time
2024-05-14 13:07:09 +00:00
timers []*fakeTimer
// Transport testing: Report various events.
2024-05-14 13:07:09 +00:00
newclientconn func(*ClientConn)
newstream func(*clientStream)
2024-05-14 13:07:09 +00:00
}
// testBlockedGoroutine is a blocked goroutine.
2024-05-14 13:07:09 +00:00
type testBlockedGoroutine struct {
f func() bool // blocked until f returns true
2024-05-14 13:07:09 +00:00
ch chan struct{} // closed when unblocked
2024-05-14 13:07:09 +00:00
}
func newTestSyncHooks() *testSyncHooks {
2024-05-14 13:07:09 +00:00
h := &testSyncHooks{
active: make(chan struct{}, 1),
2024-05-14 13:07:09 +00:00
inactive: make(chan struct{}, 1),
2024-05-14 13:07:09 +00:00
condwait: map[*sync.Cond]int{},
}
2024-05-14 13:07:09 +00:00
h.inactive <- struct{}{}
2024-05-14 13:07:09 +00:00
h.now = time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)
2024-05-14 13:07:09 +00:00
return h
2024-05-14 13:07:09 +00:00
}
// lock acquires the testSyncHooks mutex.
2024-05-14 13:07:09 +00:00
func (h *testSyncHooks) lock() {
2024-05-14 13:07:09 +00:00
select {
2024-05-14 13:07:09 +00:00
case <-h.active:
2024-05-14 13:07:09 +00:00
case <-h.inactive:
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
}
// waitInactive waits for all goroutines to become inactive.
2024-05-14 13:07:09 +00:00
func (h *testSyncHooks) waitInactive() {
2024-05-14 13:07:09 +00:00
for {
2024-05-14 13:07:09 +00:00
<-h.inactive
2024-05-14 13:07:09 +00:00
if !h.unlock() {
2024-05-14 13:07:09 +00:00
break
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
}
// unlock releases the testSyncHooks mutex.
2024-05-14 13:07:09 +00:00
// It reports whether any goroutines are active.
2024-05-14 13:07:09 +00:00
func (h *testSyncHooks) unlock() (active bool) {
2024-05-14 13:07:09 +00:00
// Look for a blocked goroutine which can be unblocked.
2024-05-14 13:07:09 +00:00
blocked := h.blocked[:0]
2024-05-14 13:07:09 +00:00
unblocked := false
2024-05-14 13:07:09 +00:00
for _, b := range h.blocked {
2024-05-14 13:07:09 +00:00
if !unblocked && b.f() {
2024-05-14 13:07:09 +00:00
unblocked = true
2024-05-14 13:07:09 +00:00
close(b.ch)
2024-05-14 13:07:09 +00:00
} else {
2024-05-14 13:07:09 +00:00
blocked = append(blocked, b)
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
h.blocked = blocked
// Count goroutines blocked on condition variables.
2024-05-14 13:07:09 +00:00
condwait := 0
2024-05-14 13:07:09 +00:00
for _, count := range h.condwait {
2024-05-14 13:07:09 +00:00
condwait += count
2024-05-14 13:07:09 +00:00
}
if h.total > condwait+len(blocked) {
2024-05-14 13:07:09 +00:00
h.active <- struct{}{}
2024-05-14 13:07:09 +00:00
return true
2024-05-14 13:07:09 +00:00
} else {
2024-05-14 13:07:09 +00:00
h.inactive <- struct{}{}
2024-05-14 13:07:09 +00:00
return false
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
}
// goRun starts a new goroutine.
2024-05-14 13:07:09 +00:00
func (h *testSyncHooks) goRun(f func()) {
2024-05-14 13:07:09 +00:00
h.lock()
2024-05-14 13:07:09 +00:00
h.total++
2024-05-14 13:07:09 +00:00
h.unlock()
2024-05-14 13:07:09 +00:00
go func() {
2024-05-14 13:07:09 +00:00
defer func() {
2024-05-14 13:07:09 +00:00
h.lock()
2024-05-14 13:07:09 +00:00
h.total--
2024-05-14 13:07:09 +00:00
h.unlock()
2024-05-14 13:07:09 +00:00
}()
2024-05-14 13:07:09 +00:00
f()
2024-05-14 13:07:09 +00:00
}()
2024-05-14 13:07:09 +00:00
}
// blockUntil indicates that a goroutine is blocked waiting for some condition to become true.
2024-05-14 13:07:09 +00:00
// It waits until f returns true before proceeding.
2024-05-14 13:07:09 +00:00
//
2024-05-14 13:07:09 +00:00
// Example usage:
2024-05-14 13:07:09 +00:00
//
2024-05-14 13:07:09 +00:00
// h.blockUntil(func() bool {
2024-05-14 13:07:09 +00:00
// // Is the context done yet?
2024-05-14 13:07:09 +00:00
// select {
2024-05-14 13:07:09 +00:00
// case <-ctx.Done():
2024-05-14 13:07:09 +00:00
// default:
2024-05-14 13:07:09 +00:00
// return false
2024-05-14 13:07:09 +00:00
// }
2024-05-14 13:07:09 +00:00
// return true
2024-05-14 13:07:09 +00:00
// })
2024-05-14 13:07:09 +00:00
// // Wait for the context to become done.
2024-05-14 13:07:09 +00:00
// <-ctx.Done()
2024-05-14 13:07:09 +00:00
//
2024-05-14 13:07:09 +00:00
// The function f passed to blockUntil must be non-blocking and idempotent.
2024-05-14 13:07:09 +00:00
func (h *testSyncHooks) blockUntil(f func() bool) {
2024-05-14 13:07:09 +00:00
if f() {
2024-05-14 13:07:09 +00:00
return
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
ch := make(chan struct{})
2024-05-14 13:07:09 +00:00
h.lock()
2024-05-14 13:07:09 +00:00
h.blocked = append(h.blocked, &testBlockedGoroutine{
f: f,
2024-05-14 13:07:09 +00:00
ch: ch,
})
2024-05-14 13:07:09 +00:00
h.unlock()
2024-05-14 13:07:09 +00:00
<-ch
2024-05-14 13:07:09 +00:00
}
// broadcast is sync.Cond.Broadcast.
2024-05-14 13:07:09 +00:00
func (h *testSyncHooks) condBroadcast(cond *sync.Cond) {
2024-05-14 13:07:09 +00:00
h.lock()
2024-05-14 13:07:09 +00:00
delete(h.condwait, cond)
2024-05-14 13:07:09 +00:00
h.unlock()
2024-05-14 13:07:09 +00:00
cond.Broadcast()
2024-05-14 13:07:09 +00:00
}
// broadcast is sync.Cond.Wait.
2024-05-14 13:07:09 +00:00
func (h *testSyncHooks) condWait(cond *sync.Cond) {
2024-05-14 13:07:09 +00:00
h.lock()
2024-05-14 13:07:09 +00:00
h.condwait[cond]++
2024-05-14 13:07:09 +00:00
h.unlock()
2024-05-14 13:07:09 +00:00
}
// newTimer creates a new fake timer.
2024-05-14 13:07:09 +00:00
func (h *testSyncHooks) newTimer(d time.Duration) timer {
2024-05-14 13:07:09 +00:00
h.lock()
2024-05-14 13:07:09 +00:00
defer h.unlock()
2024-05-14 13:07:09 +00:00
t := &fakeTimer{
2024-05-14 13:07:09 +00:00
hooks: h,
when: h.now.Add(d),
c: make(chan time.Time),
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
h.timers = append(h.timers, t)
2024-05-14 13:07:09 +00:00
return t
2024-05-14 13:07:09 +00:00
}
// afterFunc creates a new fake AfterFunc timer.
2024-05-14 13:07:09 +00:00
func (h *testSyncHooks) afterFunc(d time.Duration, f func()) timer {
2024-05-14 13:07:09 +00:00
h.lock()
2024-05-14 13:07:09 +00:00
defer h.unlock()
2024-05-14 13:07:09 +00:00
t := &fakeTimer{
2024-05-14 13:07:09 +00:00
hooks: h,
when: h.now.Add(d),
f: f,
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
h.timers = append(h.timers, t)
2024-05-14 13:07:09 +00:00
return t
2024-05-14 13:07:09 +00:00
}
func (h *testSyncHooks) contextWithTimeout(ctx context.Context, d time.Duration) (context.Context, context.CancelFunc) {
2024-05-14 13:07:09 +00:00
ctx, cancel := context.WithCancel(ctx)
2024-05-14 13:07:09 +00:00
t := h.afterFunc(d, cancel)
2024-05-14 13:07:09 +00:00
return ctx, func() {
2024-05-14 13:07:09 +00:00
t.Stop()
2024-05-14 13:07:09 +00:00
cancel()
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
}
func (h *testSyncHooks) timeUntilEvent() time.Duration {
2024-05-14 13:07:09 +00:00
h.lock()
2024-05-14 13:07:09 +00:00
defer h.unlock()
2024-05-14 13:07:09 +00:00
var next time.Time
2024-05-14 13:07:09 +00:00
for _, t := range h.timers {
2024-05-14 13:07:09 +00:00
if next.IsZero() || t.when.Before(next) {
2024-05-14 13:07:09 +00:00
next = t.when
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
if d := next.Sub(h.now); d > 0 {
2024-05-14 13:07:09 +00:00
return d
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
return 0
2024-05-14 13:07:09 +00:00
}
// advance advances time and causes synthetic timers to fire.
2024-05-14 13:07:09 +00:00
func (h *testSyncHooks) advance(d time.Duration) {
2024-05-14 13:07:09 +00:00
h.lock()
2024-05-14 13:07:09 +00:00
defer h.unlock()
2024-05-14 13:07:09 +00:00
h.now = h.now.Add(d)
2024-05-14 13:07:09 +00:00
timers := h.timers[:0]
2024-05-14 13:07:09 +00:00
for _, t := range h.timers {
2024-05-14 13:07:09 +00:00
t := t // remove after go.mod depends on go1.22
2024-05-14 13:07:09 +00:00
t.mu.Lock()
2024-05-14 13:07:09 +00:00
switch {
2024-05-14 13:07:09 +00:00
case t.when.After(h.now):
2024-05-14 13:07:09 +00:00
timers = append(timers, t)
2024-05-14 13:07:09 +00:00
case t.when.IsZero():
2024-05-14 13:07:09 +00:00
// stopped timer
2024-05-14 13:07:09 +00:00
default:
2024-05-14 13:07:09 +00:00
t.when = time.Time{}
2024-05-14 13:07:09 +00:00
if t.c != nil {
2024-05-14 13:07:09 +00:00
close(t.c)
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
if t.f != nil {
2024-05-14 13:07:09 +00:00
h.total++
2024-05-14 13:07:09 +00:00
go func() {
2024-05-14 13:07:09 +00:00
defer func() {
2024-05-14 13:07:09 +00:00
h.lock()
2024-05-14 13:07:09 +00:00
h.total--
2024-05-14 13:07:09 +00:00
h.unlock()
2024-05-14 13:07:09 +00:00
}()
2024-05-14 13:07:09 +00:00
t.f()
2024-05-14 13:07:09 +00:00
}()
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
t.mu.Unlock()
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
h.timers = timers
2024-05-14 13:07:09 +00:00
}
// A timer wraps a time.Timer, or a synthetic equivalent in tests.
2024-05-14 13:07:09 +00:00
// Unlike time.Timer, timer is single-use: The timer channel is closed when the timer expires.
2024-05-14 13:07:09 +00:00
type timer interface {
C() <-chan time.Time
2024-05-14 13:07:09 +00:00
Stop() bool
2024-05-14 13:07:09 +00:00
Reset(d time.Duration) bool
}
// timeTimer implements timer using real time.
2024-05-14 13:07:09 +00:00
type timeTimer struct {
t *time.Timer
2024-05-14 13:07:09 +00:00
c chan time.Time
}
// newTimeTimer creates a new timer using real time.
2024-05-14 13:07:09 +00:00
func newTimeTimer(d time.Duration) timer {
2024-05-14 13:07:09 +00:00
ch := make(chan time.Time)
2024-05-14 13:07:09 +00:00
t := time.AfterFunc(d, func() {
2024-05-14 13:07:09 +00:00
close(ch)
2024-05-14 13:07:09 +00:00
})
2024-05-14 13:07:09 +00:00
return &timeTimer{t, ch}
2024-05-14 13:07:09 +00:00
}
// newTimeAfterFunc creates an AfterFunc timer using real time.
2024-05-14 13:07:09 +00:00
func newTimeAfterFunc(d time.Duration, f func()) timer {
2024-05-14 13:07:09 +00:00
return &timeTimer{
2024-05-14 13:07:09 +00:00
t: time.AfterFunc(d, f),
}
2024-05-14 13:07:09 +00:00
}
func (t timeTimer) C() <-chan time.Time { return t.c }
func (t timeTimer) Stop() bool { return t.t.Stop() }
2024-05-14 13:07:09 +00:00
func (t timeTimer) Reset(d time.Duration) bool { return t.t.Reset(d) }
// fakeTimer implements timer using fake time.
2024-05-14 13:07:09 +00:00
type fakeTimer struct {
hooks *testSyncHooks
mu sync.Mutex
when time.Time // when the timer will fire
c chan time.Time // closed when the timer fires; mutually exclusive with f
f func() // called when the timer fires; mutually exclusive with c
2024-05-14 13:07:09 +00:00
}
func (t *fakeTimer) C() <-chan time.Time { return t.c }
func (t *fakeTimer) Stop() bool {
2024-05-14 13:07:09 +00:00
t.mu.Lock()
2024-05-14 13:07:09 +00:00
defer t.mu.Unlock()
2024-05-14 13:07:09 +00:00
stopped := t.when.IsZero()
2024-05-14 13:07:09 +00:00
t.when = time.Time{}
2024-05-14 13:07:09 +00:00
return stopped
2024-05-14 13:07:09 +00:00
}
func (t *fakeTimer) Reset(d time.Duration) bool {
2024-05-14 13:07:09 +00:00
if t.c != nil || t.f == nil {
2024-05-14 13:07:09 +00:00
panic("fakeTimer only supports Reset on AfterFunc timers")
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
t.mu.Lock()
2024-05-14 13:07:09 +00:00
defer t.mu.Unlock()
2024-05-14 13:07:09 +00:00
t.hooks.lock()
2024-05-14 13:07:09 +00:00
defer t.hooks.unlock()
2024-05-14 13:07:09 +00:00
active := !t.when.IsZero()
2024-05-14 13:07:09 +00:00
t.when = t.hooks.now.Add(d)
2024-05-14 13:07:09 +00:00
if !active {
2024-05-14 13:07:09 +00:00
t.hooks.timers = append(t.hooks.timers, t)
2024-05-14 13:07:09 +00:00
}
2024-05-14 13:07:09 +00:00
return active
2024-05-14 13:07:09 +00:00
}