Skip to content

Commit d51fd3b

Browse files
authored
Merge pull request #4127 from square/concurrent-http2-flow-control
Confirm concurrent HTTP/2 requests with empty flow-control window.
2 parents 2f1be26 + f5905f4 commit d51fd3b

File tree

2 files changed

+58
-19
lines changed

2 files changed

+58
-19
lines changed

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

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -313,15 +313,7 @@ private static OkHttpClient buildHttp2Client() {
313313
.build());
314314
Response response1 = call1.execute();
315315

316-
// Wait until the server has completely filled the stream and connection flow-control windows.
317-
int expectedFrameCount = Http2Connection.OKHTTP_CLIENT_WINDOW_SIZE / 16384;
318-
int dataFrameCount = 0;
319-
while (dataFrameCount < expectedFrameCount) {
320-
String log = http2Handler.take();
321-
if (log.equals("FINE: << 0x00000003 16384 DATA ")) {
322-
dataFrameCount++;
323-
}
324-
}
316+
waitForDataFrames(Http2Connection.OKHTTP_CLIENT_WINDOW_SIZE);
325317

326318
// Cancel the call and discard what we've buffered for the response body. This should free up
327319
// the connection flow-control window so new requests can proceed.
@@ -336,6 +328,18 @@ private static OkHttpClient buildHttp2Client() {
336328
assertEquals("abc", response2.body().string());
337329
}
338330

331+
/** Wait for the client to receive {@code dataLength} DATA frames. */
332+
private void waitForDataFrames(int dataLength) throws Exception {
333+
int expectedFrameCount = dataLength / 16384;
334+
int dataFrameCount = 0;
335+
while (dataFrameCount < expectedFrameCount) {
336+
String log = http2Handler.take();
337+
if (log.equals("FINE: << 0x00000003 16384 DATA ")) {
338+
dataFrameCount++;
339+
}
340+
}
341+
}
342+
339343
@Test public void connectionWindowUpdateOnClose() throws Exception {
340344
server.enqueue(new MockResponse()
341345
.setBody(new Buffer().write(new byte[Http2Connection.OKHTTP_CLIENT_WINDOW_SIZE + 1])));
@@ -347,15 +351,7 @@ private static OkHttpClient buildHttp2Client() {
347351
.build());
348352
Response response1 = call1.execute();
349353

350-
// Wait until the server has completely filled the stream and connection flow-control windows.
351-
int expectedFrameCount = Http2Connection.OKHTTP_CLIENT_WINDOW_SIZE / 16384;
352-
int dataFrameCount = 0;
353-
while (dataFrameCount < expectedFrameCount) {
354-
String log = http2Handler.take();
355-
if (log.equals("FINE: << 0x00000003 16384 DATA ")) {
356-
dataFrameCount++;
357-
}
358-
}
354+
waitForDataFrames(Http2Connection.OKHTTP_CLIENT_WINDOW_SIZE);
359355

360356
// Cancel the call and close the response body. This should discard the buffered data and update
361357
// the connnection flow-control window.
@@ -369,6 +365,38 @@ private static OkHttpClient buildHttp2Client() {
369365
assertEquals("abc", response2.body().string());
370366
}
371367

368+
@Test public void concurrentRequestWithEmptyFlowControlWindow() throws Exception {
369+
server.enqueue(new MockResponse()
370+
.setBody(new Buffer().write(new byte[Http2Connection.OKHTTP_CLIENT_WINDOW_SIZE])));
371+
server.enqueue(new MockResponse()
372+
.setBody("abc"));
373+
374+
Call call1 = client.newCall(new Request.Builder()
375+
.url(server.url("/"))
376+
.build());
377+
Response response1 = call1.execute();
378+
379+
waitForDataFrames(Http2Connection.OKHTTP_CLIENT_WINDOW_SIZE);
380+
381+
assertEquals(Http2Connection.OKHTTP_CLIENT_WINDOW_SIZE, response1.body().contentLength());
382+
int read = response1.body().source().read(new byte[8192]);
383+
assertEquals(8192, read);
384+
385+
// Make a second call that should transmit the response headers. The response body won't be
386+
// transmitted until the flow-control window is updated from the first request.
387+
Call call2 = client.newCall(new Request.Builder()
388+
.url(server.url("/"))
389+
.build());
390+
Response response2 = call2.execute();
391+
assertEquals(200, response2.code());
392+
393+
// Close the response body. This should discard the buffered data and update the connection
394+
// flow-control window.
395+
response1.close();
396+
397+
assertEquals("abc", response2.body().string());
398+
}
399+
372400
/** https://github.com/square/okhttp/issues/373 */
373401
@Test @Ignore public void synchronousRequest() throws Exception {
374402
server.enqueue(new MockResponse().setBody("A"));

okhttp/src/main/java/okhttp3/internal/http2/Http2Stream.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,16 +173,27 @@ public void sendResponseHeaders(List<Header> responseHeaders, boolean out) throw
173173
throw new NullPointerException("responseHeaders == null");
174174
}
175175
boolean outFinished = false;
176+
boolean flushHeaders = false;
176177
synchronized (this) {
177178
this.hasResponseHeaders = true;
178179
if (!out) {
179180
this.sink.finished = true;
181+
flushHeaders = true;
180182
outFinished = true;
181183
}
182184
}
185+
186+
// Only DATA frames are subject to flow-control. Transmit the HEADER frame if the connection
187+
// flow-control window is fully depleted.
188+
if (!flushHeaders) {
189+
synchronized (connection) {
190+
flushHeaders = connection.bytesLeftInWriteWindow == 0L;
191+
}
192+
}
193+
183194
connection.writeSynReply(id, outFinished, responseHeaders);
184195

185-
if (outFinished) {
196+
if (flushHeaders) {
186197
connection.flush();
187198
}
188199
}

0 commit comments

Comments
 (0)