科技 · 實用指南
Python SpooledTemporaryFile意外落盤或讀空?稽核rollover與游標
交給library或子程序前,分清隱藏的fileno落盤與EOF游標。

SpooledTemporaryFile有兩個獨立陷阱:consumer呼叫fileno()會讓很小的payload落盤;寫入後游標停在EOF又會讓下游讀到空資料。
只用公開API定位rollover觸發器,handoff前seek(0),明確bytes或descriptor contract,並在真實consumer邊界驗證資料與storage policy。
1. 分開storage state與stream position
記錄交給consumer的物件、max_size、寫入bytes、tell()與任何fileno()呼叫。spool可仍在memory卻停在EOF,也可已落盤但保留同一EOF。
不要把私有_file或_rolled當contract;在隔離測試環境觀察temp directory,只包裝自己控制的consumer boundary。
2. 說清rollover觸發器
Python記載三條路徑:超過非零max_size、呼叫fileno()、顯式rollover()。預設max_size=0在目前CPython不按size觸發,但後兩者仍會落盤。
稽核真實library/framework/subprocess;descriptor-only API可能暗中呼叫fileno()。若政策禁止disk,它便不相容。
3. handoff前重設公開游標
寫完後,下一個reader讀同一物件前必須seek(0)。CPython問題的empty stdin是cursor在結尾,不是物件無效。rollover不會rewind。
FastAPI UploadFile的async code用await upload.seek(0);同步code依contract使用公開file method,並明確close owner。
4. 選擇一種handoff contract
consumer接受bytes時,可用pipe、BytesIO或read result保持no-disk。若要求descriptor,就顯式rollover、確認temp space、seek(0)、handoff並由owner關閉。
需要durable output時複製到caller選定path,明確fsync或atomic replace;temporary file不是持久化介面。
5. 在真實邊界測試兩個軸
寫入distinct payload,記錄tell(),rewind,呼叫真實consumer並核對結果。分別執行below-threshold與forced-rollover case。
加入記錄fileno()的test double與隔離temp directory觀察;同時驗證完整delivery、storage policy與close cleanup。
