Summary
Follow-up to #1039. That PR closed the startup instance of a broader bug: exceptions raised inside the fire-and-forget execute_async(strategy.execute_phase()) task in the timing/credit pipeline are never retrieved, so a failure there silently stalls the run instead of failing it.
#1039 added a per-phase StickyCreditRouter.wait_for_workers() barrier so the first credit is only issued after a worker has registered — closing the "no worker registered yet" deadlock. But the general swallowed-exception class remains.
The general problem
send_credit() stays fail-fast for the real no-workers-mid-run case, and other exceptions can still arise inside the issuance task. Because the task is fire-and-forget and its result is never awaited/observed, any such exception is swallowed:
all_credits_sent_event (and/or all_credits_returned_event) never gets set.
_wait_for_sending_complete() awaits forever → the run hangs with no error surfaced.
Known residual triggers (called out in #1039's description and review):
- The last worker unregisters after the
wait_for_workers barrier returns but before the first send_credit() — send_credit() raises "No workers available", which is swallowed.
- Any other exception inside
strategy.execute_phase() after issuance begins.
Expected behavior
The issuance task's exception should be observed and routed to the phase-failure path (the same mechanism _on_phase_orchestrator_done / _publish_phase_failure uses for phase-setup failures), so the run exits non-zero with a clear error instead of hanging.
Options to consider:
- Attach a done-callback to the
execute_async(strategy.execute_phase()) task that inspects task.exception() and drives the phase-failure path.
- Or set the completion events in a
finally/error path so _wait_for_sending_complete() can't await forever, and record the error.
Acceptance
- An exception raised inside the issuance task fails the run (non-zero exit,
BaseServiceErrorMessage recorded) rather than hanging.
- Regression test: inject a
send_credit/execute_phase failure mid-run and assert the run fails fast with the error surfaced (no hang).
Context
Summary
Follow-up to #1039. That PR closed the startup instance of a broader bug: exceptions raised inside the fire-and-forget
execute_async(strategy.execute_phase())task in the timing/credit pipeline are never retrieved, so a failure there silently stalls the run instead of failing it.#1039 added a per-phase
StickyCreditRouter.wait_for_workers()barrier so the first credit is only issued after a worker has registered — closing the "no worker registered yet" deadlock. But the general swallowed-exception class remains.The general problem
send_credit()stays fail-fast for the real no-workers-mid-run case, and other exceptions can still arise inside the issuance task. Because the task is fire-and-forget and its result is never awaited/observed, any such exception is swallowed:all_credits_sent_event(and/orall_credits_returned_event) never gets set._wait_for_sending_complete()awaits forever → the run hangs with no error surfaced.Known residual triggers (called out in #1039's description and review):
wait_for_workersbarrier returns but before the firstsend_credit()—send_credit()raises "No workers available", which is swallowed.strategy.execute_phase()after issuance begins.Expected behavior
The issuance task's exception should be observed and routed to the phase-failure path (the same mechanism
_on_phase_orchestrator_done/_publish_phase_failureuses for phase-setup failures), so the run exits non-zero with a clear error instead of hanging.Options to consider:
execute_async(strategy.execute_phase())task that inspectstask.exception()and drives the phase-failure path.finally/error path so_wait_for_sending_complete()can't await forever, and record the error.Acceptance
BaseServiceErrorMessagerecorded) rather than hanging.send_credit/execute_phasefailure mid-run and assert the run fails fast with the error surfaced (no hang).Context
src/aiperf/timing/manager.py(_on_phase_orchestrator_done),src/aiperf/timing/phase/runner.py(_run_strategy,execute_async(strategy.execute_phase())),src/aiperf/credit/sticky_router.py(send_credit).