DEBUG.WIKI
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →
PanstarCloud:部署应用,接入 AI,云服务器与 AI 网关
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →

Trio send_all 没报错却少发数据,检查 memoryview 的 itemsize

Trio 0.34.0 的发送重试会混用字节数与 memoryview 元素偏移。对已确认连续内存传入一维字节视图,并校验接收内容与在途消息。

把一段连续内存转换成 memoryview(...).cast("H") 后交给 Trio send_all(),调用正常返回,接收端却少了一部分数据。这个条件下应检查发送缓冲区的元素大小,以及底层是否发生了部分写入。

Trio 0.34.0 的 SocketStream.send_all 和 POSIX FdStream.send_all,仍把系统调用返回的字节数用于 memoryview 的切片偏移。对于 itemsize > 1 的视图,字节计数与元素索引不一致,部分写入后的重试可能跳过数据。一次性写完的小样本不一定暴露这个问题。

先把字节数和元素数分开

例如,一个 H 视图的元素通常占两个字节,len(view) 与 view.nbytes 含义不同。系统调用报告发送了多少字节,而 view[sent:] 按元素偏移;若混用这两个单位,后续切片就可能越过原本还没发送的区域。

先记录视图的 format、itemsize、ndim、连续性与 nbytes,并让受控接收端核对完整长度和内容。不要只比较发送调用是否抛异常,也不要在日志中输出真实业务载荷。少发的具体比例受系统写入大小影响,不应把某次“刚好一半”当作固定规律。

传入一维字节视图

对于已经确认的 C 连续内存,可以在调用边界转换成一维 B 视图,让切片索引和字节计数一致。应用已有连续缓冲区 payload 时,可以把发送位置改成:

with memoryview(payload) as original:
    if not original.c_contiguous:
        raise ValueError("expected a C-contiguous buffer")
    with original.cast("B") as byte_view:
        await stream.send_all(byte_view)

stream 是应用已经建立、由当前任务管理的 Trio 流,代码位于 async 函数中。视图必须保持到 await 完成;发送期间不能让其他任务修改其底层可写内存。转换视图不会替你解决并发修改或缓冲区生命周期问题。

如果业务本来就有一份 bytes,直接发送它也能避免这种多字节元素索引的情况。不连续视图需要先确定正确的元素排列和协议字节序,再评估是否复制;不能不加判断地把所有数组转换成原始内存后发出去。cast("B") 只改变观察内存的方式,不是网络字节序转换或通用序列化。

多维视图和零维视图还涉及长度、切片规则。不要依赖恰好能在某个小输入上通过的形状;上面的调用只接受已确认的 C 连续内存,并显式展平成字节视图。

处理已发生的截断,不能盲目重发

调用返回成功不意味着对端已经收到完整业务消息。若协议包含长度、校验或操作编号,先用它核对完整消息和处理状态。对端可能已收到前半段,或已经开始执行有副作用的操作;直接在同一连接上重发整段,可能把残余字节与新消息拼在一起。

恢复方式应遵守原有协议的断帧、连接重建与幂等规则。保留可重放的原始输入及发送记录,不能靠清空接收缓存掩盖已丢失的数据。需要确认修复时,在隔离管道或受控服务中安排足够大的载荷及背压,让部分写入路径得到覆盖,比较完整字节内容,而不是只看日志里的长度。

正式 0.34.0 的这两处实现仍可核到相同单位混用。后续升级应检查具体正式版本与同一接收校验,不把提交了补丁等同于发行修复。回退字节视图调整前,也要保留接收端对旧格式和在途消息的处理约定。