From ac8035f6b55db0096ca3b4a8961323a1d900ff3e Mon Sep 17 00:00:00 2001 From: Omar Elamri Date: Fri, 14 Aug 2026 21:16:56 +0000 Subject: [PATCH 1/2] Move connection window update before stream termination logic By RFC 9113, section 6.9, receivers must take frames into account for flow control even if they're errored. This change moves the stream error response logic after connection window updates --- .../io/grpc/okhttp/OkHttpServerTransport.java | 24 ++++++++++--------- .../okhttp/OkHttpServerTransportTest.java | 2 ++ 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerTransport.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerTransport.java index 7d192b16943..8b5d8e6405f 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerTransport.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpServerTransport.java @@ -864,6 +864,19 @@ public void data(boolean inFinished, int streamId, BufferedSource in, int length // concerned with the window being exceeded at this point. in.require(length); + // connection window update + // The connection window must be updated even if the stream is in an errored state. + // See RFC 9113, section 6.9 + connectionUnacknowledgedBytesRead += paddedLength; + if (connectionUnacknowledgedBytesRead + >= config.flowControlWindow * Utils.DEFAULT_WINDOW_UPDATE_RATIO) { + synchronized (lock) { + frameWriter.windowUpdate(0, connectionUnacknowledgedBytesRead); + frameWriter.flush(); + } + connectionUnacknowledgedBytesRead = 0; + } + synchronized (lock) { StreamState stream = streams.get(streamId); if (stream == null) { @@ -887,17 +900,6 @@ public void data(boolean inFinished, int streamId, BufferedSource in, int length buf.write(in.getBuffer(), length); stream.inboundDataReceived(buf, length, paddedLength - length, inFinished); } - - // connection window update - connectionUnacknowledgedBytesRead += paddedLength; - if (connectionUnacknowledgedBytesRead - >= config.flowControlWindow * Utils.DEFAULT_WINDOW_UPDATE_RATIO) { - synchronized (lock) { - frameWriter.windowUpdate(0, connectionUnacknowledgedBytesRead); - frameWriter.flush(); - } - connectionUnacknowledgedBytesRead = 0; - } } @Override diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerTransportTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerTransportTest.java index 00db6e1d339..d0420213ca2 100644 --- a/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerTransportTest.java +++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerTransportTest.java @@ -1067,6 +1067,8 @@ public void windowUpdate() throws Exception { writeDataDirectly(clientWriterSink, FLAG_PADDED | FLAG_END_STREAM, 1, message, 100); clientFrameWriter.flush(); assertThat(clientFrameReader.nextFrame(clientFramesRead)).isTrue(); + verify(clientFramesRead).windowUpdate(0, expectedConsumed + 100); + assertThat(clientFrameReader.nextFrame(clientFramesRead)).isTrue(); verify(clientFramesRead).rstStream(eq(1), eq(ErrorCode.FLOW_CONTROL_ERROR)); clientFrameWriter.rstStream(3, ErrorCode.CANCEL); pingPong(); From 6569e5d29383edcbc78e816c41d641235042c5b6 Mon Sep 17 00:00:00 2001 From: Kannan J Date: Mon, 17 Aug 2026 09:06:49 +0530 Subject: [PATCH 2/2] Update OkHttpServerTransportTest.java --- .../src/test/java/io/grpc/okhttp/OkHttpServerTransportTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerTransportTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerTransportTest.java index d0420213ca2..1456f421156 100644 --- a/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerTransportTest.java +++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpServerTransportTest.java @@ -1067,6 +1067,7 @@ public void windowUpdate() throws Exception { writeDataDirectly(clientWriterSink, FLAG_PADDED | FLAG_END_STREAM, 1, message, 100); clientFrameWriter.flush(); assertThat(clientFrameReader.nextFrame(clientFramesRead)).isTrue(); + // Receive window update for the padded data size before stream reset verify(clientFramesRead).windowUpdate(0, expectedConsumed + 100); assertThat(clientFrameReader.nextFrame(clientFramesRead)).isTrue(); verify(clientFramesRead).rstStream(eq(1), eq(ErrorCode.FLOW_CONTROL_ERROR));