Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Effects made accessible in watchdog handlers #2359

Merged
merged 10 commits into from
Jul 13, 2024
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 <benjamintasch@berkeley.edu>}
* @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
56 changes: 31 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,10 @@ 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 +212,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 +230,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 +270,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 +284,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);
}
=}
}
Loading