Skip to content

Commit f91cdf7

Browse files
committed
fixup! add test to document behavior of net/http read/write deadline
1 parent 2abe3a5 commit f91cdf7

File tree

1 file changed

+77
-39
lines changed

1 file changed

+77
-39
lines changed

staging/src/k8s.io/apiserver/pkg/endpoints/filters/read_write_deadline_test.go

Lines changed: 77 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,12 @@ import (
3333
"k8s.io/apimachinery/pkg/util/wait"
3434
)
3535

36-
func TestPerRequestWriteDeadlineWithWriteShouldReturnErrorAfterTimeout(t *testing.T) {
36+
// NOTE: The following tests assert on low-level error returned from net/http, if
37+
// a new version of Go changes the error type returned, then some of these tests
38+
// might fail during GoLang update, we can fix the broken test(s) by changing the
39+
// wanted error in the test setup to the new observed error.
40+
41+
func TestPerRequestWithWriteEventuallyReturnsDeadlineError(t *testing.T) {
3742
// This test documents the behavior of the per request write deadline
3843
// using a standard net http server.
3944
//
@@ -46,6 +51,17 @@ func TestPerRequestWriteDeadlineWithWriteShouldReturnErrorAfterTimeout(t *testin
4651
// - d) client: expected to receive an error from the server
4752
// - e) server: the Write method of the ResponseWriter object should
4853
// return an "i/o timeout" error once its internal buffer is full
54+
//
55+
// The size of the internal buffer is 4kB bytes:
56+
//
57+
// http/1x: the net.Conn is written to by a buffered Writer
58+
// (*bufio.Writer) of default size of 4kB bytes:
59+
// a) https://github.com/golang/go/blob/ffb3e574012ce9d3d5193d7b8df135189b8a6671/src/net/http/server.go#L1650
60+
// b) https://github.com/golang/go/blob/ffb3e574012ce9d3d5193d7b8df135189b8a6671/src/net/http/server.go#L2014
61+
//
62+
// http/2.0: a buffered writer (4kB bytes) writes to the unerlying net.Conn
63+
// a) https://github.com/golang/go/blob/ffb3e574012ce9d3d5193d7b8df135189b8a6671/src/net/http/h2_bundle.go#L3568
64+
// b) https://github.com/golang/go/blob/ffb3e574012ce9d3d5193d7b8df135189b8a6671/src/net/http/h2_bundle.go#L4279
4965
t.Parallel()
5066

5167
const deadline = 100 * time.Millisecond
@@ -57,6 +73,10 @@ func TestPerRequestWriteDeadlineWithWriteShouldReturnErrorAfterTimeout(t *testin
5773
}{
5874
{
5975
protoMajor: 1, // http/1x
76+
// NOTE: we can't use channel based waiiter for http/1x,
77+
// since the request handler is executed in the same
78+
// goroutine as the connection serving goroutine
79+
// https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/4460-per-request-deadline#client-hanging-indefinitely
6080
// write timeout is set to 100ms, a wait of 5s should be
6181
// enough to withstand flakes in CI.
6282
waiter: &waitWithDuration{after: 5 * time.Second},
@@ -65,7 +85,7 @@ func TestPerRequestWriteDeadlineWithWriteShouldReturnErrorAfterTimeout(t *testin
6585
},
6686
{
6787
protoMajor: 2, // http/2.0
68-
waiter: &waitWithChannelClose{after: make(chan time.Time)},
88+
waiter: &waitForClose{after: make(chan time.Time)},
6989
clientErrVerifier: wantContains{"stream error: stream ID 1; INTERNAL_ERROR; received from peer"},
7090
handlerErrVerifier: wantError{err: os.ErrDeadlineExceeded},
7191
},
@@ -90,8 +110,8 @@ func TestPerRequestWriteDeadlineWithWriteShouldReturnErrorAfterTimeout(t *testin
90110
// b) wait until the write deadline exceeds
91111
<-test.waiter.wait()
92112

93-
// c) keep writing 1kB of data at a time until
94-
// Write returns an error
113+
// c) keep writing 1kB of data at a time, Write
114+
// will eventually return an i/o timeout error
95115
func() {
96116
now := time.Now()
97117
written := 0
@@ -122,6 +142,9 @@ func TestPerRequestWriteDeadlineWithWriteShouldReturnErrorAfterTimeout(t *testin
122142

123143
_, err := client.Get(server.URL)
124144
// d) verify that the client receives the appropriate error
145+
// the client should not see a response body since the timeout
146+
// exceeded before the handler returned, and the handler
147+
// never invoked flush before the timeout.
125148
test.clientErrVerifier.verify(t, err)
126149
}()
127150

@@ -132,7 +155,7 @@ func TestPerRequestWriteDeadlineWithWriteShouldReturnErrorAfterTimeout(t *testin
132155
}
133156
}
134157

135-
func TestPerRequestWriteDeadlineWithTimeoutOccursBeforeHandlerWrites(t *testing.T) {
158+
func TestPerRequestWithFlushReturnsErrorAfterDeadline(t *testing.T) {
136159
// This test documents the behavior of the per request write deadline
137160
// using a standard net http server.
138161
//
@@ -166,7 +189,7 @@ func TestPerRequestWriteDeadlineWithTimeoutOccursBeforeHandlerWrites(t *testing.
166189
},
167190
{
168191
protoMajor: 2,
169-
waiter: &waitWithChannelClose{after: make(chan time.Time)},
192+
waiter: &waitForClose{after: make(chan time.Time)},
170193
clientErrVerifier: wantContains{"stream error: stream ID 1; INTERNAL_ERROR; received from peer"},
171194
handlerErrVerifier: wantError{err: os.ErrDeadlineExceeded},
172195
},
@@ -198,6 +221,8 @@ func TestPerRequestWriteDeadlineWithTimeoutOccursBeforeHandlerWrites(t *testing.
198221

199222
// c) write a few bytes so there is something in the buffer to
200223
// flush, the client should never see these bytes in the response.
224+
// NOTE: since the intenal buffered Writer has a size of 4kB, this
225+
// Write operation should not cause an actual write to the net.Conn
201226
if _, err := w.Write([]byte("hello")); err != nil {
202227
t.Errorf("server: unexpected error from Write after timeout: %v", err)
203228
return
@@ -235,7 +260,7 @@ func TestPerRequestWriteDeadlineWithTimeoutOccursBeforeHandlerWrites(t *testing.
235260
}
236261
}
237262

238-
func TestPerRequestWriteDeadlineWithTimeoutOccursAfterHandlerWrites(t *testing.T) {
263+
func TestPerRequestWithClientNeverReceivesContentFlushedAfterDeadline(t *testing.T) {
239264
// This test documents the behavior of the per handler write
240265
// deadline using a standard net http server.
241266
//
@@ -269,7 +294,7 @@ func TestPerRequestWriteDeadlineWithTimeoutOccursAfterHandlerWrites(t *testing.T
269294
},
270295
{
271296
protoMajor: 2,
272-
waiter: &waitWithChannelClose{after: make(chan time.Time)},
297+
waiter: &waitForClose{after: make(chan time.Time)},
273298
clientErrVerifier: wantContains{"stream error: stream ID 1; INTERNAL_ERROR; received from peer"},
274299
handlerErrVerifier: wantError{err: os.ErrDeadlineExceeded},
275300
},
@@ -323,6 +348,9 @@ func TestPerRequestWriteDeadlineWithTimeoutOccursAfterHandlerWrites(t *testing.T
323348

324349
_, err := server.Client().Get(server.URL)
325350
// f) verify that the client receives the appropriate error
351+
// NOTE: due to 'a' (the handler wrote but did not flush), and
352+
// since the handler did not return before the timeout, the
353+
// client will not receive an http.Response with the data written.
326354
test.clientErrVerifier.verify(t, err)
327355
}()
328356

@@ -333,7 +361,7 @@ func TestPerRequestWriteDeadlineWithTimeoutOccursAfterHandlerWrites(t *testing.T
333361
}
334362
}
335363

336-
func TestPerRequestWriteDeadlineWithTimeoutOccursAfterHandlerFlushes(t *testing.T) {
364+
func TestPerRequestWithClientReceivesContentFlushedBeforeDeadline(t *testing.T) {
337365
// This test documents the behavior of the per handler write
338366
// deadline using a standard net http server.
339367
//
@@ -372,7 +400,7 @@ func TestPerRequestWriteDeadlineWithTimeoutOccursAfterHandlerFlushes(t *testing.
372400
},
373401
{
374402
protoMajor: 2,
375-
waiter: &waitWithChannelClose{after: make(chan time.Time)},
403+
waiter: &waitForClose{after: make(chan time.Time)},
376404
clientErrVerifier: wantContains{"stream error: stream ID 1; INTERNAL_ERROR; received from peer"},
377405
handlerErrVerifier: wantError{err: os.ErrDeadlineExceeded},
378406
},
@@ -398,7 +426,7 @@ func TestPerRequestWriteDeadlineWithTimeoutOccursAfterHandlerFlushes(t *testing.
398426
t.Errorf("server: unexpected error from Write: %v", err)
399427
return
400428
}
401-
// b) flush the payload in the buffer
429+
// b) flush the payload that has been written to the buffer
402430
if err := flusher.FlushError(); err != nil {
403431
t.Errorf("server: unexpected error from FlushError: %v", err)
404432
return
@@ -415,7 +443,8 @@ func TestPerRequestWriteDeadlineWithTimeoutOccursAfterHandlerFlushes(t *testing.
415443
<-test.waiter.wait()
416444

417445
// e) write message 2 (the client should never
418-
// see the following message)
446+
// see the following message, and the test
447+
// verifies that in step g)
419448
if _, err := w.Write([]byte(msg2)); err != nil {
420449
t.Errorf("server: unexpected error from Write after timeout: %v", err)
421450
}
@@ -465,7 +494,7 @@ func TestPerRequestWriteDeadlineWithTimeoutOccursAfterHandlerFlushes(t *testing.
465494
}
466495
}
467496

468-
func TestPerRequestWriteDeadlineWithHandlerWritingIndefinitely(t *testing.T) {
497+
func TestPerRequestWithHandlerShouldAbortWriteAfterDeadline(t *testing.T) {
469498
// This test documents the behavior of the per handler write
470499
// deadline using a standard net http server.
471500
//
@@ -481,7 +510,7 @@ func TestPerRequestWriteDeadlineWithHandlerWritingIndefinitely(t *testing.T) {
481510
// expected to terminate as a result
482511
t.Parallel()
483512

484-
const deadline = 300 * time.Millisecond
513+
const deadline = 100 * time.Millisecond
485514
tests := []struct {
486515
protoMajor int
487516
clientErrVerifier verifier
@@ -557,7 +586,7 @@ func TestPerRequestWriteDeadlineWithHandlerWritingIndefinitely(t *testing.T) {
557586
}
558587
}
559588

560-
func TestPerRequestReadDeadlineWithClientNotWritingToRequestBody(t *testing.T) {
589+
func TestPerRequestWithBodyReadShouldTimeoutAfterDeadline(t *testing.T) {
561590
// This test documents the behavior of the per handler read
562591
// deadline using a standard net http server.
563592
//
@@ -574,22 +603,17 @@ func TestPerRequestReadDeadlineWithClientNotWritingToRequestBody(t *testing.T) {
574603
// an "i/o timeout" error
575604
t.Parallel()
576605

577-
const deadline = 500 * time.Millisecond
606+
const deadline = 100 * time.Millisecond
578607
tests := []struct {
579608
protoMajor int
580-
waiter waiter
581609
handlerErrVerifier verifier
582610
}{
583611
{
584-
protoMajor: 1,
585-
// write timeout is set to 500ms, a wait of 5s should
586-
// be enough to withstand flakes in CI.
587-
waiter: &waitWithDuration{after: 5 * time.Second},
612+
protoMajor: 1,
588613
handlerErrVerifier: wantError{err: os.ErrDeadlineExceeded},
589614
},
590615
{
591616
protoMajor: 2,
592-
waiter: &waitWithChannelClose{after: make(chan time.Time)},
593617
handlerErrVerifier: wantError{err: os.ErrDeadlineExceeded},
594618
},
595619
}
@@ -675,7 +699,7 @@ func TestPerRequestReadDeadlineWithClientNotWritingToRequestBody(t *testing.T) {
675699
}
676700
}
677701

678-
func TestPerRequestReadDeadlineWithTimeoutOccursWhileClientIsSendingContent(t *testing.T) {
702+
func TestPerRequestWithBodyReadShouldYieldPartialContentBeforeDeadline(t *testing.T) {
679703
// This test documents the behavior of the per request read
680704
// deadline using a standard net http server.
681705
//
@@ -696,7 +720,7 @@ func TestPerRequestReadDeadlineWithTimeoutOccursWhileClientIsSendingContent(t *t
696720
// the Body of the request
697721
t.Parallel()
698722

699-
const deadline = 300 * time.Millisecond
723+
const deadline = 100 * time.Millisecond
700724
tests := []struct {
701725
protoMajor int
702726
handlerErrVerifier verifier
@@ -808,7 +832,7 @@ func TestPerRequestReadDeadlineWithTimeoutOccursWhileClientIsSendingContent(t *t
808832
}
809833
}
810834

811-
func TestPerRequestReadDeadlineWithNoRequestBody(t *testing.T) {
835+
func TestPerRequestWithReadingEmptyBodyShouldNotYieldErrorAfterDeadline(t *testing.T) {
812836
// This test documents the behavior of the per request read
813837
// deadline using a standard net http server.
814838
//
@@ -897,7 +921,7 @@ func TestPerRequestReadDeadlineWithNoRequestBody(t *testing.T) {
897921

898922
}
899923

900-
func TestPerRequestReadWriteDeadlineWithHijack(t *testing.T) {
924+
func TestPerRequestWithHijackedConnectionShouldResetDeadline(t *testing.T) {
901925
// This test documents the behavior of the per handler read/write
902926
// deadline using a standard net http server.
903927
//
@@ -984,7 +1008,7 @@ func TestPerRequestReadWriteDeadlineWithHijack(t *testing.T) {
9841008
wantNoError{}.verify(t, err)
9851009
}
9861010

987-
func TestPerRequestWriteDeadlineWithConnectionReuse(t *testing.T) {
1011+
func TestPerRequestWithConnectionIsReused(t *testing.T) {
9881012
// This test documents the behavior of the per request write deadline
9891013
// using a standard net http server.
9901014
//
@@ -1020,7 +1044,7 @@ func TestPerRequestWriteDeadlineWithConnectionReuse(t *testing.T) {
10201044
},
10211045
{
10221046
protoMajor: 2, // http/2.0
1023-
waiter: &waitWithChannelClose{after: make(chan time.Time)},
1047+
waiter: &waitForClose{after: make(chan time.Time)},
10241048
clientErrVerifier: wantContains{"stream error: stream ID 1; INTERNAL_ERROR; received from peer"},
10251049
handlerErrVerifier: wantError{err: os.ErrDeadlineExceeded},
10261050
connReuseFn: shouldUseExistingConnection,
@@ -1125,7 +1149,7 @@ func TestPerRequestWriteDeadlineWithConnectionReuse(t *testing.T) {
11251149
}
11261150
}
11271151

1128-
func TestPerRequestWriteDeadlineWithSlowReader(t *testing.T) {
1152+
func TestPerRequestWithSlowReader(t *testing.T) {
11291153
// This test documents the behavior of the per handler write
11301154
// deadline using a standard net http server.
11311155
//
@@ -1339,29 +1363,43 @@ type waiter interface {
13391363
close()
13401364
}
13411365

1366+
// sleep based waiter implementation, the request handler sleeps for certain
1367+
// duration before it returns, we need to choose the sleep duration wisely
1368+
// in order to avoid flakes in CI.
13421369
type waitWithDuration struct {
13431370
after time.Duration
13441371
}
13451372

13461373
func (w waitWithDuration) wait() <-chan time.Time { return time.After(w.after) }
13471374
func (w waitWithDuration) close() {}
13481375

1349-
type waitWithChannelClose struct {
1376+
// channel based waiter implementation, the request handler waits on a channel
1377+
// to close, these are the steps:
1378+
// a) the client sends a request to the http/2.0 server
1379+
// a) the request handler sets per-request write timeout, and then
1380+
// b) the request handler blocks indefinitely on this channel to close
1381+
// c) write timeout elapses, and http/2.0 server asynchronously resets the stream
1382+
// d) the client receives a stream reset error immediately
1383+
// after the write timeout occurs.
1384+
// e) the client then closes this channel
1385+
// f) the request handler unblocks and terminates
1386+
//
1387+
// This waiter can be used for http/2.0 only, since the request handler executes
1388+
// on a separate goroutine than the tcp connection serving gorutine, this allows
1389+
// the connection serving loop to asynchronously reset the http2 stream. On the
1390+
// other hand, http/1x executes the request handler in the same goroutine as the
1391+
// connection serving goroutine, this forces the connection serving goroutine to
1392+
// wait for he handler to return.
1393+
// See https://github.com/golang/go/blob/b8ac61e6e64c92f23d8cf868a92a70d13e20a124/src/net/http/server.go#L3285
1394+
type waitForClose struct {
13501395
after chan time.Time
13511396
}
13521397

1353-
func (w waitWithChannelClose) wait() <-chan time.Time {
1354-
// for http/2, we do the following:
1355-
// a) let the handler block indefinitely
1356-
// b) this forces the write timeout to occur on the server side
1357-
// c) the http2 client receives a stream reset error immediately
1358-
// after the write timeout occurs.
1359-
// d) the client then closes the channel by calling close
1360-
// e) the handler unblocks and terminates
1398+
func (w waitForClose) wait() <-chan time.Time {
13611399
return w.after
13621400
}
13631401

1364-
func (w waitWithChannelClose) close() { close(w.after) }
1402+
func (w waitForClose) close() { close(w.after) }
13651403

13661404
type chanErr chan error
13671405

0 commit comments

Comments
 (0)