threading and synchronization problem

A newbies question:

Consider the following situation of activating and de-activating an alarm:

  • activating (using some switch): after 60 seconds (using a countdown timer as the trigger) the motion sensors will be armed (so there is time to leave the room).
  • de-activating (using some switch): bypassing the motion sensors and cancelling the countdown timer (for the rare case de-activating occurs within 60 seconds after activation).

Now my Question (probably very unlikely, but just to be sure):
If de-activating occurs at 60 seconds after activation then the following situation could chronologically occur (assuming that the countdown timer, arming the sensors, and bypassing the sensors all have their own thread; say thread t1, t2 and t3):

  1. the countdown timer (thread t1) triggers the code for arming the sensors.
  2. de-activating the alarm cancels the timer and bypasses the sensors (thread t3)
  3. in thread t2 the sensors still get armed

The sensors are in the end unintentionally armed!?
So my guess is i need some mutex (critical section) around the code arming and the code bypassing the sensors.
Anyone any idea how to achieve this, i can’t seem to find this?

Thank you for your help.

In your specific case you can resolve the race condition by attaching the “bypass sensor” actions to the “timer is cancelled” event instead. The logic inside the Countdown Timer plugin has been carefully designed so that these events occur at most once.

In the general case, critical sections are damn near impossible to get right. The LuaUPnP thread scheduling algorithm isn’t available for us to see. All you can rely on is that in UI5, inside a given Lua context the code is single-threaded and non-preemptive, so one function won’t interrupt another in the same plugin. Across plugins, all bets are off. There is no atomic “test-and-set” function in Lua, nor is there much point in spinlocking when there is no guarantee of preemptive scheduling.

What that leaves you with is a very limited toolkit for doing mutual exclusion. You have to get both the Lock and Wait operations to take place in the same Lua context (I.e., the same device). In practice this is never what you want, and it often turns out to be easier to reframe the problem so that the need for a mutex goes away. Like I did in the Countdown Timer plugin, giving it extra events to guarantee atomicity.

Ok, thanks for that clear answer.
If i understand it correctly, mutexing is out of the question (at least outside a device), because the OS does not support it (at least it’s not available for us).
And for the countdown timer, only one of the events will be triggered, either timer cancelled or completed.

But, as far as i can see, attaching the “bypass actions” to the “timer cancelled” event will not solve my problem.
Still it is possible that de-activating the alarm does not trigger the “bypass sensor” actions.
If at the moment i press de-activate, the timer is completed, then the “timer cancelled” event will not be fired (but the “timer completed” will), and the sensors will still get armed.
Or am i missing the point here?

[quote=“dagyliro, post:3, topic:171265”]Still it is possible that de-activating the alarm does not trigger the “bypass sensor” actions.
If at the moment i press de-activate, the timer is completed, then the “timer cancelled” event will not be fired (but the “timer completed” will), and the sensors will still get armed.[/quote]

Yes, that’s true, but this scenario is no different to waiting an hour after the timer completes and trying to press deactivate: the timer has already completed. There will always be a window where the UI hasn’t updated, the photons haven’t reached your eye, the mouse click on “deactivate” hasn’t been registered by the OS, … (Think of it like sniping at an eBay auction.)

By attaching all the consequential actions to the timer (complete or cancel) you can be sure that, collectively, all of your devices are in a consistent state with respect to each other. That’s the bane of race conditions, and all you can really hope to control.

Ok, i think i’m getting the bigger picture.
I was under the impression that the suggestion of attaching the ‘bypass sensor’ actions to the timer cancelled event would somehow solve my problem (i.e. de-activating the alarm but, at least in theory, still ending up with armed sensors).

I am probably still missing something, but even attaching the actions to the timer could end up in a inconsistent state. For example when pressing the activate and de-activate buttons a couple of times at (approximately) the same moment. Then the first timer-cancelled event could interfere with the second timer-complete event?

I think it is to bad that a system (vera) for a real-time environment does not provide the appropriate tooling (semaphore etc.) to handle that environment in a clean way.

The specific chain of events I think you mean is: wait for the timer to complete. The moment it completes (but before its trigger scene can arm the sensors), start the timer anew and then cancel it. Now there are two triggers yet to process: one to arm the sensors, from the completed timer, and one to bypass them, from the cancelled timer.

I think you are right, that this is still a race condition, albeit very hard to make happen in real life.

It would not be a problem if there were a guarantee that triggers are processed in the order received, and that scenes acting on the same device are likewise processed in the order received, but since that logic is inside the closed-source LuaUPnP executable we can’t know.

Yes, you are right, that was the chain of events i meant.
And indeed, in real life it will probably not be a problem.
Do you think that the Lua coroutines could be of any assistance?

Nope. Coroutines are cool but they aren’t useful for synchronization.

As far as i can tell, coroutines can act as atomic operations (i.e. the are not interrupted, at least not without a yield).
In a conceptual way that is the same behavoir as a critical section.

If the ‘arm sensor’ actions would be placed in one coroutine and the ‘bypass sensor’ actions in another, then the inconsistency problem would be solved?
If, in addition, in the ‘bypass sensor’ coroutine a boolean value would be set, and in the ‘arm sensor’ coroutine that value would be checked, then de-activating the alarm would indeed bypass all sensors (and the problem, as mentioned in my first post, would be solved)?
The coroutines could be placed in a dymmy scene, who would then act as a kind of library, as all scenes share the same Lua context.

Perhaps i misunderstand coroutines, but this is what i can make of it.

You could do just the same with regular functions, which run until they return without pre-emption.

Ok, now you got me a bit confused.
So regular functions are just like coroutines, except for the yield/resume?

Do you think the ‘solution’ i suggested (with coroutines or regular functions) in the previous post is correct (or at least does what i imply)?

That’s right: coroutines are just functions that retain their stack when they yield, so that they can be resumed from the same point. They don’t give any extra power to the Lua language. Code that uses coroutines can always be rewritten to not use them. There are just some problems that are more naturally expressed with a yield/resume model than a call/return model.

I don’t know whether moving all the critical stuff to the Scene Controller Lua context will completely close the race condition. It would require guarantees about order of execution within LuaUPnP, which we don’t have.

Ok, thanks for your patience and clarifications.
As you already mentioned, in practice it will probably not be a problem.
I will now try to work this out.

Regular functions run until they return without pre-emption.
Does that also apply for luup code in a scene (luup code not in a function)?

I don’t know for sure, but I think yes.

[quote=“futzle, post:2, topic:171265”]…
The LuaUPnP thread scheduling algorithm isn’t available for us to see. All you can rely on is that in UI5, inside a given Lua context the code is single-threaded and non-preemptive, so one function won’t interrupt another in the same plugin.
…[/quote]

This would be nice if it was true. Unfortunately, I recently found out that luup.variable_watch callbacks are executed in a different thread than other calls within a plugin, and there is no synchronization between the threads. Not sure how MCV expects us to write “correct” code without any mutex support. (guess we just cross our fingers and hope for the best :slight_smile:

Hugh