Ordering, Flushing and Multiple Handlers
Scenario: A certificate is renewed and the web server must reload before a later task runs a TLS smoke test. The test runs first, against the old certificate, and fails.
New words, in plain English
meta: flush_handlers- a special task that runs all pending handlers immediately at that point.- Definition order - handlers run in the order they are DEFINED, not the order notified.
listen- a topic several handlers can subscribe to, and several tasks can notify.- Chained handlers - a handler that itself notifies another handler.
Handler ordering trips people up because it is not the order you would guess. Handlers run in the order they are defined in the handlers section, regardless of the order in which they were notified. If a reload must precede a restart, define the reload first - notifying it first changes nothing.
When work later in the same play depends on the handler having already run, meta: flush_handlers is the tool. It is a task like any other, placed exactly where the flush is needed, and it runs every pending handler at that point.
listen inverts the relationship usefully. Instead of tasks naming handlers, handlers subscribe to a topic. One notify: tls material changed can trigger a certificate reload, a proxy reload and a cache flush, each defined independently. Adding a fourth subscriber later requires no change to the notifying task at all - which matters a great deal in roles other teams consume.
Handlers can also notify other handlers, giving you an ordered chain: reload config notifies verify service, for example. Keep chains short; they are easy to write and hard to read.
Analogy: Handlers are the end-of-meeting actions list. They are worked through in the order written on the agenda, not the order people raised them. flush_handlers is calling a recess to deal with one item now, because the rest of the meeting depends on it.
A worked example
- name: Deploy the certificate
ansible.builtin.copy:
src: app.pem
dest: /etc/ssl/app.pem
mode: "0600"
notify: tls material changed # a TOPIC, not a handler name
- name: Make the reload happen NOW, not at the end of the play
ansible.builtin.meta: flush_handlers
- name: Smoke-test TLS with the new certificate
ansible.builtin.command: /opt/bin/tls-check.sh
changed_when: false
handlers:
- name: reload nginx # defined FIRST, so runs FIRST
listen: tls material changed
ansible.builtin.service: { name: nginx, state: reloaded }
- name: flush the edge cache
listen: tls material changed
ansible.builtin.command: /opt/bin/purge-cache.sh
Handlers in a role live in roles/<name>/handlers/main.yml and are visible to the whole play, so one role's task can notify another role's handler. That is powerful and occasionally surprising - a good reason to prefix handler names and topics with the role name.
A final ordering subtlety: flush_handlers flushes all pending handlers, not just the one you had in mind. If the play has accumulated several notifications, they all run at that point. Usually fine; worth knowing before you place a flush in the middle of a delicate sequence.
Tip: Prefix handler names andlistentopics with the role name -webapp reload, notreload. Handler namespaces are shared across the whole play.
Goal: Put this to work in the ansible-role-refactor lab. Open/labs/ansible, pickansible-role-refactor, and fix the real broken project - Ansible really does SSH into four managed hosts and converge them.