Rules and concurrency

Here I do the test for you:

in Main UI Settings → Scripts I use the scratchpad script, in jruby

logger.info "shared cache test: #{shared_cache["test1"]}"
shared_cache['test1'] = Time.now.to_s

Resulting log:

  • When I saved the script, you’ll see Rule 'scratchpad' has been updated which also means the cache gets removed since it’s the only script accessing the cache key.
  • The first one is obviously empty since it hasn’t been assigned yet
  • Subsequent executions show that it retained the previous value
13:27:56.160 [INFO ] [openhab.event.RuleUpdatedEvent       ] - Rule 'scratchpad' has been updated.
13:28:07.245 [INFO ] [tion.jrubyscripting.script.scratchpad] - shared cache test: 
13:28:12.551 [INFO ] [tion.jrubyscripting.script.scratchpad] - shared cache test: 2025-10-05 13:28:07 +1000
13:28:17.133 [INFO ] [tion.jrubyscripting.script.scratchpad] - shared cache test: 2025-10-05 13:28:12 +1000
13:28:23.491 [INFO ] [openhab.event.RuleUpdatedEvent       ] - Rule 'scratchpad' has been updated.
13:28:26.454 [INFO ] [tion.jrubyscripting.script.scratchpad] - shared cache test: 

How do you “unload” this script so that the cache entry is deleted? By deleting it?

As I said above - when you saved the script (any script - rule, transformation, scene, etc), the old one is unloaded first, then the new version is loaded.

Either you press Cmd+S / Ctrl+S or click “Save” in the top righthand corner.

But that is a manual operation. Do you mean that users should open and resave the scripts to “clean up” the cache? My production system runs for months at a time, some very old value might not be very relevant. I don’t use any cached values today, so I’m not sure exactly when they are useful, but I assume that they are most useful if the previous value was stored as part of some ongoing process that don’t span over months. Which is why I thought that a TTL might have some use cases to prevent using stale information.

It is up to you how you want to implement the logic, but you could remove the cache programmatically if you deem that to be the reasonable action.

https://www.openhab.org/javadoc/latest/org/openhab/core/automation/module/script/rulesupport/shared/valuecache#remove(java.lang.String)

You can do the same in jruby, just do shared_cache.delete("key"). jsscripting uses cache.shared.remove("key").

Yes, I know that you can remove an entry. My idea was that maybe you don’t know when the script runs that it won’t be used again for potentially a long time, so that it’s difficult to know when to remove it. In such cases, setting a TTL for the entry might be a more manageable way to handle it. I never meant for it to be mandatory, to apply to all cache entries, only that you could optionally specify a TTL if you wanted to, on a per key/entry basis.

edit: But it’s not something I have “strong feelings for”, it was just an idea I got, that this might be useful in certain scenarios. If nobody think so, forget about it.

That’s a coding logic problem that you as the coder need to figure out / implement.

A built in TTL is a horrible way to do it, especially if it’s enabled by default. Adding this feature is an unnecessary bloat to core.

You might want TTL but what if I expect the value to be stored regardless of how long it has been? Say I want to keep track of when a truck passed by and they come once a week or once a month?

If you want TTL you can implement it yourself in your script.

Again, I never suggested a general TTL on cache entries. My idea was that maybe it was useful if you could, optionally, specify a TTL for entries where you wanted a TTL. If you didn’t specify one when you stored it, it would “live forever”.

To elaborate on the two possible extra shared cache implementations I had in mind, I’ve created:

I get that it might not be so easy to understand what I mean, so I figured that a “pseudo implementation” might do a better job at it.

Does anybody think that either of these would have any value? It would be in addition to the existing caches, ways to achieve some of the things that are currently done “unsafely” with the current cache, in a “safe” way that won’t randomly fail.

Sure, but so do the transformations. If I store two values per transformation in the cache and I use the transformation three different places, those six values will remain in the cache. But I resuse the same keys so it’s just six entries no matter how many times the transform is invoked.

A TTL would break my transformations that do this though and I would be very much against it.

Assuming that’s true (it’s been a long time since I’ve tested), there’s no way to access that in a non-scrpt action and that’s where you really need it. If you have a script action, you can just call the action directly in the script.

Yes, it works exactly as @jimtng describes in the UI too.

delete it or disable it or save it

I can’t really think of any scenario where the cache would need to be cleaned up in the first place. But if one needs to remove stuff from the cache they can remove it from a the rule by calling remove or do any of the manual operations listed above.

The cache behaves like a map. it will only grow uncontrollably if you use random keys. And if you do that there’s no point because you could never retrieve the cached value again when you need it.

Those values almost certainly get replaced with new values nearly every time the rule runs. And if the role can run without the cached value because it’s old and stale, it probably doesn’t need to cache the value in the first place.

Nobody seem to register that I suggested an optional TTL. That is, you’d have to specify the TTL value when you stored the value for it to have a TTL.

There’s no way because how would you make the non-script action able to handle “arbitrary inputs”?

Not sure I follow here, do you mean that you would just run “the first action” again? That sounds wasteful, and might not always give the desired result either (running that action twice). So, maybe what you’re saying is that the rule would just have one action then, where “the first action” just become a part of “the second action”?

I wasn’t so much worried that the cache size would become a problem as that it might contain stale/outdated information. But, since I don’t use this myself, maybe I’m just imagining problems that aren’t really there in practice. My idea is that you probably have some “special handling” for when the value don’t exist in the cache, you do some kind of initialization/sets it to an initial value. Maybe, if this hadn’t run for a long time, it would be desirable that the same initialization was performed, instead of an old/stale and perhaps irrelevant value was assumed to be current and treated as that.

Yes, but maybe the rule hasn’t run for a long time because of external factors/that it hasn’t been triggered.

The rule must be able to handle if the cache doesn’t have the value, otherwise it would never work “the first time”.

And yet that’s pretty much the only place that this would be a useful feature.

If you have script action, it’s easier to put everything in the script action. There’s no point in passing data between script actions.

But script actions are only one type of action. All Thing Actions and all the built in actions can be called as a rule’s actions too.

These are the only uses where being able to access data passed from the previous action is useful.

I’m not at a computer so it’s really frustrating trying to explain what’s really apparent to those of us who actually use theses features every day. It’s like teaching someone to ride a bike but I have to argue about why the tires have to be round. That’s a whole lot easier with a keyboard.

If I want to call a Thing Action from a JS Script action, I’ll just call it. It’s a one liner in the code. Being able get a value from another rule’s action has zero utility here.

Where it can have utility is for rules that do not have any Script Actions. These rules have one or more actions that directly call the actions (e.g. sendNotification) without any code at all. No JS, no Rules DSL, no script at all.

Then, like @jimtng said, add some logic to your script if the age of the data matters. I’d add a timestamp to the cache and just check that. I can’t really think of a case right now where that would be a problem but I guess anything is possible. It seems to me either the data is good no matter how old it is, or you should probably just recalculate it every time the rule runs and not use the cache in the first place.

True and it’s almost always an initialization. If there’s no value, start with zero or the current value. If there’s no TimerMgr, create one. No timestamp, use now or sometime way in the past or way in the future.

99% of the time I use the cache, it’s to hold timers (I only have one case at all where I use the shared cache). The other 1% it’s too hold static data that doesn’t change after it’s initialized. I’ve never encountered a case not can I think of a case that makes practical (as opposed to merely theoretical) sense where the age of the data in the cache means anything at all.

Ok, maybe it’s useless then. It’s there anyway, but exactly how it links inputs/outputs between actions isn’t clear to me.

I understand that generally, I’d guess that having multiple actions in a rule is relatively rare in itself. But, if you imagine that there was an action that couldn’t easily be executed from a script, and that could return some value. This was what I thought we were discussing anyway, and in such a case, would you think that it’s pointless to pass on the action because you’d rather somehow “execute” the action directly from the script, and then get the value from there? Or do you just mean that it isn’t relevant because such an action doesn’t currently exist?

I’m not sure if that’s where the problem lies in this case, I think we’re simply imagining difference scenarios. I understand that you can launch Thing Actions from scripts, and generally do what any other pre-made action that exists today does. I was thinking if there were some reason to have more than once action when one of them is a script in the first place, if it wouldn’t then potentially be useful to be able to acquire the return value of the non-script action, if that ran first.

I understand that it can be solved other ways, but it would be much easier for specify a TTL value when you store the value in the cache than to add a separate timestamp and do all that. That was what I was thinking of, but if there’s no use case for such logic, it’s probably pointless.

I don’t think such an action is possible. From a Script, an action is a function call with zero to n arguments. I can see no possible way any action that couldn’t be run from a script or even harder to run from a script. And if there were, that action would be next to useless because the only other place you could call it you’d only be able to use hard coded arguments. No Item states, no triggering event, nothing but constants.

That’s not an “if” that can exist now nor in the foreseeable future. Actions are first and foremost created to be executed from a Script. It’s their whole reason for being. They can be used as a script actions but they are not so useful there because you can’t pass and use data returned from one action to the next nor can you use any data in the event that triggered the rule nor any states from Items. Only heard coded strings and numbers.

But because actions are created to be executed from Scripts, there will never be a case that an action exists that is easier to run outside a Script without rearchitecting how actions work and their purpose.

You’re losing me a bit here, but I’m not sure if it matters. Unfortunately, there are two concepts called “actions” in OH Action which extends Module, and ThingActions which extends ThingHandlerService. The JavaDocs for ThingActions states:

Marker interface for Automation Actions with access to a ThingHandler.

I’m still confused by this since it implies that there’s a “parent concept” “Automation Action”, but I can’t really find that anywhere. I can’t find any relation between Action and ThingActions either.

Just to be clear, when I refer to “action”/“Action”, I mean Action as in the module type, which is a concept that is a part of a rule. If I refer to ThingActions, I will say “thing action” or similar.

When I read the above, I’m unsure if you’re talking about one or the other. I wouldn’t think that it would be so easy to “invoke” an Action from a script, because you’d probably have to first find the rule and then the Action instance in question. From there I’m not sure what you would do, because as far as I understand, Actions are usually executed by RuleEngine. Doing this is probably similar to what you do when you “run another rule”, but I thought that included running all actions of that rule, not a particular one.

Even more confusing, in the REST API, /actions/ are actually ThingActions.

So, are we talking about different things?

This is what I mean about trying to argue the physics of why tires need to be round. You have no practical experience with how these actually work, how they are presented to the end user, what these even are.

It matters tremendously.

These are functions that can be called from a Script to cause OH to do something. Thing Actions are provided by Things. For example MQTT provides a publushMessage() action that lets one publish a message to an MQTT topic from a rule.

There’s are also built in Actions. createTimer() is a built in automation action provided by core. There are others.

All of these are Automation Actions. I don’t know if there is an interface or class . It’s just a concept.

Both, because a Thing Action can be used as a Rule Action.

But really but you didn’t yet have an understanding on how this all works which comes from the fact that you’ve only looked at code and made assumptions instead of actually using them.

It is unfortunate OH uses the same word in multiple different ways. Script is another case of an overloaded term.

There are these entities called Automation Actions. Thing Actions are one instance but there are other Automation Actions. Automation Actions are made to be called from rules.

Rules can have one or more Rule Actions. An Automation Action can be used as a Rule Action.

The only place where it makes sense for one rule action to return a value that can be used by the next rule action is when they are all Automation Actions. This is what I’m talking about. When Automation Actions are used directly as Rule Actions.

When using Script Actions, none of this is relevant because you can just call the Automation Actions directly in the Script Action. And it doesn’t really make sense to have more than one Script Action on a single rule.

I don’t really get the frustration. Yes, I have little to no experience in OH scripting because I haven’t needed it (except for my pretty basic Rules DSL scripts). That doesn’t mean that I have no knowledge of other aspects. But, if you just tell me what you mean, I can usually figure out what it’s about within a reasonable time-frame.

What I meant with “I’m not sure if it matters” was that it seems like we’ve established that there’s no “known” use-case for transferring outputs and inputs between (rule) Actions, so maybe we don’t have to go all the way down this road with “aligning concepts”.

First of all, I know what ThinkActions are, I just have never found any relation between them and Actions. I’ve written a binding where I’ve created several ThingActions, so I also know how they can be used. I also know that you can execute ThingActions from Actions, but that doesn’t mean that they’re the same thing, so we still need to agree what we’re talking about for it to make any sense.

I’ve found the “Automation Actions” now, they are defined in the org.openhab.core.model.script bundle (not one I usually open, also because it’s one of relatively few packages that starts an endless build loop in Eclipse if you open them).

So, if I were to call these anything, I’d rather call them “script actions”, since they are clearly in the “scripts domain”. From what I can see, they aren’t really structured very much, they share no interface or similar, but are basically isolated methods/functions that are annotated with @ActionDoc. @ActionDoc seems to me to be the one way you can identify them, apart from the name of the package, but I can find a few that are outside said package, like WebPushNotificationAction and NotificationAction.

There seems to be another confusing factor here, most of these seems to be implemented in two different ways. I’m guessing that there’s an “old” and “new” way of doing things, or something like that. From writing the ThingActions I think there was something similar, that you defined “actions” one way for Rules DSL and one way for the UI (and probably many scripting languages). The “second way” seems to be to implement the interface ActionService. The methods/functions might not necessarily map 100% between the “new” and the “old” way, so there’s another potential confusion.

All the “script actions” that I’ve looked at returns something, but I have no idea if using a rule Action to execute a “script action” will map the return value of the “script action” to the outputs of that Action. But, we’ve pretty much established that it doesn’t matter anyway, haven’t we?

When using the UI to create an Action, I do see that ThingActions show up as choices, along with a few other “standard actions” under “core”:

Most of the “script actions” don’t show up there though, so the relation between them is still quite fuzzy to me.

I do what I can with the information I have, that’s true, but isn’t that true for most people? I’m sorry if it’s very frustrating that I haven’t done extensive scripting, but there’s not much I can do about it. I’d think I would still be able to contribute even if I lack this experience.

:+1: You would need something pretty close to magic to somehow map the outputs from one Action to function parameters of a “script action”, yes.

edit: Maybe that’s exactly what the mythical input mapping in the rules does - decide what output from another module maps to what “parameter” for the Action in quesstion, if that’s a “script action”?

It’s a real shame with the overlap of the terms, it doesn’t make this any easier.

That’s what I’ve been trying to do this whole time.

I don’t have anything meaningful to contribute to the discussion other than to say that while a significant amount of this discussion went over my head due to a lack of domain awareness on my part, that I’m truly grateful for the discussion from everyone who participated here. OpenHAB is well loved, and I’m really appreciative that despite some apparent points of contention between parties, the discussion here seemed to remain very productive and had some beneficial outputs which contributed to documentation improvements. This is a true testament to the amount that OpenHAB is so well loved. I just want to thank everyone for having this discussion in the open where I could read along and catch some of the context.

9 Likes

Interestingly, while experimenting with a rule, I seem to have managed to provoke “an error” (or a situation it can’t figure out) in the automatic mapping of inputs and outputs:

Validation of rule dd46e76c57 has failed! Required input “offset” of the condition “3” not connected

I’ll try to dig into this later, it might reveal further pieces of this puzzle (for me at least). I realize that I might be the only one that find this “puzzle” interesting at all :wink:

1 Like