Bun canary 的 gunzipSync 只解出首个 gzip 成员
Bun 特定 canary 的 Bun.gunzipSync 可能只解开拼接 gzip 的第一个成员且不报错。保留压缩原件,使用 node:zlib 路径验证所有成员及损坏输入。
压缩文件没有报损坏,解压结果却只剩第一批日志;后面追加进去的记录全部消失。如果文件由多个 gzip 压缩段直接拼接而成,应检查解压器是否读完了所有成员。
Bun 1.4.4-canary.1+11c367163 的 Bun.gunzipSync() 有只返回首个 gzip 成员内容、却不抛异常的具体表现。同一输入改走 node:zlib 的 gunzipSync() 可以读出全部成员。这里不能推断所有正式 Bun 1.4.4 都有相同问题。
合法的 gzip 文件可以包含多个成员
gzip 格式允许压缩成员连续排列。把两份完整的 gzip 字节流拼在一起,与在一个压缩流后随意加入垃圾字节不是一回事。每个成员都有自己的头、压缩数据和校验尾部。
先保留原始 .gz 文件,确认生成端是否采用追加压缩段的方式,并比较预期记录数、末尾标记或业务序号。解压结果有文本且没有异常,只能说明取得了部分可解压内容,不能证明整份输入已经消费完。
也不要把文件最后四个字节的 ISIZE 当作全部解压内容的长度。它属于最后一个成员,还是模 2 的 32 次方计数,不能直接用于证明多个成员的总输出完整。
明确使用 node:zlib 的解压入口
对大小受控、已有完整 Buffer 的输入,可将解压入口改成:
import { gunzipSync } from 'node:zlib';
const output = gunzipSync(compressedBytes, {
maxOutputLength: allowedOutputBytes,
});
compressedBytes 是已读取并保留的 gzip 字节,allowedOutputBytes 应取自应用允许的解压输出上限。超过上限或解压失败时要停止本次处理,保留原件与异常,不要把空结果当成成功。
该方法仍是同步、整块解压,会占用内存并阻塞当前线程,不适合无界输入。处理大文件时应使用对应的流式解压与下游消费流程,同时设置应用级体积和资源限制;不要仅为拿到完整结果而取消所有上限。
导入方式也要核对,避免调用点仍然通过别名回到 Bun.gunzipSync()。项目里已有解压封装时,只调整这条受影响入口,再检查它对 Buffer、异常与编码的既有约定。
验收完整成员,也验收损坏文件
至少保留单成员、两个完整成员拼接、最后一个成员截断和尾部无效字节这几种独立样本。正常输入应返回完整内容,损坏输入应进入既定错误流程,不能静默接受首段后继续业务。
日志或数据导入还要验证业务记录总数及最后一条记录,不能仅比较开头字符串。不要靠手工搜索 gzip 魔数切片解压,魔数也可能出现在压缩数据里,容易制造新的数据丢失。
更换入口后原来遗漏的记录也不会自动重新入库,应按业务的幂等与恢复流程处理保留的压缩原件,避免重复导入已经成功的首段。


