Skip to content

Commit

Permalink
Channel#bytesBefore[un]writable off by 1 (#13389)
Browse files Browse the repository at this point in the history
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`
  • Loading branch information
Scottmitch committed May 22, 2023
1 parent f76d646 commit 0e8b025
Show file tree
Hide file tree
Showing 3 changed files with 13 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -361,26 +361,22 @@ public ChannelFuture closeFuture() {

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

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

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -925,7 +925,7 @@ public void writabilityOfParentIsRespected() {
assertFalse(parentChannel.isWritable());

assertTrue(childChannel.isWritable());
assertEquals(4096, childChannel.bytesBeforeUnwritable());
assertEquals(4097, childChannel.bytesBeforeUnwritable());

// Flush everything which simulate writing everything to the socket.
parentChannel.flush();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -748,29 +748,25 @@ public long totalPendingWriteBytes() {
* This quantity will always be non-negative. If {@link #isWritable()} is {@code false} then 0.
*/
public long bytesBeforeUnwritable() {
long bytes = channel.config().getWriteBufferHighWaterMark() - totalPendingSize;
// +1 because writability doesn't change until the threshold is crossed (not equal to).
long bytes = channel.config().getWriteBufferHighWaterMark() - totalPendingSize + 1;
// If bytes is negative we know we are not writable, but if bytes is non-negative we have to check writability.
// Note that totalPendingSize and isWritable() use different volatile variables that are not synchronized
// together. totalPendingSize will be updated before isWritable().
if (bytes > 0) {
return isWritable() ? bytes : 0;
}
return 0;
return bytes > 0 && isWritable() ? bytes : 0;
}

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

/**
Expand Down

0 comments on commit 0e8b025

Please sign in to comment.