Skip to content

Commit 6a9a64c

Browse files
committed
Degrade connections after a timeout (3.14.x branch)
This is a manual cherry-pick of 09da07c See also the degraded connections proposal. #3146 (comment)
1 parent ddc44ec commit 6a9a64c

File tree

7 files changed

+240
-53
lines changed

7 files changed

+240
-53
lines changed

mockwebserver/src/main/java/okhttp3/mockwebserver/MockWebServer.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,7 @@ public void processConnection() throws Exception {
507507
}
508508

509509
if (socketPolicy == STALL_SOCKET_AT_START) {
510+
dispatchBookkeepingRequest(sequenceNumber, socket);
510511
return; // Ignore the socket until the server is shut down!
511512
}
512513

okhttp-tests/src/test/java/okhttp3/internal/http2/Http2ConnectionTest.java

Lines changed: 56 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@
4949
import static okhttp3.internal.Internal.initializeInstanceForTests;
5050
import static okhttp3.internal.Util.EMPTY_BYTE_ARRAY;
5151
import static okhttp3.internal.Util.EMPTY_HEADERS;
52+
import static okhttp3.internal.http2.Http2Connection.AWAIT_PING;
53+
import static okhttp3.internal.http2.Http2Connection.DEGRADED_PING;
54+
import static okhttp3.internal.http2.Http2Connection.DEGRADED_PONG_TIMEOUT_NS;
5255
import static okhttp3.internal.http2.Http2Connection.Listener.REFUSE_INCOMING_STREAMS;
5356
import static okhttp3.internal.http2.Settings.DEFAULT_INITIAL_WINDOW_SIZE;
5457
import static okhttp3.internal.http2.Settings.ENABLE_PUSH;
@@ -173,7 +176,7 @@ public final class Http2ConnectionTest {
173176
peer.acceptFrame(); // ACK
174177
peer.sendFrame().windowUpdate(0, 10); // Increase the connection window size.
175178
peer.acceptFrame(); // PING
176-
peer.sendFrame().ping(true, 1, 0);
179+
peer.sendFrame().ping(true, AWAIT_PING, 0);
177180
peer.acceptFrame(); // HEADERS STREAM 3
178181
peer.sendFrame().windowUpdate(3, 5);
179182
peer.acceptFrame(); // DATA STREAM 3 "abcde"
@@ -244,7 +247,7 @@ public final class Http2ConnectionTest {
244247
peer.acceptFrame(); // SYN_STREAM 5
245248
peer.sendFrame().goAway(3, ErrorCode.PROTOCOL_ERROR, EMPTY_BYTE_ARRAY);
246249
peer.acceptFrame(); // PING
247-
peer.sendFrame().ping(true, 1, 0);
250+
peer.sendFrame().ping(true, AWAIT_PING, 0);
248251
peer.acceptFrame(); // DATA STREAM 3
249252
peer.play();
250253

@@ -527,7 +530,7 @@ public final class Http2ConnectionTest {
527530
peer.sendFrame().headers(false, 3, headerEntries("a", "android"));
528531
peer.sendFrame().data(true, 3, new Buffer().writeUtf8("robot"), 5);
529532
peer.acceptFrame(); // PING
530-
peer.sendFrame().ping(true, 1, 0); // PING
533+
peer.sendFrame().ping(true, AWAIT_PING, 0); // PING
531534
peer.play();
532535

533536
// play it back
@@ -559,7 +562,7 @@ public final class Http2ConnectionTest {
559562
peer.acceptFrame(); // SYN_STREAM
560563
peer.acceptFrame(); // PING
561564
peer.sendFrame().headers(true, 3, headerEntries("headers", "bam"));
562-
peer.sendFrame().ping(true, 1, 0); // PONG
565+
peer.sendFrame().ping(true, AWAIT_PING, 0); // PONG
563566
peer.play();
564567

565568
// play it back
@@ -587,7 +590,7 @@ public final class Http2ConnectionTest {
587590
peer.sendFrame().headers(false, 3, headerEntries("headers", "bam"));
588591
peer.acceptFrame(); // PING
589592
peer.sendFrame().headers(true, 3, headerEntries("trailers", "boom"));
590-
peer.sendFrame().ping(true, 1, 0); // PONG
593+
peer.sendFrame().ping(true, AWAIT_PING, 0); // PONG
591594
peer.play();
592595

593596
// play it back
@@ -652,7 +655,7 @@ public final class Http2ConnectionTest {
652655
peer.sendFrame().data(false, 3, new Buffer().writeUtf8("robot"), 5);
653656
peer.sendFrame().headers(true, 3, headerEntries("trailers", "boom"));
654657
peer.acceptFrame(); // PING
655-
peer.sendFrame().ping(true, 1, 0); // PONG
658+
peer.sendFrame().ping(true, AWAIT_PING, 0); // PONG
656659
peer.play();
657660

658661
// play it back
@@ -673,7 +676,7 @@ public final class Http2ConnectionTest {
673676
peer.acceptFrame(); // SYN_STREAM
674677
peer.sendFrame().rstStream(3, ErrorCode.PROTOCOL_ERROR);
675678
peer.acceptFrame(); // PING
676-
peer.sendFrame().ping(true, 1, 0); // PONG
679+
peer.sendFrame().ping(true, AWAIT_PING, 0); // PONG
677680
peer.play();
678681

679682
// play it back
@@ -692,7 +695,7 @@ public final class Http2ConnectionTest {
692695
peer.sendFrame().settings(new Settings());
693696
peer.acceptFrame(); // ACK
694697
peer.acceptFrame(); // PING
695-
peer.sendFrame().ping(true, 1, 0);
698+
peer.sendFrame().ping(true, AWAIT_PING, 0);
696699
peer.play();
697700

698701
// Play it back.
@@ -716,7 +719,7 @@ public final class Http2ConnectionTest {
716719
peer.sendFrame().headers(false, 3, headerEntries("headers", "bam"));
717720
peer.sendFrame().data(true, 3, new Buffer().writeUtf8("robot"), 5);
718721
peer.acceptFrame(); // PING
719-
peer.sendFrame().ping(true, 1, 0); // PONG
722+
peer.sendFrame().ping(true, AWAIT_PING, 0); // PONG
720723
peer.play();
721724

722725
// play it back
@@ -747,7 +750,7 @@ public final class Http2ConnectionTest {
747750
peer.acceptFrame(); // HEADERS
748751
peer.sendFrame().headers(true, 3, headerEntries("a", "android"));
749752
peer.acceptFrame(); // PING
750-
peer.sendFrame().ping(true, 1, 0); // PING
753+
peer.sendFrame().ping(true, AWAIT_PING, 0); // PING
751754
peer.play();
752755

753756
// play it back
@@ -781,7 +784,7 @@ public final class Http2ConnectionTest {
781784
peer.acceptFrame(); // SYN_STREAM
782785
peer.acceptFrame(); // PING
783786
peer.sendFrame().headers(true, 3, headerEntries("a", "android"));
784-
peer.sendFrame().ping(true, 1, 0);
787+
peer.sendFrame().ping(true, AWAIT_PING, 0);
785788
peer.play();
786789

787790
// play it back
@@ -822,7 +825,7 @@ public final class Http2ConnectionTest {
822825
peer.sendFrame().settings(new Settings());
823826
peer.acceptFrame(); // ACK
824827
peer.acceptFrame(); // PING
825-
peer.sendFrame().ping(true, 1, 5);
828+
peer.sendFrame().ping(true, AWAIT_PING, 5);
826829
peer.play();
827830

828831
// play it back
@@ -837,10 +840,8 @@ public final class Http2ConnectionTest {
837840
InFrame pingFrame = peer.takeFrame();
838841
assertThat(pingFrame.type).isEqualTo(Http2.TYPE_PING);
839842
assertThat(pingFrame.streamId).isEqualTo(0);
840-
// OkOk
841-
assertThat(pingFrame.payload1).isEqualTo(0x4f4b6f6b);
842-
// donut
843-
assertThat(pingFrame.payload2).isEqualTo(0xf09f8da9);
843+
assertThat(pingFrame.payload1).isEqualTo(AWAIT_PING);
844+
assertThat(pingFrame.payload2).isEqualTo(0x4f4b6f6b); // OKok.
844845
assertThat(pingFrame.ack).isFalse();
845846
}
846847

@@ -850,7 +851,7 @@ public final class Http2ConnectionTest {
850851
peer.acceptFrame(); // ACK
851852
peer.sendFrame().ping(false, 2, 0);
852853
peer.acceptFrame(); // PING
853-
peer.sendFrame().ping(true, 3, 0); // This ping will not be returned.
854+
peer.sendFrame().ping(true, 99, 0); // This pong is silently ignored.
854855
peer.sendFrame().ping(false, 4, 0);
855856
peer.acceptFrame(); // PING
856857
peer.play();
@@ -1003,7 +1004,7 @@ public final class Http2ConnectionTest {
10031004
peer.acceptFrame(); // SYN_STREAM
10041005
peer.sendFrame().rstStream(3, ErrorCode.CANCEL);
10051006
peer.acceptFrame(); // PING
1006-
peer.sendFrame().ping(true, 1, 0);
1007+
peer.sendFrame().ping(true, AWAIT_PING, 0);
10071008
peer.play();
10081009

10091010
// play it back
@@ -1132,7 +1133,7 @@ public final class Http2ConnectionTest {
11321133
peer.sendFrame().headers(false, 3, headerEntries("b", "banana"));
11331134
peer.sendFrame().data(true, 3, new Buffer().writeUtf8("square"), 6);
11341135
peer.acceptFrame(); // PING
1135-
peer.sendFrame().ping(true, 1, 0);
1136+
peer.sendFrame().ping(true, AWAIT_PING, 0);
11361137
peer.play();
11371138

11381139
// play it back
@@ -1158,7 +1159,7 @@ public final class Http2ConnectionTest {
11581159
peer.sendFrame().headers(false, 3, headerEntries("a", "android"));
11591160
peer.acceptFrame(); // PING
11601161
peer.sendFrame().headers(false, 3, headerEntries("b", "banana"));
1161-
peer.sendFrame().ping(true, 1, 0);
1162+
peer.sendFrame().ping(true, AWAIT_PING, 0);
11621163
peer.play();
11631164

11641165
// play it back
@@ -1270,7 +1271,7 @@ public final class Http2ConnectionTest {
12701271
peer.acceptFrame(); // SYN_STREAM 3
12711272
peer.acceptFrame(); // PING.
12721273
peer.sendFrame().goAway(3, ErrorCode.PROTOCOL_ERROR, Util.EMPTY_BYTE_ARRAY);
1273-
peer.sendFrame().ping(true, 1, 0);
1274+
peer.sendFrame().ping(true, AWAIT_PING, 0);
12741275
peer.acceptFrame(); // DATA STREAM 1
12751276
peer.play();
12761277

@@ -1321,18 +1322,18 @@ public final class Http2ConnectionTest {
13211322
peer.acceptFrame(); // GOAWAY
13221323
peer.acceptFrame(); // PING
13231324
peer.sendFrame().headers(false, 2, headerEntries("b", "b")); // Should be ignored!
1324-
peer.sendFrame().ping(true, 1, 0);
1325+
peer.sendFrame().ping(true, AWAIT_PING, 0);
13251326
peer.play();
13261327

13271328
// play it back
13281329
Http2Connection connection = connect(peer);
13291330
connection.newStream(headerEntries("a", "android"), false);
13301331
synchronized (connection) {
1331-
if (connection.shutdown) {
1332+
if (!connection.isHealthy(System.nanoTime())) {
13321333
throw new ConnectionShutdownException();
13331334
}
13341335
}
1335-
connection.writePing(false, 0x01, 0x02);
1336+
connection.writePing();
13361337
connection.shutdown(ErrorCode.PROTOCOL_ERROR);
13371338
assertThat(connection.openStreamCount()).isEqualTo(1);
13381339
connection.awaitPong(); // Prevent the peer from exiting prematurely.
@@ -1422,23 +1423,34 @@ public final class Http2ConnectionTest {
14221423
assertThat(peer.takeFrame().type).isEqualTo(Http2.TYPE_RST_STREAM);
14231424
}
14241425

1426+
/**
1427+
* Confirm that the client times out if the server stalls after 3 bytes. After the timeout the
1428+
* connection is still considered healthy while we await the degraded pong. When that doesn't
1429+
* arrive the connection goes unhealthy.
1430+
*/
14251431
@Test public void readTimesOut() throws Exception {
14261432
// write the mocking script
14271433
peer.sendFrame().settings(new Settings());
14281434
peer.acceptFrame(); // ACK
14291435
peer.acceptFrame(); // SYN_STREAM
14301436
peer.sendFrame().headers(false, 3, headerEntries("a", "android"));
1437+
peer.sendFrame().data(false, 3, new Buffer().writeUtf8("abc"), 3);
14311438
peer.acceptFrame(); // RST_STREAM
1439+
peer.acceptFrame(); // DEGRADED PING
1440+
peer.acceptFrame(); // AWAIT PING
1441+
peer.sendFrame().ping(true, DEGRADED_PING, 1); // DEGRADED PONG
1442+
peer.sendFrame().ping(true, AWAIT_PING, 0); // AWAIT PONG
14321443
peer.play();
14331444

14341445
// play it back
14351446
Http2Connection connection = connect(peer);
14361447
Http2Stream stream = connection.newStream(headerEntries("b", "banana"), false);
14371448
stream.readTimeout().timeout(500, TimeUnit.MILLISECONDS);
1438-
Source source = stream.getSource();
1449+
BufferedSource source = Okio.buffer(stream.getSource());
1450+
source.require(3);
14391451
long startNanos = System.nanoTime();
14401452
try {
1441-
source.read(new Buffer(), 1);
1453+
source.require(4);
14421454
fail();
14431455
} catch (InterruptedIOException expected) {
14441456
}
@@ -1448,9 +1460,22 @@ public final class Http2ConnectionTest {
14481460
assertThat((double) TimeUnit.NANOSECONDS.toMillis(elapsedNanos)).isCloseTo(500d, offset(200d));
14491461
assertThat(connection.openStreamCount()).isEqualTo(0);
14501462

1463+
// When the timeout is sent the connection doesn't immediately go unhealthy.
1464+
assertThat(connection.isHealthy(System.nanoTime())).isTrue();
1465+
1466+
// But if the ping doesn't arrive, the connection goes unhealthy.
1467+
Thread.sleep(TimeUnit.NANOSECONDS.toMillis(DEGRADED_PONG_TIMEOUT_NS));
1468+
assertThat(connection.isHealthy(System.nanoTime())).isFalse();
1469+
1470+
// When a pong does arrive, the connection becomes healthy again.
1471+
connection.writePingAndAwaitPong();
1472+
assertThat(connection.isHealthy(System.nanoTime())).isTrue();
1473+
14511474
// verify the peer received what was expected
14521475
assertThat(peer.takeFrame().type).isEqualTo(Http2.TYPE_HEADERS);
14531476
assertThat(peer.takeFrame().type).isEqualTo(Http2.TYPE_RST_STREAM);
1477+
assertThat(peer.takeFrame().type).isEqualTo(Http2.TYPE_PING);
1478+
assertThat(peer.takeFrame().type).isEqualTo(Http2.TYPE_PING);
14541479
}
14551480

14561481
@Test public void writeTimesOutAwaitingStreamWindow() throws Exception {
@@ -1461,7 +1486,7 @@ public final class Http2ConnectionTest {
14611486
peer.sendFrame().settings(peerSettings);
14621487
peer.acceptFrame(); // ACK SETTINGS
14631488
peer.acceptFrame(); // PING
1464-
peer.sendFrame().ping(true, 1, 0);
1489+
peer.sendFrame().ping(true, AWAIT_PING, 0);
14651490
peer.acceptFrame(); // SYN_STREAM
14661491
peer.sendFrame().headers(false, 3, headerEntries("a", "android"));
14671492
peer.acceptFrame(); // DATA
@@ -1504,11 +1529,11 @@ public final class Http2ConnectionTest {
15041529
peer.sendFrame().settings(peerSettings);
15051530
peer.acceptFrame(); // ACK SETTINGS
15061531
peer.acceptFrame(); // PING
1507-
peer.sendFrame().ping(true, 1, 0);
1532+
peer.sendFrame().ping(true, AWAIT_PING, 0);
15081533
peer.acceptFrame(); // SYN_STREAM
15091534
peer.sendFrame().headers(false, 3, headerEntries("a", "android"));
15101535
peer.acceptFrame(); // PING
1511-
peer.sendFrame().ping(true, 3, 0);
1536+
peer.sendFrame().ping(true, AWAIT_PING, 0);
15121537
peer.acceptFrame(); // DATA
15131538
peer.acceptFrame(); // RST_STREAM
15141539
peer.play();
@@ -1576,7 +1601,7 @@ public final class Http2ConnectionTest {
15761601
peer.acceptFrame(); // PING
15771602
peer.sendFrame().headers(false, 3, headerEntries("a", "android"));
15781603
peer.sendFrame().headers(false, 3, headerEntries("c", "c3po"));
1579-
peer.sendFrame().ping(true, 1, 0);
1604+
peer.sendFrame().ping(true, AWAIT_PING, 0);
15801605
peer.play();
15811606

15821607
// play it back
@@ -1601,7 +1626,7 @@ public final class Http2ConnectionTest {
16011626
peer.sendFrame().headers(false, 3, headerEntries("a", "android"));
16021627
peer.acceptFrame(); // PING
16031628
peer.sendFrame().headers(true, 3, headerEntries("c", "cola"));
1604-
peer.sendFrame().ping(true, 1, 0); // PONG
1629+
peer.sendFrame().ping(true, AWAIT_PING, 0); // PONG
16051630
peer.play();
16061631

16071632
// play it back

0 commit comments

Comments
 (0)