The deferral helpers from the previous commit were appended to the cron/scheduler.py facade and carried ~70 lines of fallbacks (getattr/callable checks, result() waits, a teardown_registered flag) for hypothetical Future doubles; the only producer is _cron_pool.submit, always a real concurrent.futures.Future whose done()/add_done_callback() cannot raise. Also drops the _finalize_cron_session_db passthrough. Behaviour unchanged; the test now asserts the finalize+teardown contract on the real Future instead of a patched wrapper.
35 lines
1.3 KiB
Python
35 lines
1.3 KiB
Python
"""Cron: teardown of a worker that outlived its ``run_job``.
|
|
|
|
``ThreadPoolExecutor.shutdown(wait=False)`` after an inactivity timeout does not stop a
|
|
worker already inside ``run_conversation``. Finalizing its SessionDB from ``run_job``'s
|
|
``finally`` would close a handle the worker is still writing to — the checkpoint/WAL-unlink
|
|
overlap behind #102827. The worker's Future owns the teardown instead.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import concurrent.futures
|
|
from typing import Optional
|
|
|
|
|
|
def defer_teardown_to_running_worker(
|
|
future: Optional[concurrent.futures.Future], session_db, agent, job_id: str, job_name: str,
|
|
cron_session_id: str,
|
|
) -> bool:
|
|
"""Return True when the worker is still running and its Future will finalize the session
|
|
and tear the agent down on completion; False when the caller must do it now."""
|
|
if future is None or future.done():
|
|
return False
|
|
from cron.scheduler import _finalize_cron_session, _teardown_cron_agent
|
|
|
|
def _finish(_future) -> None:
|
|
try:
|
|
if session_db:
|
|
_finalize_cron_session(session_db, agent, job_id, job_name, cron_session_id)
|
|
finally:
|
|
_teardown_cron_agent(agent, job_id)
|
|
|
|
# Runs inline if the worker finished between done() and here — still exactly once.
|
|
future.add_done_callback(_finish)
|
|
return True
|