protobuf-net 中断读取后,下一份有效数据也报 Incorrect number of bytes consumed
protobuf-net 的池化 StreamProtoReader 在定长读取中途失败后,旧状态可能污染后续 MemoryStream 解码。正式 3.4.40 已重置这两个字段;不要把有效消息改成错误协议来绕过异常。
一份 Protobuf 数据单独读取正常,某次定长读取因超时或流异常中断后,下一份有效数据却开始报:
ProtoException: Incorrect number of bytes consumed
如果调用经过 protobuf-net 的 StreamProtoReader,后一次又使用 MemoryStream,要把前一次失败也纳入排查。仅反复检查后一份消息的字段号、长度与序列化模型,可能找不到问题,因为残留状态来自池中复用的 reader。
新 MemoryStream 不等于新 reader
定长读取使用 _isFixedLength 和 _dataRemaining64 跟踪剩余字节。若读取途中抛出异常,剩余长度可能还大于零,reader 随后被释放回池。
MemoryStream 有一条直接访问底层缓冲区的快路径,改用 _available 判断消费进度。正式 3.4.30 的这一分支没有主动清除前述定长字段。重用曾经中断的 reader 时,完整消费后一份消息后,结束检查仍可能拿旧的剩余长度判断,于是把有效消息判成未读完。
这里要同时具备中断的定长读取、reader 复用和后续快路径等条件。真正截断的数据、错误的长度前缀以及不同消息模型,也能导致消费长度异常,不能仅凭同一条错误文字认定池状态污染。
正式 3.4.40 在快路径重新初始化状态
3.4.40 已在 MemoryStream 快路径设置 _isFixedLength = false 和 _dataRemaining64 = 0,继续沿用 _available 的消费规则。采用包含这项修正的正式兼容版本,并核对发布产物中的 protobuf-net.Core,而不只看上层包声明。
显式管理依赖的项目,可以在已有包引用中调整版本:
<PackageReference Include="protobuf-net" Version="3.4.40" />
集中包管理的项目应在既有版本入口修改,不在项目文件里再叠一份竞争声明。保留序列化契约、字段编号与长度前缀约定;这个问题不需要给消息添加虚假的尾部字段,或把不完整消息当作成功结果。
新建 RuntimeTypeModel 或换一个 MemoryStream 并不等于清空内部 reader 池,不应把它们写成已经隔离污染的保证。也不要通过反射修改私有池字段。短期无法更新时,保留触发样本并把这条故障明确交给上层失败处理,不循环重试同一解码直到碰巧成功。
验证重点在失败之后
需要验证的是一串操作:先成功读取一份有效消息,再让独立受控流在定长读取中途失败,最后用新 MemoryStream 读取原来的有效字节。最后一次应完整还原字段;中间那次异常仍应传播,不能被“修复”成成功。
不要为制造这一条件去中断生产流或修改线上消息。在隔离验证中保留流类型、前缀方式、模型和字节原件,比较更新前后的执行路径。只验证健康启动后的第一次读取,绕开了这条复用条件。
同时确认输入流的所有权与关闭流程。reader 的释放不替应用决定业务请求是否成功;此前中断操作产生的副作用仍需按原业务记录核实。更新库可以阻止新的状态泄漏,不能证明之前失败的请求从未处理。


