Netty 4.2.15 暂停读取后,FlowControlHandler 仍释放整批消息
Netty 4.2.15 排空已解码队列时可能忽略下游再次关闭 auto-read。核对 netty-handler 的正式修正,并验证积压恢复与显式 read。
Netty 4.2.15.Final 的 FlowControlHandler 恢复读取后,业务 handler 即使在第一条 channelRead 中再次调用 setAutoRead(false),已经排队的消息仍可能整批涌入。限流开关已经变为 false,待处理请求数却继续增长。
这条回归发生在消息队列的排空循环里。关闭 auto-read 可以停止后续底层读取,却无法撤回解码器已经产出的消息;FlowControlHandler 本应在它们交给业务前再次检查是否还允许读取。
把“关闭后不读 socket”与“停止释放已解码消息”分开
典型管线是解码器、FlowControlHandler、业务 handler。一次 socket 读取可能解码出多条请求。业务处理第一条时暂停读取,剩余消息暂存在 FlowControlHandler;重新允许读取后,业务可能处理一条就发现仍超限,于是立即再次暂停。
4.2.15 的实现先判断一次 auto-read,再进入不限制条数的 dequeueAll。排空过程中不再重新读取开关,因此第二次暂停来得及阻止未来 I/O,却阻止不了这一批消息继续下传。它不是“setAutoRead 从来没生效”,而是暂停发生在排空过程中的特定缺口。
下面是放在现有 Netty/JUnit 5 测试工程中的诊断用例;字符串只用于观察消息数量,不涉及引用计数缓冲区:
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.flow.FlowControlHandler;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class FlowPauseTest {
@Test
void pauseAgainWhileResuming() {
List<Object> received = new ArrayList<>();
EmbeddedChannel channel = new EmbeddedChannel(
new FlowControlHandler(),
new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
received.add(msg);
ctx.channel().config().setAutoRead(false);
}
});
try {
channel.writeInbound("one", "two", "three");
assertEquals(1, received.size());
channel.config().setAutoRead(true);
assertEquals(2, received.size());
} finally {
channel.finishAndReleaseAll();
}
}
}
第二个断言检查恢复后只多释放一条消息。不要只检查最终 isAutoRead() 为 false:故障版本中开关值也可以正确,消息却已经全部交给下游。
核对实际 handler JAR,采用包含修正的版本
Netty 4.2.17.Final 的正式源码已在每次循环判断 config.isAutoRead() || unsatisfiedReads > 0,能够响应下游在排空途中再次暂停。它是一个可以核对到修正实现的正式版本;项目应采用依赖管理允许、包含同项修正的受维护兼容版本,而不要求长期固定在这个示例版本。
统一核对 Netty 模块的解析结果及运行包,避免只更新 transport,却仍使用旧的 netty-handler。由框架管理的依赖应按框架兼容方式更新,保留变更前依赖清单和应用产物。
显式 read() 是读取请求,修正实现仍会满足尚未完成的读取请求。若业务或另一个 handler 不断调用 read(),不能期待仅将 auto-read 关闭就取消这些请求。这里的用例没有另外发出手工 read,才适合判断这条回归。
验证还应覆盖首次暂停、积压恢复、恢复中再次暂停和关闭连接。使用 ByteBuf 的真实管线需保留原来的释放与所有权规则;不能为减少消息数直接丢弃缓冲区,也不要用阻塞 event loop 的方式假装产生背压。若修正后仍超限,再检查 handler 顺序、手工 read 及业务并发计数,而不是继续提高请求上限掩盖积压。


