diff --git a/core/src/main/java/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java b/core/src/main/java/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java index 7d8dfb1c10..9e4facbfc7 100644 --- a/core/src/main/java/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java +++ b/core/src/main/java/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java @@ -1215,6 +1215,42 @@ private Collection transformReactorNetwork( } } + // Connect watchdogs + Set watchdogs = new HashSet<>(); + watchdogs.addAll(watchdogSources.keySet()); + watchdogs.addAll(watchdogDestinations.keySet()); + + for (WatchdogInstance watchdog : watchdogs) { + KNode node = associateWith(_kNodeExtensions.createNode(), watchdog.getDefinition()); + NamedInstanceUtil.linkInstance(node, watchdog); + _utilityExtensions.setID(node, watchdog.uniqueID()); + nodes.add(node); + setLayoutOption(node, CoreOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + Pair ports = _linguaFrancaShapeExtensions.addWatchdogFigureAndPorts(node); + setAnnotatedLayoutOptions(watchdog.getDefinition(), node); + if (watchdog.getTimeout() != null) { + _kLabelExtensions.addOutsideBottomCenteredNodeLabel( + node, String.format("timeout: %s", watchdog.getTimeout().toString()), 7); + } + Set> iterSet = + watchdog.effects != null ? watchdog.effects : new HashSet<>(); + for (TriggerInstance effect : iterSet) { + if (effect instanceof ActionInstance) { + actionSources.put((ActionInstance) effect, ports.getValue()); + } + } + + // connect source + for (KPort source : watchdogSources.get(watchdog)) { + connect(createDelayEdge(watchdog), source, ports.getKey()); + } + + // connect targets + for (KPort target : watchdogDestinations.get(watchdog)) { + connect(createDelayEdge(watchdog), ports.getValue(), target); + } + } + // Connect actions Set actions = new HashSet<>(); actions.addAll(actionSources.keySet()); @@ -1258,34 +1294,6 @@ private Collection transformReactorNetwork( } } - // Connect watchdogs - Set watchdogs = new HashSet<>(); - watchdogs.addAll(watchdogSources.keySet()); - watchdogs.addAll(watchdogDestinations.keySet()); - - for (WatchdogInstance watchdog : watchdogs) { - KNode node = associateWith(_kNodeExtensions.createNode(), watchdog.getDefinition()); - NamedInstanceUtil.linkInstance(node, watchdog); - _utilityExtensions.setID(node, watchdog.uniqueID()); - nodes.add(node); - setLayoutOption(node, CoreOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); - Pair ports = _linguaFrancaShapeExtensions.addWatchdogFigureAndPorts(node); - setAnnotatedLayoutOptions(watchdog.getDefinition(), node); - if (watchdog.getTimeout() != null) { - _kLabelExtensions.addOutsideBottomCenteredNodeLabel( - node, String.format("timeout: %s", watchdog.getTimeout().toString()), 7); - } - // connect source - for (KPort source : watchdogSources.get(watchdog)) { - connect(createDelayEdge(watchdog), source, ports.getKey()); - } - - // connect targets - for (KPort target : watchdogDestinations.get(watchdog)) { - connect(createDelayEdge(watchdog), ports.getValue(), target); - } - } - // Transform connections. // First, collect all the source ports. List sourcePorts = new LinkedList<>(reactorInstance.inputs); diff --git a/core/src/main/java/org/lflang/generator/WatchdogInstance.java b/core/src/main/java/org/lflang/generator/WatchdogInstance.java index 2abc7c166f..73abe02b75 100644 --- a/core/src/main/java/org/lflang/generator/WatchdogInstance.java +++ b/core/src/main/java/org/lflang/generator/WatchdogInstance.java @@ -8,14 +8,19 @@ */ package org.lflang.generator; +import java.util.LinkedHashSet; +import java.util.Set; import org.lflang.TimeValue; +import org.lflang.lf.Action; +import org.lflang.lf.VarRef; +import org.lflang.lf.Variable; import org.lflang.lf.Watchdog; /** * Instance of a watchdog. Upon creation the actual delay is converted into a proper time value. If * a parameter is referenced, it is looked up in the given (grand)parent reactor instance. * - * @author{Benjamin Asch } + * @author Benjamin Asch */ public class WatchdogInstance extends TriggerInstance { @@ -30,9 +35,18 @@ public WatchdogInstance(Watchdog definition, ReactorInstance reactor) { this.timeout = TimeValue.ZERO; } - this.name = definition.getName().toString(); + this.name = definition.getName(); this.definition = definition; this.reactor = reactor; + for (VarRef effect : definition.getEffects()) { + Variable variable = effect.getVariable(); + if (variable instanceof Action) { + // Effect is an Action. + var actionInstance = reactor.lookupActionInstance((Action) variable); + if (actionInstance != null) this.effects.add(actionInstance); + } + // Otherwise, do nothing (effect is either a mode or an unresolved reference). + } } ////////////////////////////////////////////////////// @@ -47,7 +61,7 @@ public Watchdog getDefinition() { } public TimeValue getTimeout() { - return (TimeValue) this.timeout; + return this.timeout; } public ReactorInstance getReactor() { @@ -59,6 +73,12 @@ public String toString() { return "WatchdogInstance " + name + "(" + timeout.toString() + ")"; } + ////////////////////////////////////////////////////// + //// Public fields. + + /** The ports or actions that this reaction may write to. */ + public Set> effects = new LinkedHashSet<>(); + ////////////////////////////////////////////////////// //// Private fields. diff --git a/core/src/main/java/org/lflang/generator/c/CWatchdogGenerator.java b/core/src/main/java/org/lflang/generator/c/CWatchdogGenerator.java index 9ce5dec07f..e83ea122af 100644 --- a/core/src/main/java/org/lflang/generator/c/CWatchdogGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CWatchdogGenerator.java @@ -13,6 +13,7 @@ import org.lflang.ast.ASTUtils; import org.lflang.generator.CodeBuilder; import org.lflang.generator.ReactorInstance; +import org.lflang.lf.Action; import org.lflang.lf.Mode; import org.lflang.lf.ModeTransition; import org.lflang.lf.Reactor; @@ -22,9 +23,10 @@ import org.lflang.util.StringUtil; /** - * @brief Generate C code for watchdogs. This class contains a collection of static methods - * supporting code generation in C for watchdogs. These methods are protected because they are - * intended to be used only within the same package. + * Generate C code for watchdogs. This class contains a collection of static methods supporting code + * generation in C for watchdogs. These methods are protected because they are intended to be used + * only within the same package. + * * @author Benjamin Asch * @author Edward A. Lee */ @@ -38,8 +40,7 @@ public class CWatchdogGenerator { */ public static boolean hasWatchdogs(Reactor reactor) { List watchdogs = ASTUtils.allWatchdogs(reactor); - if (watchdogs != null && !watchdogs.isEmpty()) return true; - return false; + return !watchdogs.isEmpty(); } ///////////////////////////////////////////////////////////////// @@ -159,15 +160,6 @@ protected static void generateWatchdogStruct( } } - /** - * Generate a global table of watchdog structs. - * - * @param count The number of watchdogs found. - * @return The code that defines the table or a comment if count is 0. - */ - ///////////////////////////////////////////////////////////////// - // Private methods - /** * Generate necessary initialization code inside the body of a watchdog handler. * @@ -185,17 +177,8 @@ private static String generateInitializationForWatchdog( // Define the "self" struct. String structType = CUtil.selfType(tpr); - // A null structType means there are no inputs, state, - // or anything else. No need to declare it. - if (structType != null) { - code.pr( - String.join( - "\n", - structType - + "* self = (" - + structType - + "*)instance_args; SUPPRESS_UNUSED_WARNING(self);")); - } + code.pr( + structType + "* self = (" + structType + "*)instance_args; SUPPRESS_UNUSED_WARNING(self);"); // Declare mode if in effects field of watchdog if (watchdog.getEffects() != null) { @@ -227,6 +210,8 @@ private static String generateInitializationForWatchdog( + name + " not a valid mode of this reactor."); } + } else if (variable instanceof Action) { + watchdogInitialization.pr(generateActionVariablesInHandler((Action) variable, tpr)); } } } @@ -243,6 +228,21 @@ private static String generateInitializationForWatchdog( return code.toString(); } + /** + * Generate action variables for the watchdog handler. + * + * @param action The action. + */ + private static String generateActionVariablesInHandler( + Action action, TypeParameterizedReactor tpr) { + String structType = CGenerator.variableStructType(action, tpr, false); + CodeBuilder builder = new CodeBuilder(); + builder.pr( + "// Expose the action struct as a local variable whose name matches the action name."); + builder.pr(structType + "* " + action.getName() + " = &self->_lf_" + action.getName() + ";"); + return builder.toString(); + } + /** * Do heavy lifting to generate the watchdog handler function * @@ -268,6 +268,8 @@ private static String generateFunction( function.pr(header + " {"); function.indent(); function.pr(init); + function.pr("{"); // Limit scope. + function.indent(); function.pr("environment_t * __env = self->base.environment;"); function.pr("LF_MUTEX_LOCK(&__env->mutex);"); function.pr("tag_t tag = {.time =" + watchdog.getName() + "->expiration , .microstep=0};"); @@ -280,6 +282,8 @@ private static String generateFunction( function.pr("_lf_schedule_at_tag(__env, " + watchdog.getName() + "->trigger, tag, NULL);"); function.pr("lf_cond_broadcast(&__env->event_q_changed);"); function.pr("LF_MUTEX_UNLOCK(&__env->mutex);"); + function.unindent(); + function.pr("}"); function.prSourceLineNumber(watchdog.getCode(), suppressLineDirectives); function.pr(ASTUtils.toText(watchdog.getCode())); function.prEndSourceLineNumber(suppressLineDirectives); diff --git a/core/src/main/java/org/lflang/scoping/LFScopeProviderImpl.java b/core/src/main/java/org/lflang/scoping/LFScopeProviderImpl.java index c5cbea4e7e..3455c1d822 100644 --- a/core/src/main/java/org/lflang/scoping/LFScopeProviderImpl.java +++ b/core/src/main/java/org/lflang/scoping/LFScopeProviderImpl.java @@ -49,6 +49,7 @@ import org.lflang.lf.Reactor; import org.lflang.lf.ReactorDecl; import org.lflang.lf.VarRef; +import org.lflang.lf.Watchdog; /** * This class enforces custom rules. In particular, it resolves references to parameters, ports, @@ -273,6 +274,11 @@ private RefType getRefType(VarRef variable) { } else if (conn.getRightPorts().contains(variable)) { return RefType.CRIGHT; } + } else if (variable.eContainer() instanceof Watchdog) { + var watchdog = (Watchdog) variable.eContainer(); + if (watchdog.getEffects().contains(variable)) { + return RefType.EFFECT; + } } return RefType.NULL; } diff --git a/test/C/src/concurrent/Watchdog.lf b/test/C/src/concurrent/Watchdog.lf index 45903401a3..ba0eb06843 100644 --- a/test/C/src/concurrent/Watchdog.lf +++ b/test/C/src/concurrent/Watchdog.lf @@ -10,7 +10,7 @@ target C { } reactor Watcher(timeout: time = 1500 ms) { - // Offset ameliorates startup time. + // Offset may reduce the likelihood of flakiness if long startup times occur. timer t(1 s, 1 s) // Period has to be smaller than watchdog timeout. Produced if the watchdog triggers. output d: int diff --git a/test/C/src/concurrent/WatchdogAction.lf b/test/C/src/concurrent/WatchdogAction.lf new file mode 100644 index 0000000000..2ca5157142 --- /dev/null +++ b/test/C/src/concurrent/WatchdogAction.lf @@ -0,0 +1,74 @@ +/** + * Test watchdog. This test starts a watchdog timer of 1500ms every 1s. Half the time, it then + * sleeps after starting the watchdog so that the watchdog expires. There should be a total of two + * watchdog expirations. This version uses an action instead of a reaction to the watchdog. + * @author Benjamin Asch + * @author Edward A. Lee + */ +target C { + timeout: 11000 ms +} + +reactor Watcher(timeout: time = 1500 ms) { + // Offset may reduce the likelihood of flakiness if long startup times occur. + timer t(1 s, 1 s) + // Period has to be smaller than watchdog timeout. Produced if the watchdog triggers. + output d: int + state count: int = 0 + logical action a + + watchdog poodle(timeout) -> a {= + instant_t p = lf_time_physical_elapsed(); + lf_print("******** Watchdog timed out at elapsed physical time: " PRINTF_TIME, p); + self->count++; + lf_schedule(a, 0); + =} + + reaction(t) -> poodle, d {= + lf_watchdog_start(poodle, 0); + lf_print("Watchdog started at physical time " PRINTF_TIME, lf_time_physical_elapsed()); + lf_print("Will expire at " PRINTF_TIME, lf_time_logical_elapsed() + self->timeout); + lf_set(d, 42); + =} + + reaction(a) -> d {= + lf_print("Reaction poodle was called."); + lf_set(d, 1); + =} + + reaction(shutdown) -> poodle {= + lf_watchdog_stop(poodle); + // Watchdog may expire in tests even without the sleep, but it should at least expire twice. + if (self->count < 2) { + lf_print_error_and_exit("Watchdog expired %d times. Expected at least 2.", self->count); + } + =} +} + +main reactor { + logical action a + state count: int = 0 + + w = new Watcher() + + reaction(startup) {= + if (NUMBER_OF_WATCHDOGS != 1) { + lf_print_error_and_exit("NUMBER_OF_WATCHDOGS was %d", NUMBER_OF_WATCHDOGS); + } + =} + + reaction(w.d) {= + lf_print("Watcher reactor produced an output. %d", self->count % 2); + self->count++; + if (self->count % 4 == 0) { + lf_print(">>>>>> Taking a long time to process that output!"); + lf_sleep(MSEC(1600)); + } + =} + + reaction(shutdown) {= + if (self->count < 12) { + lf_print_error_and_exit("Watchdog produced output %d times. Expected at least 12.", self->count); + } + =} +}