Watch at Ease看见世界,读懂生活。

科技 · 实用指南

Python SpooledTemporaryFile意外落盘或读空?审计rollover与游标

在把spooled upload交给库或子进程前,分清隐藏的fileno落盘与EOF游标。

SpooledTemporaryFile三种rollover触发器图
原创示意图:小payload也可能因fileno而落盘。 Watch at Ease

SpooledTemporaryFile有两个独立陷阱:消费者调用fileno()会让很小的payload落盘;写入后游标停在EOF,又会让下游读到空数据。

只用公开API定位rollover触发器,交接前seek(0),明确bytes或descriptor合同,并在真实消费者边界同时验证字节与存储策略。

1. 分开存储状态与流位置

记录交给消费者的对象、max_size、写入字节、tell()与任何fileno()调用。spool可仍在内存却停在EOF,也可已落盘但仍停在同一EOF。

不要把私有_file或_rolled当合同;在隔离测试环境观察临时目录,并只包装自己控制的consumer边界以找出descriptor请求。

2. 说清rollover触发器

Python记录三条路径:超过非零max_size、调用fileno()、显式rollover()。默认max_size=0在当前CPython不按大小触发,但后两者仍会落盘。

审计真实library/framework/subprocess;只收descriptor的API可能暗中调用fileno()。若政策禁止磁盘,它就与该政策不兼容。

3. 交接前重置公开游标

写完后,下一个reader读取同一对象前必须seek(0)。CPython问题里的空stdin正是游标在结尾,而不是对象无效。rollover不会承诺rewind。

FastAPI UploadFile异步代码用await upload.seek(0);同步代码按API合同使用公开file方法,并明确最后close的owner。

4. 选择一种交接合同

consumer接受bytes时,可用pipe、BytesIO或普通read结果保持不落盘。它若要求真实descriptor,就显式rollover、确认临时空间、seek(0)、交接并由约定owner关闭。

需要持久输出时复制到调用方选定路径,并明确fsync或atomic replace;临时文件关闭后会消失,不是持久化接口。

5. 在真实边界测试两个轴

写入独特payload,记录tell(),rewind,调用真实consumer并核对字节或解析结果。分别跑阈值以下与强制rollover用例。

增加记录fileno()调用的test double与隔离的临时目录观察。验收必须同时证明完整交付、磁盘使用符合政策,并单独断言close清理。

spooled stream交接与游标验收图
原创示意图:重置游标并在真实consumer核对字节。 Watch at Ease