Open-Source Wikis

/

Ansible

/

Plugins

/

Strategy plugins

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 PlayIterator per 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 IncludedFile from lib/ansible/playbook/included_file.py.
  • Handler notifications and queueing.
  • The --step interactive mode.

The specific strategies override how dispatch and waiting interact.

linear

The default. Implements the lockstep behavior most users expect:

  1. For task N, dispatch to every active host in parallel (up to fork count).
  2. Wait for all hosts to complete task N.
  3. 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 clear

free

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: free

Used 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) c

Commands: 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 FinalQueue for results.
  • A WorkerProcess pool, 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; DisplaySendDisplay.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:

  1. Receives the IncludedFile from the iterator.
  2. Asks the iterator to expand the include into concrete tasks.
  3. Splices the new tasks into the per-host iteration state.
  4. 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:

  1. Tracks per-host _notified_handlers lists as tasks fire notify:.
  2. At end of play (or on meta: flush_handlers), switches to ITERATING_HANDLERS state.
  3. 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.py via strategy_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. Override run(). Be ready: this is one of the deeper APIs in the project, and the existing strategies' interactions with PlayIterator, handlers, and rescue/always blocks are subtle.
  • Adding a meta actionlib/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=debug and step through interactively, or wire display.vvv calls into play_iterator.py.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Strategy plugins – Ansible wiki | Factory