From 7d380094cc76b6b03b4d17a26c4ff73500575032 Mon Sep 17 00:00:00 2001 From: Ravi Nadahar Date: Sat, 11 Oct 2025 02:14:16 +0200 Subject: [PATCH] Make RuleManager.runNow() run in the dedicated rule thread Signed-off-by: Ravi Nadahar --- .../automation/internal/RuleEngineImpl.java | 105 +++++++++++------- 1 file changed, 67 insertions(+), 38 deletions(-) diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/RuleEngineImpl.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/RuleEngineImpl.java index 686e446f4ce..37830722c71 100644 --- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/RuleEngineImpl.java +++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/RuleEngineImpl.java @@ -26,6 +26,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; @@ -168,7 +169,7 @@ public class RuleEngineImpl implements RuleManager, RegistryChangeListener moduleTypes) { * @param td {@link TriggerData} object containing new values for {@link Trigger}'s {@link Output}s */ protected void runRule(String ruleUID, TriggerHandlerCallbackImpl.TriggerData td) { - if (thCallbacks.get(ruleUID) == null) { - // the rule was unregistered - return; - } - if (!started) { - logger.debug("Rule engine not yet started - not executing rule '{}'", ruleUID); - return; - } synchronized (this) { + if (thCallbacks.get(ruleUID) == null) { + // the rule was unregistered + return; + } + if (!started) { + logger.debug("Rule engine not yet started - not executing rule '{}'", ruleUID); + return; + } + final RuleStatus ruleStatus = getRuleStatus(ruleUID); if (ruleStatus != null && ruleStatus != RuleStatus.IDLE) { logger.error("Failed to execute rule ‘{}' with status '{}'", ruleUID, ruleStatus.name()); @@ -1096,41 +1099,67 @@ protected void runRule(String ruleUID, TriggerHandlerCallbackImpl.TriggerData td @Override public Map runNow(String ruleUID, boolean considerConditions, @Nullable Map context) { - Map returnContext = new HashMap<>(); final WrappedRule rule = getManagedRule(ruleUID); if (rule == null) { logger.warn("Failed to execute rule '{}': Invalid Rule UID", ruleUID); - return returnContext; + return Map.of(); } + + TriggerHandlerCallbackImpl thCallback; synchronized (this) { - final RuleStatus ruleStatus = getRuleStatus(ruleUID); - if (ruleStatus != null && ruleStatus != RuleStatus.IDLE) { - logger.error("Failed to execute rule ‘{}' with status '{}'", ruleUID, ruleStatus.name()); - return returnContext; + thCallback = thCallbacks.get(ruleUID); + } + if (thCallback == null) { + RuleStatus ruleStatus; + synchronized (this) { + ruleStatus = getRuleStatus(ruleUID); } - // change state to RUNNING - setStatus(ruleUID, new RuleStatusInfo(RuleStatus.RUNNING)); + logger.error("Failed to execute rule '{}' with status '{}': could not acquire rule thread", ruleUID, + ruleStatus == null ? "UNKNOWN" : ruleStatus.name()); + return Map.of(); } - try { - clearContext(ruleUID); - if (context != null && !context.isEmpty()) { - getContext(ruleUID, null).putAll(context); + + Future> future = thCallback.getScheduler().submit(() -> { + Map returnContext = new HashMap<>(); + synchronized (this) { + final RuleStatus ruleStatus = getRuleStatus(ruleUID); + if (ruleStatus != null && ruleStatus != RuleStatus.IDLE) { + logger.error("Failed to execute rule '{}' with status '{}'", ruleUID, ruleStatus.name()); + return Map.of(); + } + // change state to RUNNING + setStatus(ruleUID, new RuleStatusInfo(RuleStatus.RUNNING)); } - if (!considerConditions || calculateConditions(rule)) { - executeActions(rule, false); + try { + clearContext(ruleUID); + if (context != null && !context.isEmpty()) { + getContext(ruleUID, null).putAll(context); + } + if (!considerConditions || calculateConditions(rule)) { + executeActions(rule, false); + } + logger.debug("The rule '{}' is executed.", ruleUID); + returnContext.putAll(getContext(ruleUID, null)); + } catch (Throwable t) { + logger.error("Failed to execute rule '{}': ", ruleUID, t); } - logger.debug("The rule '{}' is executed.", ruleUID); - returnContext.putAll(getContext(ruleUID, null)); - } catch (Throwable t) { - logger.error("Failed to execute rule '{}': ", ruleUID, t); - } - // change state to IDLE only if the rule has not been DISABLED. - synchronized (this) { - if (getRuleStatus(ruleUID) == RuleStatus.RUNNING) { - setStatus(ruleUID, new RuleStatusInfo(RuleStatus.IDLE)); + // change state to IDLE only if the rule has not been DISABLED. + synchronized (this) { + if (getRuleStatus(ruleUID) == RuleStatus.RUNNING) { + setStatus(ruleUID, new RuleStatusInfo(RuleStatus.IDLE)); + } } - } - return returnContext; + return returnContext; + }); + try { + return future.get(); + } catch (InterruptedException e) { + logger.warn("Interrupted while waiting for rule '{}' to execute, attempting to cancel execution", ruleUID); + future.cancel(true); + } catch (ExecutionException e) { + logger.error("Failed to execute rule '{}': ", ruleUID, e); + } + return Map.of(); } @Override