module

Krikri::TaskBatcher

Pure planning logic for task batching (on by default; --no-batching disables it) - groups a flat task list into consecutive "batchable runs" with no I/O, no host/vars dependency, and no knowledge of connection type. TaskExecutor decides separately (per host, at run time) whether a given run of size >= 1 is actually worth/eligible for a real batched SSH round trip; a size-1 run here just means "this task can't extend a run on either side" and behaves exactly like today's one-task-at-a-time path.

A task can only safely share a batch with its neighbors if nothing about executing it needs data that only exists once an earlier task in the SAME run has actually executed remotely - see the reasoning behind each condition below.

Constants

RAW_MODULES = {"raw", "ansible.builtin.raw", "ansible.legacy.raw"}

The raw: spellings (bare and qualified) - the one definition of the set, shared with TaskExecutor's own raw handling. A task's action_name keeps the name as written (raw) while module_name is the alias-resolution target (ansible.builtin.shell), so callers check action_name || module_name.

Class methods

plan(tasks : Array(Task), aborts_on_notify : Proc(Task, Bool) | Nil = nil) : Array(Array(Task))

Groups tasks (one flat list - a play's top-level tasks, or one block:/rescue:/always:'s own nested list; this does not recurse into block_tasks/rescue_tasks/always_tasks itself - call it separately for each nested list you also want grouped) into consecutive runs, preserving order. Every task in tasks appears in exactly one returned group. aborts_on_notify (supplied by TaskExecutor, which owns the play's handler list) answers "if this task fires its notify:, will the run abort with HandlerNotFoundError?" - Ansible stops right there, having run nothing after it, while a batch group would already have executed every remaining step in the same SSH round trip, applying real side effects on the target that Ansible never applies. Such a task therefore ENDS its group (it may still run batched with what precedes it - the abort happens after it, not before). Only a notify: that is CERTAIN to abort breaks the run, so an ordinary playbook loses no batching at all.

Source
raw_task?(task : Task) : Bool

raw: is an ACTION plugin in real Ansible, not a module: it hands the command text to the connection with a pty and returns the connection's own capture - {rc, stdout, stdout_lines, stderr, stderr_lines} with no command-module keys, failing with "non-zero return code" rather than a module msg. TaskExecutor::execute_task_once rebuilds exactly that (shape_raw_result, raw_remote_pty_shape, raw_spawn_failure_shape) for a solo raw task; a batch script has no pty to capture through and its members skip that shaping entirely, so a grouped raw reported the shell plugin's own cmd/start/end/delta result and "Module failed: The command exited with a non-zero return code." where real printed "Action failed: non-zero return code" (issue #960, adarnimrod.openwrt-bootstrap's raw: opkg update). Every other raw fidelity fix (issues #490/#631/ #775/#820 - pty stream merge, CRLF, the ssh close notice, TERM) lives on that solo path too, so raw keeps its own group and always takes it.

Source