ansible/ansible
Strategy plugins
The strategy plugin controls when each task gets dispatched to which worker for which host. ansible-core ships four: linear (default), free, host_pinned, debug.
Files
lib/ansible/plugins/strategy/
├── __init__.py # StrategyBase — the shared "next task → dispatch → wait → process" loop
├── linear.py # The default — all hosts run the same task in lockstep
├── free.py # Each host runs as fast as it can, independently
├── host_pinned.py # Like linear, but each worker is dedicated to one host for the play
└── debug.py # Like linear, but drops into an interactive prompt on failure| Strategy | Lines |
|---|---|
__init__.py (StrategyBase) |
1,159 |
linear.py |
395 |
free.py |
(small) |
host_pinned.py |
(small) |
debug.py |
(small) |
StrategyBase carries the bulk of the logic; the specific strategies override run() to choose the dispatch policy.
What a strategy does
graph TD
START[run play] --> LOOP{More tasks for any host?}
LOOP -->|no| DRAIN[Drain remaining results]
LOOP -->|yes| PICK[Pick (host, task) per policy]
PICK --> DISP[Push to worker queue]
DISP --> WAIT{Wait for results?}
WAIT -->|yes - linear| BLOCK[Block until all hosts complete this task]
WAIT -->|no - free| NONBLOCK[Continue dispatching new tasks]
BLOCK --> RES[Process results, fire callbacks]
NONBLOCK --> RES
RES --> NEXT[Update PlayIterator state]
NEXT --> LOOP
DRAIN --> END[end play]The shared base class handles:
- Pulling the next task from
PlayIteratorper host. - Materializing task vars by templating against the host's variable scope.
- Pushing
(host, task, play_context, vars)to a worker queue. - Draining the result queue: process
WireTaskResult,DisplaySend,PromptSend,CallbackSend. - Handling
meta:tasks (refresh_inventory,flush_handlers,noop,clear_facts,end_play). - Including dynamic content via
IncludedFilefromlib/ansible/playbook/included_file.py. - Handler notifications and queueing.
- The
--stepinteractive mode.
The specific strategies override how dispatch and waiting interact.
linear
The default. Implements the lockstep behavior most users expect:
- For task N, dispatch to every active host in parallel (up to fork count).
- Wait for all hosts to complete task N.
- Move to task N+1.
Slow hosts hold up the entire group. The lockstep behavior is what makes --start-at-task and --limit predictable. lib/ansible/plugins/strategy/linear.py handles the wrinkles: handlers, failed_when, until/retries loops, and the "ITERATING_RESCUE/ALWAYS" transitions per host.
- hosts: webservers
strategy: linear # the default — explicit only if you want to be clearfree
Each host runs through its task list as fast as it can. Tasks proceed independently per host. A host that finishes early gets started on the next task while slower hosts are still on the previous one.
- hosts: webservers
strategy: freeUsed for embarrassingly-parallel workloads where there are no inter-host dependencies. Note that notify/handler ordering is still respected — handlers fire at the end of the play (or on meta: flush_handlers).
host_pinned
A hybrid: like linear lockstep but each worker process is "pinned" to one host for the duration of the play. With forks=N and M=N hosts, each host gets a dedicated worker. Useful when establishing a new connection per task is expensive and the connection plugin doesn't support ControlPersist.
debug
Identical to linear, but on task failure it drops into an interactive prompt:
Debugger invoked
(debug) p task
(debug) p task_vars
(debug) r
ok: [hostname]
(debug) cCommands: p task, p task_vars, p result, update_task <attr> <value>, r (redo), c (continue), q (quit).
Implementation in lib/ansible/plugins/strategy/debug.py. See Debugging for the user-facing flow.
Communication with workers
The TQM provides each strategy with:
- An input queue per worker (
multiprocessing.Queue). - A shared
FinalQueuefor results. - A
WorkerProcesspool, started at TQM init.
The strategy round-robins or task-affinity-based dispatches (host, task) tuples to worker queues, and pulls results from FinalQueue. Result types include:
WireTaskResult— a task's output.DisplaySend— a worker wants something printed.PromptSend— a worker is asking for user input (vault password, become password).CallbackSend— a worker is firing a callback event manually.
The strategy routes each type to the right place: WireTaskResult → callback dispatch + iterator update; DisplaySend → Display.display; PromptSend → controller-side prompt + reply; CallbackSend → callback dispatch.
Includes mid-play
include_tasks/include_role are dynamic — they're evaluated at task execution time. When the iterator advances over an include, the strategy:
- Receives the
IncludedFilefrom the iterator. - Asks the iterator to expand the include into concrete tasks.
- Splices the new tasks into the per-host iteration state.
- Continues dispatching.
This is why include_tasks can use templates in its file: argument — the include resolves at task time with full variable context.
Handlers
Handlers (lib/ansible/playbook/handler.py) are tasks that run only when a previous task notifies them. The strategy:
- Tracks per-host
_notified_handlerslists as tasks firenotify:. - At end of play (or on
meta: flush_handlers), switches toITERATING_HANDLERSstate. - Dispatches each notified handler against each notifying host.
Handler ordering is preserved: handlers run in the order they were defined in the play, not the order they were notified.
Integration points
- Imported by:
lib/ansible/executor/task_queue_manager.pyviastrategy_loader.get. - Imports:
lib/ansible/executor/play_iterator.py,lib/ansible/executor/task_result.py,lib/ansible/playbook/handler.py,lib/ansible/playbook/included_file.py,lib/ansible/_internal/_templating/_engine.py.
Entry points for modification
- Customizing scheduling for a specific environment — write a strategy plugin in a collection. Subclass
StrategyBase. Overriderun(). Be ready: this is one of the deeper APIs in the project, and the existing strategies' interactions withPlayIterator, handlers, and rescue/always blocks are subtle. - Adding a meta action —
lib/ansible/plugins/strategy/__init__.py:StrategyBase._execute_meta. Add a new branch and update the meta module's documentation. - Debugging strategy state — set
ANSIBLE_STRATEGY=debugand step through interactively, or wiredisplay.vvvcalls intoplay_iterator.py.
Cross-links
- Executor — the bigger picture of TQM + workers + strategies.
- Callback — the event system the strategy emits into.
- Primitives → Task and block — what the iterator walks.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.