You keep saying this but it’s just not true. Things are executed in the order they are received in the event loop. Please, try the automation that I posted above with the 100k runs. You’ll see that it works every time. This is just the nature of asyncio and how scripts work.
Action sections are executed in the event loop but their service calls are also executed in the same place. When an automation reaches a service call, it adds that service to the event loop, which is executed in order. You 100% can use input_booleans as mutexs. If you execute a turn_on event to an input_boolean, any other service that accesses the state machine after will show the input_boolean as on. Even with 2 things that appear to fire concurrently. Whatever fires first (and one will fire first), the second one will show the input_boolean as on.
Okay, thank you. However, some more general things are still not clear to me (sorry). Hopefully you can elaborate…
Is an ‘action section’ all of the actions making up the ‘Then do’ part of an automation (or the entire set of actions in a script)? Or something else? For the rest of my questions I will assume the former so please correct me if this is wrong.
Are script calls in an action section equivalent to ‘service calls’?
You said that action sections are executed in the asyncio event loop. Is the entire action section always treated as a single asyncio task? Or is it split into multiple (sequential) asyncio tasks in some scenarios?
Your statement that service calls made in the action section result in a new task being queued on the asyncio event loop (presumably at the end?). This would suggest that if an action section includes service calls it cannot be executed in its entirety as a single asyncio task? If true then this also suggests (to me at least) that a completely different task (from a different automation/script) could get queued on the event loop before the service call task.
I’d really like to understand this as it is important in the wider context not just in terms of mutexes.
Yes to both. 1 task to run the script, and a task for each service call when reached.
You need to read up on asyncio. You’re asking questions that only asycio documentation will cover properly. Home assistant uses a standard asyncio AbstractEventLoop with a few minor non-noteworthy changes. As others have said above, it makes decisions when to create new threads or not so that everything continuously runs. Long awaited tasks are allocated to worker threads.
The script may be treated like an long awaited task that’s running while it schedules other tasks in the event loop. It depends on what it decides to do.
If you want more details, then you should read the code in homeassistant’s main folder (Not the script file linked above). The only way you’ll understand what’s happening in core is to read how HA schedules tasks on the main loop. Even then, the documentation for asyncio is your best resource.
Lastly, all of this info really isn’t needed. You can view automation traces to debug what went wrong instead of what you’re doing here. It will give you timestamps when things execute and why they didn’t execute. The only exception is that errors will just be indicated in the trace, however the error contents will be in your logs.
I do use Traces but they seem to have annoying limitations (unless I am missing something - which is quite possible):
If you have an And or Or building block in an automation’s conditions the trace doesn’t seem to give any details on the evaluation of the conditions within those so you don’t know which one(s) resulted in true (or false). Is there a way to dig into this detail within the trace timeline?
The system seems to only keep the last 5 traces, which is far too few in many cases. Can this limit be increased?
As I’ve said in other places here, I’m not a Python programmer and currently have no plans to be one so reading the HA source code isn’t an ideal solution. This stuff really ought to be described in the HA documentation in enough detail so folk can understand how the system behaves.
Yes it does, you need to click on each one in the sidebar. If you’re like me and don’t like using the UI. You can view the trace details as JSON without needing to click each node.
Yes, you can increase this to any number you want. IIRC the field is max_traces, it’s covered in the documentation.
I’m fairly sure no one is interested in regurgitating asyncio documentation into HA. This information (while you disagree) is largely not needed. Not only would it confuse people, it doesn’t really help anyone solve the problems in the long run.
There’s no need to worry about concurrency issues if you write your automations and scripts keeping in mind other automations. Each automation in Home Assistant has an associated entity ID which usually looks like automation.<name>. The state of this entity changes to on when the automation is enabled and off when it is disabled. To directly check if the automation is actively running, you need to monitor its last_triggered attribute along with a condition to see if it is running currently. All of this can be put into template sensors so you can just refer to those sensor states in your automations or scripts as well.
You can also enable and disable any automation at will via YAML code in other automations as well.
With all of the above tools at your disposal, along with the knowledge of what you have set up in your own HA instance, it is easy to code around any possible issues.
Sorry to say but it’s not enough in case of race condition. Unless it’s guaranteed that all operations are serialized.
Just imagine 2 automations starting at the very same time. And both checks for activity of another one. Two situations might happen: none of them indicates other one, or both do.
Neither situation is correct
As mentioned already serializing is expensive and mostly unjustified. Even databases for which resolving concurrency is daily bread, while providing serializable isolation level, don’t offer it as a default mode. And tbh I cannot recall a single system where serializable mode is in use. though there are other modes with their benefits and artifacts
I should have been more thorough in my answer. In your automations you need to include checks to see if the other automations or scripts are currently running, as needed (since you know what all the code says), to avoid the race condition.
If both automations are triggered by the same trigger and somehow depend on each others actions, why not combine them into one and have precise control over order of execution?
it’s not a question of implementation. It’s just an example of a situation when a race condition happens. You may access the same object by different automation (for various reasons) and make decisions based on that. The object might be a script or entity etc. For example, you cannot (safely) turn on the light only if the light is off. Such automation is clearly vulnerable.
Of course, this is a very naive example, nobody checks a light state prior to toggling it.
In your automations you need to include checks to see if the other automations or scripts are currently running
The whole point of concurrency control is a way how the system ensures that the state of the checked attribute cannot change in the meantime checking process.
The ability to check a state doesn’t provide those guarantees.