Ansible Handlers Execution: Role-Level vs Playbook-Level Triggering – Best Practices for Task Dependencies


2 views

In Ansible 2.0+, handlers are special tasks that only run when notified by other tasks. A critical aspect often misunderstood is their execution timing relative to roles and playbooks. Handlers execute at specific trigger points - not immediately when notified, but at predefined intervals in the workflow.

Handlers defined within roles follow these execution patterns:


# Example role structure
roles/
  role1/
    handlers/
      main.yml  # These handlers execute at role completion
    tasks/
      main.yml
  role2/
    handlers/
      main.yml  # Separate handler execution point

Key observations from Ansible's execution model:

  • Handlers trigger after all tasks in their containing role complete
  • They don't wait for the entire playbook to finish
  • Each role's handlers execute independently

For your scenario requiring role4 handlers to complete before role6:


# playbook.yml
- hosts: all
  roles:
    - role1
    - role2
    - role3
    - role4  # Contains handlers needed by role6
    - { role: role6, vars: { wait_for_handlers: true } }  # Custom flag

Three reliable patterns for handler dependency management:

  1. Handler Chaining:
    
    # In role4/handlers/main.yml
    - name: Finalize role4 setup
      debug: msg="Role4 handlers complete"
      notify: role6_start_handler
    
  2. Explicit Task Dependencies:
    
    # In role6/tasks/main.yml
    - meta: flush_handlers
    - include_tasks: main_work.yml
    
  3. Handler Forwarding:
    
    # In playbook.yml
    handlers:
      - name: global_role4_complete
        include_role:
          name: role4
          handlers_from: handlers/main.yml
    

Debug handler execution order:


ansible-playbook playbook.yml \
--tags=role4,role6 \
--verbose \
--extra-vars "debug_handlers=true"

In Ansible, handlers are special tasks that only execute when notified by other tasks. The key behavior that often causes confusion is that handlers run:

  • At the end of each play by default (not at the end of individual roles)
  • Only if they've been notified during that play's execution

When you have a playbook structure like:

- hosts: webservers
  roles:
    - role1
    - role2
    - role3
    - critical_role
    - dependent_role

All handlers from critical_role will execute before dependent_role begins, because:

  1. Handlers queue up during role execution
  2. Ansible executes all pending handlers at play completion
  3. Roles execute sequentially within a play

If you need handlers to run immediately after a specific task (before the next role):

- name: Force handlers to run now
  meta: flush_handlers

Example usage between roles:

- hosts: webservers
  roles:
    - role1
    - role2
    - role3
  
  tasks:
    - include_role:
        name: critical_role
    
    - meta: flush_handlers
    
    - include_role:
        name: dependent_role

For complex workflows, consider:

  1. Explicit task dependencies:
  2. - name: Wait for service to be ready
      wait_for:
        port: 8080
        delay: 10
        timeout: 300
    
  3. Separate plays:
  4. - hosts: webservers
      roles:
        - critical_role
    
    - hosts: webservers
      roles:
        - dependent_role
    

To verify handler timing:

ansible-playbook playbook.yml -vvv | grep -E "handler|RUNNING HANDLER"

Or add debug tasks:

handlers:
  - name: restart service
    debug:
      msg: "Handler executed at {{ ansible_date_time.time }}"
    service:
      name: nginx
      state: restarted