Add a "mutex" automation helper

It sometimes happens that two or more automations must not be running at the same moment (for example, one automation might make an adjustemnt to a piece of hardware, whilst the other is set to peridically reset it).

At the present time, the only way to ensure this would be to set some binary input to on when one of the scripts was running, or alternately, one could check the “Current” attribute of the automations. However these methods suffer from one drawback in that there is no guarantee of avoiding a race condition. Automation A could check the value and find it is off, whilst at precisely the same moment, script B checks the value and also finds it to be off.

To ensure that this never happens, it would be nice to have a Mutex helper. This would be similar to a boolean input, except that reading the value, checking if it is off and setting the state to on would be done “atomically”. Then one could a condition that invovles locking the mutex. The mutex could then be explcitly unlocked (attempting to unlock a mutex locked by another automation, or which is unlocked should either produce an error or silently fail). If not explicitly unlocked, then then all mutexes locked by an automation should automatically be unlocked when the automation exits (successfully or not).

If your automations are fighting each other then you need to rethink your logic.

e.g. using your example, combine the adjust and reset automations into one automation with a choose action.

You gave a simple example. I gave a simple solution. Perhaps post the three automations?

Also I do this quite successfully with an input boolean.

I have dozens of RS232 controls for my home theatre projector. Because the serial interface has no “busy” signal and crashes if more than one command is sent at once each command is put into a script that uses the same “busy” input boolean:

projector_iris:
  sequence:
  - condition: state
    entity_id: input_boolean.uart_busy
    state: 'off'
  - service: input_boolean.turn_on
    entity_id: input_boolean.uart_busy
  - service: esphome.projector_uart_write
    data:
      command: "KEY A5\r\n"
  - delay: 1
  - service: input_boolean.turn_off
    entity_id: input_boolean.uart_busy

projector_menu:
  sequence:
  - condition: state
    entity_id: input_boolean.uart_busy
    state: 'off'
  - service: input_boolean.turn_on
    entity_id: input_boolean.uart_busy
  - service: esphome.projector_uart_write
    data:
      command: "KEY 03\r\n"
  - delay: 1
  - service: input_boolean.turn_off
    entity_id: input_boolean.uart_busy

These can be called asynchronously from any automation or the dashboard without fear of crashing the serial interface.

2 posts were merged into an existing topic: Serial Projector control with ESPHome

The discussion above got way off topic, so to re-summarize:

It is so obvious to me that this would be useful., it is a sure-fire way of ensuring that only one of a set of automations can be run simultaneously Every other option has drawbacks.

  • Using an input boolean (flag) introduces a race condition. The read and update operations have to be done “atomically”. Otherwise, two or more automations can both read the flag as being “off” and both will set it to “on”. Then, when the first automation finishes, it will set the flag back to off, even though the other automation is still running Then a third automation can trigger, check the flag is off and witch it on. When the second finishes, it will switch the flag off, whilst the third is still running, and now we have a systematic failure. The flag becomes useless.

  • Checking the “Current” attribute of other automations requires a separate check for each rival automation, and duplicating an automation changes its entity ID, meaning every check needs to be updated to keep track of this. There is also the possibility of a kind of “deadlock” situation where nothing runs because two or more automations are checking at the same moment and both see the other as running.

NB: For automations that run in parallel mode, this can be considered a recursive lock.

Obviously there are numerous situations where one needs to ensure with 100% confidance that conflicting automations cannot run simultaneously. Considering that automations generally control some piece of hardware, there is a risk of interfering scripts causing undesured results, hardware malfunctions, or even in extreme cases causing permanent damage to equipment or endangering lives. You presumably don’t want “garage door close” to start running while “garage door open” is active for example.

This can be made really simple, checking the state of the mutex helper, can automatically try to lock it.
Then we can have a condition “input mutex a” is “unlocked” (off). This will either lock the mutex, setting its state to locked (on), and return true; or if the mutex is already “locked” (on), it will return false.

Then quite straightforwardly, with the garage door example, one would create a mutex helper “garage_door”, and each automation, open or close, would just add an extra condition :“input_mutex.garage_door” state is “unlocked”, and done.

I created a related thread here: Priority queue for state changes

And might have a solution for the mutex case:

  1. Create a string variable helper
  2. Create a script that runs in queued mode
  • The script is the only place where we are allowed to read and write to the helper variable
  • It takes a parameter that indicates whether we intend to lock or unlock the mutex.
  • The script returns whether locking was successfull, if not you’ll have to retry. You could do this by wait for template or on a timer. We do not guarantee fairness or ordering, your automation checking every 5 seconds will probably wait until all automations that use wait for template have run.
  • When calling the script we indicate the label we want to use for the lock, this is for debugging allowing us to detect bad automations:
    • We can know which automations are not releasing their locks
    • We can detect automations trying to release a mutex that they do not own
    • The script itself is the mutex, so if you need more you need to duplicate the script as well as the variable that tells us the holder of the lock.

PS. Sorry for necroing this :smiley:

Final solution:

  • Acquiring a lock is the only action that is serialized via a script
  • Releasing the lock is done by writing an empty value to the input_text helper

The script you can find here, the forum is buggy and posting code doesn’t work: HA Scripted Mutex · GitHub

Nope. You just need to learn how. See:

alias: lock1
sequence:
  - wait_template: "{{ is_state('input_text.lock1_owner', '') }}"
    continue_on_timeout: true
    alias: Wait for lock to be available
  - service: input_text.set_value
    data:
      value: "{{ owner }}"
    target:
      entity_id: input_text.lock1_owner
    alias: Set lock owner
mode: queued
fields:
  owner:
    selector:
      text: null
    name: owner
    description: Name of the owner
    required: true