@@ -33,7 +33,12 @@ import (
33
33
"k8s.io/apimachinery/pkg/util/wait"
34
34
)
35
35
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 ) {
37
42
// This test documents the behavior of the per request write deadline
38
43
// using a standard net http server.
39
44
//
@@ -46,6 +51,17 @@ func TestPerRequestWriteDeadlineWithWriteShouldReturnErrorAfterTimeout(t *testin
46
51
// - d) client: expected to receive an error from the server
47
52
// - e) server: the Write method of the ResponseWriter object should
48
53
// 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
49
65
t .Parallel ()
50
66
51
67
const deadline = 100 * time .Millisecond
@@ -57,6 +73,10 @@ func TestPerRequestWriteDeadlineWithWriteShouldReturnErrorAfterTimeout(t *testin
57
73
}{
58
74
{
59
75
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
60
80
// write timeout is set to 100ms, a wait of 5s should be
61
81
// enough to withstand flakes in CI.
62
82
waiter : & waitWithDuration {after : 5 * time .Second },
@@ -65,7 +85,7 @@ func TestPerRequestWriteDeadlineWithWriteShouldReturnErrorAfterTimeout(t *testin
65
85
},
66
86
{
67
87
protoMajor : 2 , // http/2.0
68
- waiter : & waitWithChannelClose {after : make (chan time.Time )},
88
+ waiter : & waitForClose {after : make (chan time.Time )},
69
89
clientErrVerifier : wantContains {"stream error: stream ID 1; INTERNAL_ERROR; received from peer" },
70
90
handlerErrVerifier : wantError {err : os .ErrDeadlineExceeded },
71
91
},
@@ -90,8 +110,8 @@ func TestPerRequestWriteDeadlineWithWriteShouldReturnErrorAfterTimeout(t *testin
90
110
// b) wait until the write deadline exceeds
91
111
<- test .waiter .wait ()
92
112
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
95
115
func () {
96
116
now := time .Now ()
97
117
written := 0
@@ -122,6 +142,9 @@ func TestPerRequestWriteDeadlineWithWriteShouldReturnErrorAfterTimeout(t *testin
122
142
123
143
_ , err := client .Get (server .URL )
124
144
// 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.
125
148
test .clientErrVerifier .verify (t , err )
126
149
}()
127
150
@@ -132,7 +155,7 @@ func TestPerRequestWriteDeadlineWithWriteShouldReturnErrorAfterTimeout(t *testin
132
155
}
133
156
}
134
157
135
- func TestPerRequestWriteDeadlineWithTimeoutOccursBeforeHandlerWrites (t * testing.T ) {
158
+ func TestPerRequestWithFlushReturnsErrorAfterDeadline (t * testing.T ) {
136
159
// This test documents the behavior of the per request write deadline
137
160
// using a standard net http server.
138
161
//
@@ -166,7 +189,7 @@ func TestPerRequestWriteDeadlineWithTimeoutOccursBeforeHandlerWrites(t *testing.
166
189
},
167
190
{
168
191
protoMajor : 2 ,
169
- waiter : & waitWithChannelClose {after : make (chan time.Time )},
192
+ waiter : & waitForClose {after : make (chan time.Time )},
170
193
clientErrVerifier : wantContains {"stream error: stream ID 1; INTERNAL_ERROR; received from peer" },
171
194
handlerErrVerifier : wantError {err : os .ErrDeadlineExceeded },
172
195
},
@@ -198,6 +221,8 @@ func TestPerRequestWriteDeadlineWithTimeoutOccursBeforeHandlerWrites(t *testing.
198
221
199
222
// c) write a few bytes so there is something in the buffer to
200
223
// 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
201
226
if _ , err := w .Write ([]byte ("hello" )); err != nil {
202
227
t .Errorf ("server: unexpected error from Write after timeout: %v" , err )
203
228
return
@@ -235,7 +260,7 @@ func TestPerRequestWriteDeadlineWithTimeoutOccursBeforeHandlerWrites(t *testing.
235
260
}
236
261
}
237
262
238
- func TestPerRequestWriteDeadlineWithTimeoutOccursAfterHandlerWrites (t * testing.T ) {
263
+ func TestPerRequestWithClientNeverReceivesContentFlushedAfterDeadline (t * testing.T ) {
239
264
// This test documents the behavior of the per handler write
240
265
// deadline using a standard net http server.
241
266
//
@@ -269,7 +294,7 @@ func TestPerRequestWriteDeadlineWithTimeoutOccursAfterHandlerWrites(t *testing.T
269
294
},
270
295
{
271
296
protoMajor : 2 ,
272
- waiter : & waitWithChannelClose {after : make (chan time.Time )},
297
+ waiter : & waitForClose {after : make (chan time.Time )},
273
298
clientErrVerifier : wantContains {"stream error: stream ID 1; INTERNAL_ERROR; received from peer" },
274
299
handlerErrVerifier : wantError {err : os .ErrDeadlineExceeded },
275
300
},
@@ -323,6 +348,9 @@ func TestPerRequestWriteDeadlineWithTimeoutOccursAfterHandlerWrites(t *testing.T
323
348
324
349
_ , err := server .Client ().Get (server .URL )
325
350
// 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.
326
354
test .clientErrVerifier .verify (t , err )
327
355
}()
328
356
@@ -333,7 +361,7 @@ func TestPerRequestWriteDeadlineWithTimeoutOccursAfterHandlerWrites(t *testing.T
333
361
}
334
362
}
335
363
336
- func TestPerRequestWriteDeadlineWithTimeoutOccursAfterHandlerFlushes (t * testing.T ) {
364
+ func TestPerRequestWithClientReceivesContentFlushedBeforeDeadline (t * testing.T ) {
337
365
// This test documents the behavior of the per handler write
338
366
// deadline using a standard net http server.
339
367
//
@@ -372,7 +400,7 @@ func TestPerRequestWriteDeadlineWithTimeoutOccursAfterHandlerFlushes(t *testing.
372
400
},
373
401
{
374
402
protoMajor : 2 ,
375
- waiter : & waitWithChannelClose {after : make (chan time.Time )},
403
+ waiter : & waitForClose {after : make (chan time.Time )},
376
404
clientErrVerifier : wantContains {"stream error: stream ID 1; INTERNAL_ERROR; received from peer" },
377
405
handlerErrVerifier : wantError {err : os .ErrDeadlineExceeded },
378
406
},
@@ -398,7 +426,7 @@ func TestPerRequestWriteDeadlineWithTimeoutOccursAfterHandlerFlushes(t *testing.
398
426
t .Errorf ("server: unexpected error from Write: %v" , err )
399
427
return
400
428
}
401
- // b) flush the payload in the buffer
429
+ // b) flush the payload that has been written to the buffer
402
430
if err := flusher .FlushError (); err != nil {
403
431
t .Errorf ("server: unexpected error from FlushError: %v" , err )
404
432
return
@@ -415,7 +443,8 @@ func TestPerRequestWriteDeadlineWithTimeoutOccursAfterHandlerFlushes(t *testing.
415
443
<- test .waiter .wait ()
416
444
417
445
// 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)
419
448
if _ , err := w .Write ([]byte (msg2 )); err != nil {
420
449
t .Errorf ("server: unexpected error from Write after timeout: %v" , err )
421
450
}
@@ -465,7 +494,7 @@ func TestPerRequestWriteDeadlineWithTimeoutOccursAfterHandlerFlushes(t *testing.
465
494
}
466
495
}
467
496
468
- func TestPerRequestWriteDeadlineWithHandlerWritingIndefinitely (t * testing.T ) {
497
+ func TestPerRequestWithHandlerShouldAbortWriteAfterDeadline (t * testing.T ) {
469
498
// This test documents the behavior of the per handler write
470
499
// deadline using a standard net http server.
471
500
//
@@ -481,7 +510,7 @@ func TestPerRequestWriteDeadlineWithHandlerWritingIndefinitely(t *testing.T) {
481
510
// expected to terminate as a result
482
511
t .Parallel ()
483
512
484
- const deadline = 300 * time .Millisecond
513
+ const deadline = 100 * time .Millisecond
485
514
tests := []struct {
486
515
protoMajor int
487
516
clientErrVerifier verifier
@@ -557,7 +586,7 @@ func TestPerRequestWriteDeadlineWithHandlerWritingIndefinitely(t *testing.T) {
557
586
}
558
587
}
559
588
560
- func TestPerRequestReadDeadlineWithClientNotWritingToRequestBody (t * testing.T ) {
589
+ func TestPerRequestWithBodyReadShouldTimeoutAfterDeadline (t * testing.T ) {
561
590
// This test documents the behavior of the per handler read
562
591
// deadline using a standard net http server.
563
592
//
@@ -574,22 +603,17 @@ func TestPerRequestReadDeadlineWithClientNotWritingToRequestBody(t *testing.T) {
574
603
// an "i/o timeout" error
575
604
t .Parallel ()
576
605
577
- const deadline = 500 * time .Millisecond
606
+ const deadline = 100 * time .Millisecond
578
607
tests := []struct {
579
608
protoMajor int
580
- waiter waiter
581
609
handlerErrVerifier verifier
582
610
}{
583
611
{
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 ,
588
613
handlerErrVerifier : wantError {err : os .ErrDeadlineExceeded },
589
614
},
590
615
{
591
616
protoMajor : 2 ,
592
- waiter : & waitWithChannelClose {after : make (chan time.Time )},
593
617
handlerErrVerifier : wantError {err : os .ErrDeadlineExceeded },
594
618
},
595
619
}
@@ -675,7 +699,7 @@ func TestPerRequestReadDeadlineWithClientNotWritingToRequestBody(t *testing.T) {
675
699
}
676
700
}
677
701
678
- func TestPerRequestReadDeadlineWithTimeoutOccursWhileClientIsSendingContent (t * testing.T ) {
702
+ func TestPerRequestWithBodyReadShouldYieldPartialContentBeforeDeadline (t * testing.T ) {
679
703
// This test documents the behavior of the per request read
680
704
// deadline using a standard net http server.
681
705
//
@@ -696,7 +720,7 @@ func TestPerRequestReadDeadlineWithTimeoutOccursWhileClientIsSendingContent(t *t
696
720
// the Body of the request
697
721
t .Parallel ()
698
722
699
- const deadline = 300 * time .Millisecond
723
+ const deadline = 100 * time .Millisecond
700
724
tests := []struct {
701
725
protoMajor int
702
726
handlerErrVerifier verifier
@@ -808,7 +832,7 @@ func TestPerRequestReadDeadlineWithTimeoutOccursWhileClientIsSendingContent(t *t
808
832
}
809
833
}
810
834
811
- func TestPerRequestReadDeadlineWithNoRequestBody (t * testing.T ) {
835
+ func TestPerRequestWithReadingEmptyBodyShouldNotYieldErrorAfterDeadline (t * testing.T ) {
812
836
// This test documents the behavior of the per request read
813
837
// deadline using a standard net http server.
814
838
//
@@ -897,7 +921,7 @@ func TestPerRequestReadDeadlineWithNoRequestBody(t *testing.T) {
897
921
898
922
}
899
923
900
- func TestPerRequestReadWriteDeadlineWithHijack (t * testing.T ) {
924
+ func TestPerRequestWithHijackedConnectionShouldResetDeadline (t * testing.T ) {
901
925
// This test documents the behavior of the per handler read/write
902
926
// deadline using a standard net http server.
903
927
//
@@ -984,7 +1008,7 @@ func TestPerRequestReadWriteDeadlineWithHijack(t *testing.T) {
984
1008
wantNoError {}.verify (t , err )
985
1009
}
986
1010
987
- func TestPerRequestWriteDeadlineWithConnectionReuse (t * testing.T ) {
1011
+ func TestPerRequestWithConnectionIsReused (t * testing.T ) {
988
1012
// This test documents the behavior of the per request write deadline
989
1013
// using a standard net http server.
990
1014
//
@@ -1020,7 +1044,7 @@ func TestPerRequestWriteDeadlineWithConnectionReuse(t *testing.T) {
1020
1044
},
1021
1045
{
1022
1046
protoMajor : 2 , // http/2.0
1023
- waiter : & waitWithChannelClose {after : make (chan time.Time )},
1047
+ waiter : & waitForClose {after : make (chan time.Time )},
1024
1048
clientErrVerifier : wantContains {"stream error: stream ID 1; INTERNAL_ERROR; received from peer" },
1025
1049
handlerErrVerifier : wantError {err : os .ErrDeadlineExceeded },
1026
1050
connReuseFn : shouldUseExistingConnection ,
@@ -1125,7 +1149,7 @@ func TestPerRequestWriteDeadlineWithConnectionReuse(t *testing.T) {
1125
1149
}
1126
1150
}
1127
1151
1128
- func TestPerRequestWriteDeadlineWithSlowReader (t * testing.T ) {
1152
+ func TestPerRequestWithSlowReader (t * testing.T ) {
1129
1153
// This test documents the behavior of the per handler write
1130
1154
// deadline using a standard net http server.
1131
1155
//
@@ -1339,29 +1363,43 @@ type waiter interface {
1339
1363
close ()
1340
1364
}
1341
1365
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.
1342
1369
type waitWithDuration struct {
1343
1370
after time.Duration
1344
1371
}
1345
1372
1346
1373
func (w waitWithDuration ) wait () <- chan time.Time { return time .After (w .after ) }
1347
1374
func (w waitWithDuration ) close () {}
1348
1375
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 {
1350
1395
after chan time.Time
1351
1396
}
1352
1397
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 {
1361
1399
return w .after
1362
1400
}
1363
1401
1364
- func (w waitWithChannelClose ) close () { close (w .after ) }
1402
+ func (w waitForClose ) close () { close (w .after ) }
1365
1403
1366
1404
type chanErr chan error
1367
1405
0 commit comments