Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add more comments to TestMaxInflight #17674

Merged
merged 1 commit into from
Nov 24, 2015
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
82 changes: 50 additions & 32 deletions pkg/apiserver/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,71 +57,89 @@ func pathWithPrefix(prefix, resource, namespace, name string) string {
return testapi.Default.ResourcePathWithPrefix(prefix, resource, namespace, name)
}

// Tests that MaxInFlightLimit works, i.e.
// - "long" requests such as proxy or watch, identified by regexp are not accounted despite
// hanging for the long time,
// - "short" requests are correctly accounted, i.e. there can be only size of channel passed to the
// constructor in flight at any given moment,
// - subsequent "short" requests are rejected instantly with apropriate error,
// - subsequent "long" requests are handled normally,
// - we correctly recover after some "short" requests finish, i.e. we can process new ones.
func TestMaxInFlight(t *testing.T) {
const Iterations = 3
const AllowedInflightRequestsNo = 3
// Size of inflightRequestsChannel determines how many concurent inflight requests
// are allowed.
inflightRequestsChannel := make(chan bool, AllowedInflightRequestsNo)
// notAccountedPathsRegexp specifies paths requests to which we don't account into
// requests in flight.
notAccountedPathsRegexp := regexp.MustCompile(".*\\/watch")

// Calls is used to wait until all server calls are received. We are sending
// AllowedInflightRequestsNo of 'long' not-accounted requests and the same number of
// 'short' accounted ones.
calls := &sync.WaitGroup{}
calls.Add(AllowedInflightRequestsNo * 2)
// Block is used to keep requests in flight for as long as we need to. All requests will
// be unblocked at the same time.
block := sync.WaitGroup{}
block.Add(1)
oneAccountedFinished := sync.WaitGroup{}
oneAccountedFinished.Add(1)
var once sync.Once
sem := make(chan bool, Iterations)

re := regexp.MustCompile("[.*\\/watch][^\\/proxy.*]")

// Calls verifies that the server is actually blocked up before running the rest of the test
calls := &sync.WaitGroup{}
calls.Add(Iterations * 3)

server := httptest.NewServer(MaxInFlightLimit(sem, re, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.Contains(r.URL.Path, "dontwait") {
return
}
if calls != nil {
calls.Done()
}
block.Wait()
})))
server := httptest.NewServer(
MaxInFlightLimit(
inflightRequestsChannel,
notAccountedPathsRegexp,
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// A short, accounted request that does not wait for block WaitGroup.
if strings.Contains(r.URL.Path, "dontwait") {
return
}
if calls != nil {
calls.Done()
}
block.Wait()
}),
),
)
defer server.Close()

// These should hang, but not affect accounting.
for i := 0; i < Iterations; i++ {
for i := 0; i < AllowedInflightRequestsNo; i++ {
// These should hang waiting on block...
go func() {
expectHTTP(server.URL+"/foo/bar/watch", http.StatusOK, t)
}()
}

// These should hang, but not affect accounting.
for i := 0; i < Iterations; i++ {
// These should hang waiting on block...
go func() {
expectHTTP(server.URL+"/proxy/foo/bar", http.StatusOK, t)
}()
}
// Check that sever is not saturated by not-accounted calls
expectHTTP(server.URL+"/dontwait", http.StatusOK, t)
oneAccountedFinished := sync.WaitGroup{}
oneAccountedFinished.Add(1)
var once sync.Once

for i := 0; i < Iterations; i++ {
// These should hang and be accounted, i.e. saturate the server
for i := 0; i < AllowedInflightRequestsNo; i++ {
// These should hang waiting on block...
go func() {
expectHTTP(server.URL, http.StatusOK, t)
once.Do(oneAccountedFinished.Done)
}()
}
// We wait for all calls to be received by the server
calls.Wait()
// Disable calls notifications in the server
calls = nil

// Do this multiple times to show that it rate limit rejected requests don't block.
for i := 0; i < 2; i++ {
expectHTTP(server.URL, errors.StatusTooManyRequests, t)
}

// Validate that non-accounted URLs still work
expectHTTP(server.URL+"/dontwait/watch", http.StatusOK, t)

// Let all hanging requests finish
block.Done()

// Show that we recover from being blocked up.
// However, we should until at least one of the requests really finishes.
// Too avoid flakyness we need to wait until at least one of the requests really finishes.
oneAccountedFinished.Wait()
expectHTTP(server.URL, http.StatusOK, t)
}
Expand Down