DEBUG.WIKI
晚安云;香港 CN2 ¥15/月
IPPeak:从目标地区访问网页,住宅代理、SEO 监测、广告验证
搬瓦工;先看线路,再选 VPS;CN2 GIA · 对应线路套餐;查看线路 →

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 及业务并发计数,而不是继续提高请求上限掩盖积压。