[SOLVED] Random ghosting of lights when Away
-
gwp1replied to toggledbits on May 28, 2021, 3:48 PM last edited by gwp1 May 28, 2021, 11:50 AM
@toggledbits This is on me for not being clear: the screenshot in my reply was of MY system - I wanted someone else's eyes on it as I've stared at it so long any obvious error will be lost to me at this point.
If mine IS correct then this takes me back around to wondering what I've broken elsewhere in this that the cycler isn't kicking at no more than 1 1/2 minutes but, rather, taking 12 minutes.
-
LibraSunreplied to gwp1 on May 28, 2021, 3:54 PM last edited by LibraSun May 28, 2021, 11:55 AM
@gwp1 said in Random ghosting of lights when Away:
It just took 00:12:00 to cycle and I've been staring at this 'til I'm cross-eyed.
Weird. From your screenshot, I definitely would have expected a Delay time of 00:30 to 01:30, not twelve minutes! And you're certain none of your Trigger conditions have a "Delay" or "Latch" or really long "Pulse" condition?
FYI, in case it matters, where you increment your Cycle Timer by 1, if you don't want that number to grow indefinitely large over time, consider:
${{ (cycler_stim + 1) % 100 }}
-
@librasun No "delay", "latch", or "pulse" conditions. I'm not strong in expressions so my path forward is usually to emulate EXACTLY what the sample shows... then work backwards poking, editing (re: breaking) until I understand it better.
-
@gwp1 Agreed. Smart approach! I should have notated on my example that the
%
operator stands formodulo
which in MSR (as with most modern programming languages) yields the "remainder" after division by the whole number on the right side.Thus, this would have the effect of keeping your
cycler_stim
variable from ever growing past 99. Hardly important; you may even prefer it the way it current behaves, growing by1
indefinitely (which acts as a sort of historical counter). -
@librasun said in Random ghosting of lights when Away:
${{ (cycler_stim + 1) % 100 }}
Careful here... this will produce a 0 after 100 iterations and stop cycling, because
cycler_stim==0
is used as "not cycling/deactivated". -
@toggledbits Good catch. I wasn't going down this path any time soon - but - as I said, I poke at things I'm new at and prob would have at some point.
-
toggledbitsreplied to gwp1 on May 28, 2021, 6:55 PM last edited by toggledbits May 28, 2021, 3:01 PM
@gwp1 I was playing with this a bit more. Timing-wise it works fine for me but I did notice occasionally it would stall. This seems to be a race condition between the way the Set reaction's group executes vs the reevaluation of the Cycler rule. The purpose of the group is to stop cycling when deactivated, so we can do this differently and get rid of the race:
New "Deactivated":
And then remove the Group from "Cycler" and do the "Set Variable" directly:
Detail of the problem/race: Groups in Reactions are queued as sub-reactions of the parent reaction. The parent blocks in the execution queue while the child group runs, and when the child group finishes, the parent is allowed to resume. But these are, in effect, two different threads of execution. As originally offered, the child thread can finish and the parent, having nothing more to do (since the child is the last thing), signals completion and is removed from the queue. The problem comes from the "Set Variable" requesting a re-evaluation inside the subgroup. Once the subgroup thread stops, it is likely, but not guaranteed, that the parent reaction will get control next; when it doesn't, it's the evaluation that occurs, and when this happens, it sees cycler_stim has changed and is non-zero, and tries to queue the Set reaction. But a reaction can only be queued once: if a reaction is already executing, it will not be queued again. So, the evaluation succeeds as expected, but can't queue the next run of Set reaction because the prior run, which is effectively at its end but hasn't declared itself finished yet, is still on the queue. So the new Set doesn't get queued because the old one is there. The old one, with nothing more to do, finally gets to execute again and declares itself finished, and at that point, no reactions are queued and the process stalls.
The fix: By making the stop of the Cycler Set reaction explicit, we ensure that it stops when needed and is removed from the queue. By removing the "Set Variable" from the group (and removing the group entirely), its update of cycler_stim is done in the main reaction thread, which finishes before the re-evaluation is allowed to begin, thus allowing the re-evaluation to queue the Set reaction again as it must.
It's worth noting that this is a side-effect of writing a rule and reaction that attempts to perpetuate itself, to run in an effectively infinite loop. In that, it works against some internal design choices meant to prevent such behaviors that might otherwise be "runaway" in normal circumstances.
-
One of the most tantalizing, yet ultimately frustrating, aspects of MSR is its potential use for "looping" of this exact type. I think by now we've all taken a stab at it (I know I have), but many of those attempts run into a wall which I call "chicken or the egg".
That is, on the one hand, you have to somehow "bootstrap" a looping Rule so that its Trigger conditions get it going ... but on the other hand, those conditions must not simply remain "true" or else the Rule will not loop at all. So the user is forced down one or more alleyways involving "pulse" or "interval" or revolving variable values, or -- as in this above example -- externally launching and un-launching the Rule from another rule. Whichever method you choose (I've tried them all, lol), there are potential stumbling blocks along the way.
Few, if any, users can "see" all of those prospective hazards ahead of time. MSR in some instances is designed to push back against looping: Do it too quickly, and throttling kicks in. Do it without careful synchronization, and child processes get tripped up (as has happened here). Do it with callous disregard for memory-hogging subroutines and you can bog the system down to a halt.
Ask me how I know.
By now, I think I've made every possible mistake one can make in MSR -- always my own fault -- and nearly every time I've botched a "looping Rule" I have had to fight the impulse to ask @toggledbits for a formalized "Loop" action within reactions. Like, "Why can't we have a DO..WHILE crutch here?"
Then I think, no, it's best we don't. Because (a) we're talking about a fairly advanced construct here, that (b) probably does not belong in the hands of beginning users, and (c) looping by its very nature stands at odds with an engine designed to carry out deterministic procedures in a synchronous, queued fashion.
NOTE: It's not coincidental that a Reaction's ability to directly re-run itself was removed early on in the development of MSR!
I know I'm waxing philosophical here. And I'm sure we'll all carry on crafting custom loops of one type or another -- from the simplest
each
/in
enumerator withperformAction()
buried inside, to grandioseRule A
►Rule B
behemoths. For me, the fun of it will forever be the question of "How?" -
@librasun And to think all this waxing philosophical is due to my silly ask to ghost lights in the house
Thank you both for you wisdom and infinite patience!
Thank you @toggledbits for not just providing a path but explaining the path so I (and others) can learn -- really appreciated.
-
@toggledbits IT LIVES!! Set to 00:00:05 - 00:00:10 for testing and lights are cycling randomly as expected.
-
You, sir, are living the dream.
-
-
-
-
34/37