Async Zero-Poll vs. Block Timeout: Which Ensures Clean Termination on Playbook Abort?
0 reputation · 26 Jul 2023, 05:53 UTC
0 reputation · 26 Jul 2023, 05:53 UTC
Ansible offers two documented mechanisms for limiting long‑running work: async tasks with poll=0 that return a job ID and block-level timeout that aborts a group of tasks after a wall‑clock period.
When a playbook is interrupted (e.g., Ctrl+C), the user wants any background work to be terminated cleanly, leaving no orphaned processes and ensuring the playbook’s exit status reflects the cancellation.
Existing documentation notes that async jobs may or may not be killed on playbook abort, and block timeouts are described as cooperative, potentially allowing started tasks to finish. Which approach provides more reliable cleanup?
poll=0 terminate automatically, or does it continue to run in the background?timeout guarantee that all child processes are killed when the block is aborted, or can they persist after the abort?32275 reputation · 26 Jul 2023, 16:12 UTC
A block-level timeout is the more reliable mechanism for clean termination on abort. An async task with poll=0 hands execution off to a background job and Ansible stops tracking it; when you press Ctrl+C, that job is generally left running on the target host (or controller). A block timeout, by contrast, is the one documented path where Ansible actively sends SIGTERM to the task's process group and waits for it to exit.
Confirmed behavior: With poll=0, Ansible writes an async job file, returns a job ID, and moves on. There is no built-in reaper that kills that job when the playbook is interrupted. The job runs to completion (or until its own async time limit expires) regardless of the playbook's fate.
Confirmed behavior: Block timeout (and the equivalent ANSIBLE_TASK_TIMEOUT setting) uses Ansible's task-timeout logic: on expiry or abort it signals the task process and waits up to the wall-clock period before giving up.
Version-dependent (verify before relying on it): Ansible releases before roughly 2.10 had weaker handling of async jobs on abort; newer cores improved bookkeeping but still do not guarantee async job termination. Treat any claim that "async jobs are killed on abort in recent versions" as unverified for your exact release—this draft assumes modern Ansible core (2.12+) but the safe posture is identical across versions.
Neither mechanism guarantees a clean process tree. Modules like shell or command that spawn their own subprocesses can leave those children alive if the parent dies, and some modules ignore SIGTERM or detach entirely. A block timeout kills what Ansible started; it cannot kill grandchildren the module orphaned.
For long-running work that must die with the playbook:
timeout.exec in shell wrappers so the signal reaches the real process, not a subshell).async: N / poll: 0 for fire-and-forget work where orphaning is acceptable, and pair it with an explicit cleanup path—a later async_status poll loop or a handler that kills the job by PID—rather than relying on abort semantics.Run a throwaway playbook with sleep 300:
# async variant: abort with Ctrl+C, then on the target:
ps aux | grep '[s]leep 300' # expect: still running
# block variant (timeout: 5): abort, then same check
ps aux | grep '[s]leep 300' # expect: gone
Run with -vvv to see the SIGTERM Ansible sends on block timeout. If the async job executes on the controller (e.g., delegate_to: localhost), check ps there instead. Repeat on your exact Ansible version before trusting the behavior in production.
If graceful cancellation matters, prefer block timeouts with SIGTERM-aware tasks. Async with poll=0 is a detachment primitive, not a lifecycle primitive—anything you start that way, you must be prepared to clean up yourself.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.