テクノロジー · 実用ガイド
Python の cannot schedule new futures after shutdown:先に終わったライフサイクルを特定する
Executor インスタンス、インタープリタ終了、asyncio の既定プールを分けてから再試行や再生成を判断します。

RuntimeError: cannot schedule new futures after shutdown は、ワーカー数を増やす合図ではありません。特定の Executor、asyncio の既定 Executor、またはインタープリタ全体の終了境界を送信側が越えた証拠です。正確な末尾と submit の完全なスタックから所有者を絞ります。
再試行を追加する前に証拠を保存します。各 Executor 世代に所有者を一つだけ置き、新規プロデューサーを止め、受理済み作業を待機または取消し、最後に一度だけ shutdown します。asyncio.run がループを所有するなら既定 Executor の終了も任せます。
1. 正確な終了シグナルを分類する
完全なトレースバックを保存し、after shutdown と after interpreter shutdown を区別します。submit、map、スケジューラ、ライブラリ、run_in_executor のどこから来たかも記録します。
生成、受理、終了開始、終了完了、最初の拒否で Executor 世代 ID、PID、スレッドまたはタスク名、単調時刻を記録します。公開された所有権契約で判断します。
2. インスタンス終了後の再利用を直す
with ブロックを出ると Executor は閉じます。長寿命オブジェクトが保持すると一回目だけ成功し二回目に失敗します。各独立実行で新規作成するか、with を全利用期間の所有者へ移します。
各呼出元が RuntimeError を捕捉して勝手に新しいプールを作る方法は避けます。所有者がロックまたは状態機械で現世代を公開し、draining 後の投入を拒否してから置換します。
3. インタープリタ終了と daemon 競合を直す
interpreter shutdown はプロセス終了が始まった意味です。main が戻った後も daemon の制御、スケジューラ、コールバックが投入しようとする場合や、主スレッドの未処理例外で起こります。
停止シグナルでプロデューサーを止め、新規投入を禁止し、制御スレッドと futures を待ち、Executor を閉じてから main を返します。必要作業の唯一の所有者を daemon にしません。
4. asyncio の既定 Executor 所有権を守る
run_in_executor(None, ...) はループの既定 Executor を使います。shutdown_default_executor() 後は再投入できません。asyncio.run は既定 Executor とループ終了を管理するため二重に閉じません。
手動ループでは、新しい Executor 作業を生むサーバーとコールバックを止め、関連タスクを待ち、shutdown_default_executor() を await してから loop を閉じます。別スレッドからの投入も境界前に排除します。
5. 再起動と終了試験で証明する
連続二回実行、キューありで shutdown、プロデューサー稼働中の再起動、主スレッド失敗を試験します。受理済みジョブは一つの終状態だけを持ち、draining 後の投入は禁止します。
保留ジョブを完了または取消しのどちらにするか決め、明示的に検証します。プロデューサー停止が Executor 終了より先で、重複副作用、孤立スレッド、握り潰した例外、遅延投入がない時系列を残します。
