Skip to content

Commit

Permalink
Merge pull request #2359 from lf-lang/watchdog-actions
Browse files Browse the repository at this point in the history
Effects made accessible in watchdog handlers
  • Loading branch information
edwardalee authored Jul 13, 2024
2 parents bc52337 + a19589a commit 0f9f8eb
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1215,6 +1215,42 @@ private Collection<KNode> transformReactorNetwork(
}
}

// Connect watchdogs
Set<WatchdogInstance> 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<KPort, KPort> ports = _linguaFrancaShapeExtensions.addWatchdogFigureAndPorts(node);
setAnnotatedLayoutOptions(watchdog.getDefinition(), node);
if (watchdog.getTimeout() != null) {
_kLabelExtensions.addOutsideBottomCenteredNodeLabel(
node, String.format("timeout: %s", watchdog.getTimeout().toString()), 7);
}
Set<TriggerInstance<?>> 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<ActionInstance> actions = new HashSet<>();
actions.addAll(actionSources.keySet());
Expand Down Expand Up @@ -1258,34 +1294,6 @@ private Collection<KNode> transformReactorNetwork(
}
}

// Connect watchdogs
Set<WatchdogInstance> 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<KPort, KPort> 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<PortInstance> sourcePorts = new LinkedList<>(reactorInstance.inputs);
Expand Down
26 changes: 23 additions & 3 deletions core/src/main/java/org/lflang/generator/WatchdogInstance.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 <[email protected]>}
* @author Benjamin Asch
*/
public class WatchdogInstance extends TriggerInstance<Watchdog> {

Expand All @@ -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).
}
}

//////////////////////////////////////////////////////
Expand All @@ -47,7 +61,7 @@ public Watchdog getDefinition() {
}

public TimeValue getTimeout() {
return (TimeValue) this.timeout;
return this.timeout;
}

public ReactorInstance getReactor() {
Expand All @@ -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<TriggerInstance<? extends Variable>> effects = new LinkedHashSet<>();

//////////////////////////////////////////////////////
//// Private fields.

Expand Down
54 changes: 29 additions & 25 deletions core/src/main/java/org/lflang/generator/c/CWatchdogGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
*/
Expand All @@ -38,8 +40,7 @@ public class CWatchdogGenerator {
*/
public static boolean hasWatchdogs(Reactor reactor) {
List<Watchdog> watchdogs = ASTUtils.allWatchdogs(reactor);
if (watchdogs != null && !watchdogs.isEmpty()) return true;
return false;
return !watchdogs.isEmpty();
}

/////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -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.
*
Expand All @@ -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) {
Expand Down Expand Up @@ -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));
}
}
}
Expand All @@ -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
*
Expand All @@ -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};");
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
}
Expand Down
2 changes: 1 addition & 1 deletion test/C/src/concurrent/Watchdog.lf
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
74 changes: 74 additions & 0 deletions test/C/src/concurrent/WatchdogAction.lf
Original file line number Diff line number Diff line change
@@ -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);
}
=}
}

0 comments on commit 0f9f8eb

Please sign in to comment.