Prevent a Rule from Running Concurrently With Itself

I have a rule that, when triggered, I don't want to run again until it has completed (i.e. to not run concurrently with itself.

To do this, what I have now, is (snipping out the guts of the rule as its not relevant) as follows:

IF (variable = false) THEN
Set variable to true
Do stuff...
Set variable to false
ENDIF

Where variable is a global variable, initially set to false.

Most of the time, this works. However, sometimes, variable gets stuck set to true. This makes no sense to me, unless the rule is somehow exiting before it completes (I have no such commands in the rule, just a bunch of nested ELSE-IF statements) I can't for the life of me figure out why, sometimes, variable doesn't get set back to false. If enable logging of the actions, but have never caught it happen, but from the logs I've looked over, the rule always seems to complete, all the IFs have matching END-IFs etc. so I've no idea.

Anyway, reading through some posts, it looks like one way to do this (which, from some other posts, perhaps is no longer valid) is to use the rule's private boolean. However those posts show a setting to "Enable / Disable with private boolean". I can't find this setting anywhere, so, I'm guessing it has been deprecated?

As far as I can see, what I've implemented should work, but, as I say, sometimes the global variable gets stuck true. I suppose I could hack it and periodically clear it, but that's horrible practice and would much rather figure out what's actually going on, or if there is a preferred method (private boolean?), switch to using that...

Thanks for any suggestions!

Paul

I would try using a 'Local Variable' as opposed to a Global Variable. I believe there is less overhead involved with a Local Variable, and thus it may improve performance, and help to avoid a race condition if your rule is being triggered rapidly.

The Private Boolean is part of every Rule Machine Rule, and can now be used just like any other local boolean variable. It does have the unique ability to be manipulated by another rule (similar to a Global Variable), but cannot be used outside of Rule Machine, IIRC.

2 Likes

Thanks ogiewon. So the ability to enable disable via Private Boolean is no longer functional?

I'll try that with the local variable. I come from a C++ background, so am probably interpreting what "local" means differently... Is a local variable in RM local to that rule (but common to any instantiation of that rule)? In C++, local variables are local to each instantiation, so if you run the same function twice, each has its own local variables, hence my confusion...

A Local Variable's scope is specific that Rule. If the rule runs concurrently, the same local variable is seen by all instances of that rule.

A global variable can be accessed by all rules, and can even be manipulated by other Apps, like the Dashboard, on the hub using a 'connector', IIRC. See Connect Global Variables to Dashboards

In early versions of Rule Machine, Private Boolean was the only variable available whatsoever. Rules had special features that were tied to the Private Boolean variable. As you've noted, it could be used to restrict the running of a rule, by having other rules manipulate a rule's PB.

These days, you can still use a PB to restrict the running of a rule, by simply using it as a Conditional Action as demonstrated below.

2 Likes

Thanks for the clarification and example, that's very much clearer now!

I've switched to a local variable, so fingers crossed that helps...

1 Like

I would also like to clarify that this won't prevent the rule from triggering. The rule will still trigger but the actions won't run because you have contained all the actions in a conditional statement that evaluates to false.

1 Like

Right, understood.

As I understand it, that is what using Private Boolean used to do (actually disable the rule), but that is no longer possible, so gating the actions within a conditional statement is the only option.

Right. But when a rule re-triggers, some things happen regardless of whether the rule does anything. For example, any delays get canceled. But waits are not. You can read about how I learned this here.

So basically for a rule that you want to delay an action, but the rule will be retriggered a bunch (like by motion in my case) you can't say off --> delayed 0:02:00 because when the rule retriggers, the delay gets canceled and the light never turns off.

So instead, you have to say wait for event --> elapsed time 0:02:00 because waits are not canceled.

1 Like

Ah, I see, that's good to know, thank you.

You have that backwards. Any Waits (thus subsequent actions) are cancelled when a rule is retriggered. Delays are only canceled if the option to cancel them is selected and a Cancel Delayed Action (or Cancel Timed Action) is executed.

4 Likes

You haven’t shown us your triggers. If there is event information in each trigger instance that you want you use within the (you believe, exclusive access portion of the) rule, there could be a problem.

I haven’t seen any documentation of the scheduler’s effects on Rule Machine, or whether it runs each event to completion or block. My gut feeling is that there must be some sort of round-robin scheduling, otherwise hub lockups would be common. Because there is no mutex operator on the variable you are using to test (Private Boolean, Local variable, whatever), there is still a rescheduling opportunity between the IF quoted above and the SET variable to True, during which a trigger may cause context switch, and another process instantiation may enter and test the variable before it has been set True. That will end up with two processes inside the IF block.

I believe that the only way to do this properly (at present) is in Groovy, where some mutex operators are available that are not accessible in RM. An approach might be to throw the critical regions down into a driver with an Actuator command, or some such.

The need for mutex operators has been discussed before.

1 Like

Ah, now we might be getting down to the problem with my original rule. It does contain a wait (on an event completing), so if this is correct, and if the rule is re-triggered, and therefore any actions in a rule after a wait are cancelled, that would explain why the flag gets stuck (Set variable to false at the end of the rule doesn't get called). OK, well, that at least would explain how it was getting stuck, so that now makes sense and is one mystery (to me) solved.

1 Like

Yeah, I'd pondered that thought (moving this rule into a driver). I'm trying to avoid that, but as true mutexes aren't (currently) available in RM, I may ultimately have no choice. It looks like the old way Private Boolean worked, was effectively a mutex for the rule; set it to false at the beginning of the rule, and to true at the end, though I don't know if that would prevent the rule from running altogether (setting it to false and disabling the rule stops it from executing in that instance).

Perhaps if you shared your entire rule as a screenshot, the community could offer some advice? Often, as programmers we overthink and overanalyze things, convincing ourselves we have an issue. 98% of users here have no clue what a mutex or semaphore is, and yet their rules work perfectly. :wink:

2 Likes

Most of the time ...

1 Like

OK, well, here goes, this thing isn't for the feint hearted, as its a bit of a monster as I'm trying to do something unconventional (i.e. probably silly), but its what I want, and it works (other than the issue being discussed here).

OK, what I'm doing is controlling a series of virtual RGBW bulbs (the VB devices) which I use with a SharpTools dashboard to display the lighting level in a room. The buttons on the dashboard also control the level. Hence why I need to stop certain rules running concurrently. This rule is triggered when the level (or colour) changes. It then turns the appropriate virtual bulbs on or off (e.g. if the level is 50%, the night, 10%, 25% and 50% virtual bulbs turn on). Its neat, clear to the wife and works.

But... I also have to have rules triggered when the virtual bulbs are turned on. So, when this rule is running, those rules cannot (otherwise we go around in a big circle), hence the flag to tell which rule what is going on.

All the logic works after several weeks of fiddling. The exception is this one, because it is triggered by the level changing. The level changes slowly (deliberately), 8 seconds from 0% to 100% for example, so the rule triggers multiple times.

So, that's what it does (and it does work). Here's the rule:




See, I warned you, a monster...

1 Like

Well, switching to a local variable didn't work sadly...

Private Boolean can still be used for this purpose. The only thing that changed is that Restrictions went away. If the intended restriction is disable when PB is false, then first action would be Simple Conditional:

IF(PB false) Exit Rule

Of course, something has to set it to true again; it can be set to true at the end of the actions. But, that could not be after a Wait.

From my perspective you're asking for trouble with this rule for these reasons: multiple triggers using changed followed by conditional logic testing the states of those devices; high level of complexity. I would seriously consider breaking this into separate simpler rules with specific triggers, and not used changed at all. By using changed you are throwing away information only to then retest the state in a complex logic tree.

2 Likes

Whoops. lol. I always get it backwards and then I think to myself, "no, that's backwards." and actually it was the right way. I try to stay away from waits and delays in rules that can be triggered again before the rule can complete when I can help it. To confusing.

Wow! You might want consider writing a custom Groovy app for something this complicated.

Oh, and please make sure you routinely download a backup of your hub (as a form of source code control.) I’d hate for you to have to manually rebuild this rule from scratch.

2 Likes