テクノロジー · 実用ガイド

Python SpooledTemporaryFileがディスク化・空読み?rolloverと位置を監査

libraryやsubprocessへ渡す前に、隠れたfileno rolloverとEOF位置を分離します。

オリジナルイラスト:SpooledTemporaryFileの三つのrollover trigger
オリジナルイラスト:small payloadもfilenoでdisk化します。 Watch at Ease

SpooledTemporaryFileには別々の落とし穴があります。consumerのfileno()で小さなpayloadもdiskへ移り、write後のEOF位置では下流が空を読みます。

public APIだけでtriggerを特定し、handoff前にseek(0)、bytesかdescriptorかを決め、real consumerでdeliveryとstorage policyを検証します。

1. storage stateとstream positionを分ける

consumerへ渡すobject、max_size、write bytes、tell()、fileno() callを記録します。memoryのままEOF、disk上でも同じEOFという別状態があります。

privateな_fileや_rolledをcontractにせず、隔離testでtemp directoryを観測し、所有するconsumer boundaryだけをwrapします。

2. rollover triggerを特定する

Pythonのtriggerは非zero max_size超過、fileno()、明示rollover()です。current CPythonのdefault 0はsize triggerを無効にしますが、後二つはdisk化します。

実際のlibrary/framework/subprocessを監査します。descriptor-only APIは隠れてfileno()を呼べるため、no-disk policyとは両立しません。

3. handoff前にpublic cursorを戻す

write後、次のreader前にseek(0)します。CPython issueのempty stdinはcursorがendにあったためで、object不良ではありません。rolloverはrewindしません。

FastAPI UploadFileのasync codeではawait upload.seek(0)を使い、syncではpublic file methodとfinal close ownerを明記します。

4. handoff contractを一つ選ぶ

consumerがbytesを受けるならpipe、BytesIO、read resultでno-diskを保てます。real descriptor必須なら明示rollover、temp space確認、seek(0)、handoff、owner closeを一つのcontractにします。

durable outputはcaller指定先へcopyし、必要ならfsync/atomic replaceを定義します。temporary fileは永続化interfaceではありません。

5. real boundaryで二軸を試験する

distinct payloadを書き、tell()、rewind、real consumer、exact resultを確認し、below-thresholdとforced-rolloverを実行します。

fileno()を記録するtest doubleと隔離temp directory観測を追加し、delivery、storage policy、close cleanupを別々にassertします。

オリジナルイラスト:spooled stream handoffとcursor受入
オリジナルイラスト:cursorを戻しreal consumerでbytesを照合します。 Watch at Ease