Skip to content

Commit 32ac3e7

Browse files
author
Alexey Bakhtin
committed
8342075: HttpClient: improve HTTP/2 flow control checks
Reviewed-by: andrew Backport-of: b0ac633b2d0076d64b463b2a6ce19abf6b12c50f
1 parent 8ed020e commit 32ac3e7

File tree

13 files changed

+1022
-37
lines changed

13 files changed

+1022
-37
lines changed

src/java.net.http/share/classes/jdk/internal/net/http/Http2ClientImpl.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
import jdk.internal.net.http.common.MinimalFuture;
4141
import jdk.internal.net.http.common.Utils;
4242
import jdk.internal.net.http.frame.SettingsFrame;
43+
44+
import static jdk.internal.net.http.frame.SettingsFrame.INITIAL_CONNECTION_WINDOW_SIZE;
4345
import static jdk.internal.net.http.frame.SettingsFrame.INITIAL_WINDOW_SIZE;
4446
import static jdk.internal.net.http.frame.SettingsFrame.ENABLE_PUSH;
4547
import static jdk.internal.net.http.frame.SettingsFrame.HEADER_TABLE_SIZE;
@@ -289,9 +291,13 @@ int getConnectionWindowSize(SettingsFrame clientSettings) {
289291
// and the connection window size.
290292
int defaultValue = Math.max(streamWindow, K*K*32);
291293

294+
// The min value is the max between the streamWindow and
295+
// the initial connection window size
296+
int minValue = Math.max(INITIAL_CONNECTION_WINDOW_SIZE, streamWindow);
297+
292298
return getParameter(
293299
"jdk.httpclient.connectionWindowSize",
294-
streamWindow, Integer.MAX_VALUE, defaultValue);
300+
minValue, Integer.MAX_VALUE, defaultValue);
295301
}
296302

297303
/**

src/java.net.http/share/classes/jdk/internal/net/http/Http2Connection.java

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,6 +1068,34 @@ private String checkMaxOrphanedHeadersExceeded(HeaderFrame hf) {
10681068
return null;
10691069
}
10701070

1071+
// This method is called when a DataFrame that was added
1072+
// to a Stream::inputQ is later dropped from the queue
1073+
// without being consumed.
1074+
//
1075+
// Before adding a frame to the queue, the Stream calls
1076+
// connection.windowUpdater.canBufferUnprocessedBytes(), which
1077+
// increases the count of unprocessed bytes in the connection.
1078+
// After consuming the frame, it calls connection.windowUpdater::processed,
1079+
// which decrements the count of unprocessed bytes, and possibly
1080+
// sends a window update to the peer.
1081+
//
1082+
// This method is called when connection.windowUpdater::processed
1083+
// will not be called, which can happen when consuming the frame
1084+
// fails, or when an empty DataFrame terminates the stream,
1085+
// or when the stream is cancelled while data is still
1086+
// sitting in its inputQ. In the later case, it is called for
1087+
// each frame that is dropped from the queue.
1088+
final void releaseUnconsumed(DataFrame df) {
1089+
windowUpdater.released(df.payloadLength());
1090+
dropDataFrame(df);
1091+
}
1092+
1093+
// This method can be called directly when a DataFrame is dropped
1094+
// before/without having been added to any Stream::inputQ.
1095+
// In that case, the number of unprocessed bytes hasn't been incremented
1096+
// by the stream, and does not need to be decremented.
1097+
// Otherwise, if the frame is dropped after having been added to the
1098+
// inputQ, releaseUnconsumed above should be called.
10711099
final void dropDataFrame(DataFrame df) {
10721100
if (isMarked(closedState, SHUTDOWN_REQUESTED)) return;
10731101
if (debug.on()) {
@@ -1453,11 +1481,12 @@ private void sendConnectionPreface() throws IOException {
14531481
// Note that the default initial window size, not to be confused
14541482
// with the initial window size, is defined by RFC 7540 as
14551483
// 64K -1.
1456-
final int len = windowUpdater.initialWindowSize - DEFAULT_INITIAL_WINDOW_SIZE;
1457-
if (len != 0) {
1484+
final int len = windowUpdater.initialWindowSize - INITIAL_CONNECTION_WINDOW_SIZE;
1485+
assert len >= 0;
1486+
if (len > 0) {
14581487
if (Log.channel()) {
14591488
Log.logChannel("Sending initial connection window update frame: {0} ({1} - {2})",
1460-
len, windowUpdater.initialWindowSize, DEFAULT_INITIAL_WINDOW_SIZE);
1489+
len, windowUpdater.initialWindowSize, INITIAL_CONNECTION_WINDOW_SIZE);
14611490
}
14621491
windowUpdater.sendWindowUpdate(len);
14631492
}
@@ -1911,6 +1940,19 @@ public ConnectionWindowUpdateSender(Http2Connection connection,
19111940
int getStreamId() {
19121941
return 0;
19131942
}
1943+
1944+
@Override
1945+
protected boolean windowSizeExceeded(long received) {
1946+
if (connection.isOpen()) {
1947+
try {
1948+
connection.protocolError(ErrorFrame.FLOW_CONTROL_ERROR,
1949+
"connection window exceeded");
1950+
} catch (IOException io) {
1951+
connection.shutdown(io);
1952+
}
1953+
}
1954+
return true;
1955+
}
19141956
}
19151957

19161958
/**

src/java.net.http/share/classes/jdk/internal/net/http/Stream.java

Lines changed: 51 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -160,14 +160,13 @@ class Stream<T> extends ExchangeImpl<T> {
160160
// send lock: prevent sending DataFrames after reset occurred.
161161
private final Lock sendLock = new ReentrantLock();
162162
private final Lock stateLock = new ReentrantLock();
163-
164163
/**
165164
* A reference to this Stream's connection Send Window controller. The
166165
* stream MUST acquire the appropriate amount of Send Window before
167166
* sending any data. Will be null for PushStreams, as they cannot send data.
168167
*/
169168
private final WindowController windowController;
170-
private final WindowUpdateSender windowUpdater;
169+
private final WindowUpdateSender streamWindowUpdater;
171170

172171
@Override
173172
HttpConnection connection() {
@@ -206,7 +205,8 @@ private void schedule() {
206205
int size = Utils.remaining(dsts, Integer.MAX_VALUE);
207206
if (size == 0 && finished) {
208207
inputQ.remove();
209-
connection.ensureWindowUpdated(df); // must update connection window
208+
// consumed will not be called
209+
connection.releaseUnconsumed(df); // must update connection window
210210
Log.logTrace("responseSubscriber.onComplete");
211211
if (debug.on()) debug.log("incoming: onComplete");
212212
sched.stop();
@@ -222,7 +222,11 @@ private void schedule() {
222222
try {
223223
subscriber.onNext(dsts);
224224
} catch (Throwable t) {
225-
connection.dropDataFrame(df); // must update connection window
225+
// Data frames that have been added to the inputQ
226+
// must be released using releaseUnconsumed() to
227+
// account for the amount of unprocessed bytes
228+
// tracked by the connection.windowUpdater.
229+
connection.releaseUnconsumed(df);
226230
throw t;
227231
}
228232
if (consumed(df)) {
@@ -274,8 +278,12 @@ private void schedule() {
274278
private void drainInputQueue() {
275279
Http2Frame frame;
276280
while ((frame = inputQ.poll()) != null) {
277-
if (frame instanceof DataFrame) {
278-
connection.dropDataFrame((DataFrame)frame);
281+
if (frame instanceof DataFrame df) {
282+
// Data frames that have been added to the inputQ
283+
// must be released using releaseUnconsumed() to
284+
// account for the amount of unprocessed bytes
285+
// tracked by the connection.windowUpdater.
286+
connection.releaseUnconsumed(df);
279287
}
280288
}
281289
}
@@ -301,12 +309,13 @@ private boolean consumed(DataFrame df) {
301309
boolean endStream = df.getFlag(DataFrame.END_STREAM);
302310
if (len == 0) return endStream;
303311

304-
connection.windowUpdater.update(len);
305-
312+
connection.windowUpdater.processed(len);
306313
if (!endStream) {
314+
streamWindowUpdater.processed(len);
315+
} else {
307316
// Don't send window update on a stream which is
308317
// closed or half closed.
309-
windowUpdater.update(len);
318+
streamWindowUpdater.released(len);
310319
}
311320

312321
// true: end of stream; false: more data coming
@@ -376,8 +385,21 @@ public String toString() {
376385
}
377386

378387
private void receiveDataFrame(DataFrame df) {
379-
inputQ.add(df);
380-
sched.runOrSchedule();
388+
try {
389+
int len = df.payloadLength();
390+
if (len > 0) {
391+
// we return from here if the connection is being closed.
392+
if (!connection.windowUpdater.canBufferUnprocessedBytes(len)) return;
393+
// we return from here if the stream is being closed.
394+
if (closed || !streamWindowUpdater.canBufferUnprocessedBytes(len)) {
395+
connection.releaseUnconsumed(df);
396+
return;
397+
}
398+
}
399+
inputQ.add(df);
400+
} finally {
401+
sched.runOrSchedule();
402+
}
381403
}
382404

383405
/** Handles a RESET frame. RESET is always handled inline in the queue. */
@@ -461,7 +483,7 @@ CompletableFuture<ExchangeImpl<T>> sendBodyAsync() {
461483
this.responseHeadersBuilder = new HttpHeadersBuilder();
462484
this.rspHeadersConsumer = new HeadersConsumer();
463485
this.requestPseudoHeaders = createPseudoHeaders(request);
464-
this.windowUpdater = new StreamWindowUpdateSender(connection);
486+
this.streamWindowUpdater = new StreamWindowUpdateSender(connection);
465487
}
466488

467489
private boolean checkRequestCancelled() {
@@ -495,6 +517,8 @@ void incoming(Http2Frame frame) throws IOException {
495517
if (debug.on()) {
496518
debug.log("request cancelled or stream closed: dropping data frame");
497519
}
520+
// Data frames that have not been added to the inputQ
521+
// can be released using dropDataFrame
498522
connection.dropDataFrame(df);
499523
} else {
500524
receiveDataFrame(df);
@@ -1397,12 +1421,18 @@ void cancel(IOException cause) {
13971421

13981422
@Override
13991423
void onProtocolError(final IOException cause) {
1424+
onProtocolError(cause, ResetFrame.PROTOCOL_ERROR);
1425+
}
1426+
1427+
void onProtocolError(final IOException cause, int code) {
14001428
if (debug.on()) {
1401-
debug.log("cancelling exchange on stream %d due to protocol error: %s", streamid, cause.getMessage());
1429+
debug.log("cancelling exchange on stream %d due to protocol error [%s]: %s",
1430+
streamid, ErrorFrame.stringForCode(code),
1431+
cause.getMessage());
14021432
}
14031433
Log.logError("cancelling exchange on stream {0} due to protocol error: {1}\n", streamid, cause);
14041434
// send a RESET frame and close the stream
1405-
cancelImpl(cause, ResetFrame.PROTOCOL_ERROR);
1435+
cancelImpl(cause, code);
14061436
}
14071437

14081438
void connectionClosing(Throwable cause) {
@@ -1699,6 +1729,13 @@ String dbgString() {
16991729
return dbgString = dbg;
17001730
}
17011731
}
1732+
1733+
@Override
1734+
protected boolean windowSizeExceeded(long received) {
1735+
onProtocolError(new ProtocolException("stream %s flow control window exceeded"
1736+
.formatted(streamid)), ResetFrame.FLOW_CONTROL_ERROR);
1737+
return true;
1738+
}
17021739
}
17031740

17041741
/**

0 commit comments

Comments
 (0)