Skip to content

Commit 0e8b025

Browse files
authored
Channel#bytesBefore[un]writable off by 1 (#13389)
Motivation: Channel#bytesBefore[un]writable methods are described as returning the number of bytes until writability state changes. The ChannelConfig buffer high/low water marks are described as the thresholds must be exceeded before writability changes. The implementation of bytesBefore[un]writable methods return the number of bytes until the threshold is meet but not exceeded. If implementations depend upon this to drive readability they may hang. Modifications: - Channel#bytesBefore[un]writable implementations in http/2 and ChannelOutboundBuffer increment the value by 1 to return how much is required to cross the water mark therefore trigger a change in writability. Result: No more stales when implementations use `bytesBeforeWritable` / `bytesBeforeUnwritable`
1 parent f76d646 commit 0e8b025

File tree

3 files changed

+13
-21
lines changed

3 files changed

+13
-21
lines changed

codec-http2/src/main/java/io/netty/handler/codec/http2/AbstractHttp2StreamChannel.java

+6-10
Original file line numberDiff line numberDiff line change
@@ -361,26 +361,22 @@ public ChannelFuture closeFuture() {
361361

362362
@Override
363363
public long bytesBeforeUnwritable() {
364-
long bytes = config().getWriteBufferHighWaterMark() - totalPendingSize;
364+
// +1 because writability doesn't change until the threshold is crossed (not equal to).
365+
long bytes = config().getWriteBufferHighWaterMark() - totalPendingSize + 1;
365366
// If bytes is negative we know we are not writable, but if bytes is non-negative we have to check
366367
// writability. Note that totalPendingSize and isWritable() use different volatile variables that are not
367368
// synchronized together. totalPendingSize will be updated before isWritable().
368-
if (bytes > 0) {
369-
return isWritable() ? bytes : 0;
370-
}
371-
return 0;
369+
return bytes > 0 && isWritable() ? bytes : 0;
372370
}
373371

374372
@Override
375373
public long bytesBeforeWritable() {
376-
long bytes = totalPendingSize - config().getWriteBufferLowWaterMark();
374+
// +1 because writability doesn't change until the threshold is crossed (not equal to).
375+
long bytes = totalPendingSize - config().getWriteBufferLowWaterMark() + 1;
377376
// If bytes is negative we know we are writable, but if bytes is non-negative we have to check writability.
378377
// Note that totalPendingSize and isWritable() use different volatile variables that are not synchronized
379378
// together. totalPendingSize will be updated before isWritable().
380-
if (bytes > 0) {
381-
return isWritable() ? 0 : bytes;
382-
}
383-
return 0;
379+
return bytes <= 0 || isWritable() ? 0 : bytes;
384380
}
385381

386382
@Override

codec-http2/src/test/java/io/netty/handler/codec/http2/Http2MultiplexTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -925,7 +925,7 @@ public void writabilityOfParentIsRespected() {
925925
assertFalse(parentChannel.isWritable());
926926

927927
assertTrue(childChannel.isWritable());
928-
assertEquals(4096, childChannel.bytesBeforeUnwritable());
928+
assertEquals(4097, childChannel.bytesBeforeUnwritable());
929929

930930
// Flush everything which simulate writing everything to the socket.
931931
parentChannel.flush();

transport/src/main/java/io/netty/channel/ChannelOutboundBuffer.java

+6-10
Original file line numberDiff line numberDiff line change
@@ -748,29 +748,25 @@ public long totalPendingWriteBytes() {
748748
* This quantity will always be non-negative. If {@link #isWritable()} is {@code false} then 0.
749749
*/
750750
public long bytesBeforeUnwritable() {
751-
long bytes = channel.config().getWriteBufferHighWaterMark() - totalPendingSize;
751+
// +1 because writability doesn't change until the threshold is crossed (not equal to).
752+
long bytes = channel.config().getWriteBufferHighWaterMark() - totalPendingSize + 1;
752753
// If bytes is negative we know we are not writable, but if bytes is non-negative we have to check writability.
753754
// Note that totalPendingSize and isWritable() use different volatile variables that are not synchronized
754755
// together. totalPendingSize will be updated before isWritable().
755-
if (bytes > 0) {
756-
return isWritable() ? bytes : 0;
757-
}
758-
return 0;
756+
return bytes > 0 && isWritable() ? bytes : 0;
759757
}
760758

761759
/**
762760
* Get how many bytes must be drained from the underlying buffer until {@link #isWritable()} returns {@code true}.
763761
* This quantity will always be non-negative. If {@link #isWritable()} is {@code true} then 0.
764762
*/
765763
public long bytesBeforeWritable() {
766-
long bytes = totalPendingSize - channel.config().getWriteBufferLowWaterMark();
764+
// +1 because writability doesn't change until the threshold is crossed (not equal to).
765+
long bytes = totalPendingSize - channel.config().getWriteBufferLowWaterMark() + 1;
767766
// If bytes is negative we know we are writable, but if bytes is non-negative we have to check writability.
768767
// Note that totalPendingSize and isWritable() use different volatile variables that are not synchronized
769768
// together. totalPendingSize will be updated before isWritable().
770-
if (bytes > 0) {
771-
return isWritable() ? 0 : bytes;
772-
}
773-
return 0;
769+
return bytes <= 0 || isWritable() ? 0 : bytes;
774770
}
775771

776772
/**

0 commit comments

Comments
 (0)