runIn not scheduling event

When running the following code from clicking the "Configure" command, it works:

logging("Scheduling refreshChildren...", 1)
runIn(30, "refreshChildren")
runIn(60, "refreshChildrenAgain")
logging("Done scheduling refreshChildren...", 1)

The scheduled events show and run as expected and the log shows the log messages:

When the exact same code is executed when pressing "Save Preferences", I get the log messages in the log, but no scheduled events:

Is this known? Is it expected? Or is it a bug? I have not yet tested this by isolating the code to an otherwise empty driver, but will do so if this is not a known or expected outcome.

I've never seen code that works in configure() not work in updated(). My guess is you have some weird groovy nuance going on that you don't understand or something else that a second set of eyes might help you figure out. Paste the updated() and configure() methods?

I will minimize the code and paste a skeleton driver that can reproduce the issue, if that fails in reproducing then I'll keep looking further in. Pasting my current code is probably not going to help much in narrowing it down, but I can do that as a last resort, thank you for offering to have a look :slight_smile:

I can read code pretty quickly but if you don't want to paste it that's fine. A few things that you should note though...

  • Groovy is weird. I don't know if it matters because it is probably using Java reflection underneath with a String anyway to find that method but you don't need to wrap the method names and pass them as Strings.

  • The runIn command actually takes three parameters; delay in seconds, method, and a map of options. One of the options you can specific is overwrite but it is true by default.

For example, runIn(30, refreshChildren, [overwrite: true]) is the same as runIn(30, refreshChildren, [overwrite: true]). If you want to not overwrite a previous schedule of that method you need to pass overwrite as false.

  • You can also pass data to the method you are going to call by putting a map in the data of the parameter map... e.g.

runIn(30, refreshChildren, [data: [somekey: value]])

I didn't check a working example on that last one but I think it's right. Then the data map is passed to the method when it is called.

  • If you want to unschedule you can call unschedule() or pass unschedule a method aka unschedule(refreshChildren)

It's not that I don't want to paste the code, it's that it 3800 lines or so and I don't want to inflict that pain on anyone. Here is the latest commit in development, does not have the runin part, yet:

I guess I should have done this earlier, but I was lazy and wanted to know if it was a known issue I just didn't know about. I'm happy it wasn't a known issue, just me making a mistake. When going through the code to make the bare minimum driver, I found the issue. A lazy non-specific unschedule call that would happen only in the updated() call stack. I've changed it to only unschedule what it is supposed to do and now all is working as expected.

Thank you for all the detailed information, I do know the above, but there are still many details about the groovy environment on HE I don't know 100%. As for using a string instead of having the method name translated into a string before being stuffed into the scheduler is just a matter of trying to avoid any and all kinds of Groovy "magic" I can if it is not too inconvenient to do so. Without being able to reliably time the code over thousands of iterations in an environment identical to HE I want to at least do that. It is probably futile, but it is what I do, most of the time, I still have parts in the code where I don't and parts where I could do it better, but it's a hobby project and I don't have to hold myself to the same extreme standards as if I got paid to do this. Then there would never be only one pair of eyes on any given piece of code anyway, so most/more things would be caught.

Thanks again!

1 Like

Either work and the definition is calling for a string:

void runIn(Long delayInSeconds, String handlerMethod, Map options = null)

There is no method name verification upon calling runIn in either case. This is probably more of a style choice rather than anything else. Either way of calling results in a string saved for use in the scheduler.
Do you have any other reason other than style? I can't see one, but I'd love to hear about one and be told I'm wrong in my understanding so that I can learn something new about Groovy. Groovy is new to me, programming is not.

All of this Groovy goodness comes at a price in terms of performance. Although not really important for a single call to a method such as updated(), for code with frequent repetition you are always best to strictly type your variables. It avoids the extra overhead of calling castToType() which is Groovy's internal function for "guessing" a specific data type.

I've coded runIn's both ways; with and without quotes. The "best" way is to use quotes since that's the proper way to enclose a string. But as you see, Groovy is able to "guess" that you meant to use a string.

This obviously falls down if you define a like-named variable within the scope of execution..

For example..

def updated()
    def refreshChildren = false
    if (someComparison == true) refreshChildren = true

    //do stuff

    if (refreshChildren) runIn(30, refreshChildren, [data: [somekey: value]])

It might sound obvious, but that call to runIn will pass the Boolean instead of the intended method name. Keeping the method name in quotes prevents this.

Yes, that is something I'm very much aware of, avoiding Groovy goodness as much as I can for the sake of performance. For a call like runIn, which doesn't really matter if it takes a bit more time or not, I wouldn't chase it, but for reasons such as what you talk about I would also say a quoted string is much safer. And if it was frequently called, faster. Being new here I didn't want to go into an argument about this and talked about it as a style choice, but see that maybe was wrong of me.

This has yielded significant performance improvements in my drivers, so it is something I follow as far as it makes sense. I do see most people don't. It's a matter of choice between convenience and performance, as is usually the case. If it is convenient, it is not very performant. This is not always the case, but often.

Definitely agree.

It does that a lot, which is costly in terms of performance. A lot of seemingly innocent calls actually take a lot of time. As I get used to the language I'm sure I'll identify more of these things without using tools to actually analyze the code and the resulting byte-code. Analyzing code like that is not something I will do unless I really need to chase performance, of course. It takes more time. But it is a good way of learning to catch more of the "innocent" calls which actually are rather complex and time consuming.

That is very obvious in an example, but it could have been declared as a property elsewhere if it was real code, so this is a real concern. One more reason for quoted strings.
In some languages the reason to keep the method name in a scheduler would be that you're actually passing a pointer, but that is not the case here, of course. This is also something that needs to get used to if you come from such a language.

I ran into a "fun" thing Groovy does if a property is undefined, which is the case when a Device Setting isn't set. It would then try to use a method starting with the word get and then the name of the property... Having that method defined and checking if that undefined value is null inside that method gives a nice Error 500. It is important to keep track of the goodness of Groovy, that is for certain... That it has goodness that does things like this prompted me to read up some more on the subject, and it will take time to learn and remember all the specifics of the language.

Thank you for your reply! Even if my initial post came from pure laziness and just doing the most rudimentary and most basic call stack tracing solved my problem, this thread was of interest at least to me with posts like yours!