Skip to content

Commit

Permalink
Merge pull request #2172 from lf-lang/watchdog
Browse files Browse the repository at this point in the history
Add watchdogs to environment struct
  • Loading branch information
lhstrh authored Feb 14, 2024
2 parents ab0d406 + 778ef53 commit 86dac48
Show file tree
Hide file tree
Showing 12 changed files with 305 additions and 100 deletions.
1 change: 1 addition & 0 deletions core/src/main/java/org/lflang/generator/EnclaveInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public class EnclaveInfo {
public int numWorkers = 1;
public int numModalReactors = 0;
public int numModalResetStates = 0;
public int numWatchdogs = 0;

public String traceFileName = null;
private ReactorInstance instance;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ private String generateCreateEnvironments() {
+ ","
+ enclave.enclaveInfo.numModalResetStates
+ ","
+ enclave.enclaveInfo.numWatchdogs
+ ","
+ traceFileName
+ ");");
}
Expand Down
16 changes: 11 additions & 5 deletions core/src/main/java/org/lflang/generator/c/CGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,16 @@ public void doGenerate(Resource resource, LFGeneratorContext context) {
throw e;
}

// Inform the runtime of the number of watchdogs
// TODO: Can we do this at a better place? We need to do it when we have the main reactor
// since we need main to get all enclaves.
var nWatchdogs =
CUtil.getEnclaves(main).stream()
.map(it -> it.enclaveInfo.numWatchdogs)
.reduce(0, Integer::sum);
CompileDefinitionsProperty.INSTANCE.update(
targetConfig, Map.of("NUMBER_OF_WATCHDOGS", String.valueOf(nWatchdogs)));

// Create docker file.
if (targetConfig.get(DockerProperty.INSTANCE).enabled() && mainDef != null) {
try {
Expand Down Expand Up @@ -615,9 +625,6 @@ private void generateCodeFor(String lfModuleName) throws IOException {
if (targetLanguageIsCpp()) code.pr("}");
}

// If there are watchdogs, create a table of triggers.
code.pr(CWatchdogGenerator.generateWatchdogTable(watchdogCount));

// Generate function to initialize the trigger objects for all reactors.
code.pr(
CTriggerObjectsGenerator.generateInitializeTriggerObjects(
Expand Down Expand Up @@ -1696,8 +1703,7 @@ public void generateReactorInstance(ReactorInstance instance) {
initializeOutputMultiports(instance);
initializeInputMultiports(instance);
recordBuiltinTriggers(instance);
watchdogCount +=
CWatchdogGenerator.generateInitializeWatchdogs(initializeTriggerObjects, instance);
CWatchdogGenerator.generateInitializeWatchdogs(initializeTriggerObjects, instance);

// Next, initialize the "self" struct with state variables.
// These values may be expressions that refer to the parameter values defined above.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ public static String generateInitializeTriggerObjects(
"int modal_state_reset_count[_num_enclaves] = {0};"
+ " SUPPRESS_UNUSED_WARNING(modal_state_reset_count);",
"int modal_reactor_count[_num_enclaves] = {0};"
+ " SUPPRESS_UNUSED_WARNING(modal_reactor_count);"));
+ " SUPPRESS_UNUSED_WARNING(modal_reactor_count);",
"int watchdog_count[_num_enclaves] = {0};"
+ " SUPPRESS_UNUSED_WARNING(watchdog_count);"));

// Create the table to initialize intended tag fields to 0 between time
// steps.
Expand Down
71 changes: 44 additions & 27 deletions core/src/main/java/org/lflang/generator/c/CWatchdogGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.lflang.lf.VarRef;
import org.lflang.lf.Variable;
import org.lflang.lf.Watchdog;
import org.lflang.util.StringUtil;

/**
* @brief Generate C code for watchdogs. This class contains a collection of static methods
Expand Down Expand Up @@ -47,30 +48,40 @@ public static boolean hasWatchdogs(Reactor reactor) {
/**
* For the specified reactor instance, generate initialization code for each watchdog in the
* reactor. This code initializes the watchdog-related fields on the self struct of the reactor
* instance.
* instance. It also increments the watchdog count in the environment the parent reactor instance
* is within.
*
* @param code The place to put the code
* @param instance The reactor instance
* @return The count of watchdogs found in the reactor
*/
protected static int generateInitializeWatchdogs(CodeBuilder code, ReactorInstance instance) {
protected static void generateInitializeWatchdogs(CodeBuilder code, ReactorInstance instance) {
var foundOne = false;
var temp = new CodeBuilder();
var reactorRef = CUtil.reactorRef(instance);
int watchdogCount = 0;
var enclaveInfo = CUtil.getClosestEnclave(instance).enclaveInfo;
var enclaveStruct = CUtil.getEnvironmentStruct(instance);
var enclaveId = CUtil.getEnvironmentId(instance);

for (Watchdog watchdog :
ASTUtils.allWatchdogs(ASTUtils.toDefinition(instance.getDefinition().getReactorClass()))) {
var watchdogField = reactorRef + "->_lf_watchdog_" + watchdog.getName();
temp.pr(
String.join(
"\n",
"_lf_watchdogs[watchdog_number++] = &" + watchdogField + ";",
enclaveStruct
+ ".watchdogs[watchdog_count["
+ enclaveId
+ "]++] = &"
+ watchdogField
+ ";",
watchdogField
+ ".min_expiration = "
+ CTypes.getInstance()
.getTargetTimeExpr(instance.getTimeValue(watchdog.getTimeout()))
+ ";",
watchdogField + ".thread_active = false;",
watchdogField + ".active = false;",
watchdogField + ".terminate = false;",
"if (" + watchdogField + ".base->reactor_mutex == NULL) {",
" "
+ watchdogField
Expand All @@ -83,8 +94,7 @@ protected static int generateInitializeWatchdogs(CodeBuilder code, ReactorInstan
if (foundOne) {
code.pr(temp.toString());
}
code.pr("SUPPRESS_UNUSED_WARNING(_lf_watchdog_count);");
return watchdogCount;
enclaveInfo.numWatchdogs += watchdogCount;
}

/**
Expand Down Expand Up @@ -134,7 +144,8 @@ protected static void generateWatchdogStruct(
"\n",
"self->_lf_watchdog_" + watchdogName + ".base = &(self->base);",
"self->_lf_watchdog_" + watchdogName + ".expiration = NEVER;",
"self->_lf_watchdog_" + watchdogName + ".thread_active = false;",
"self->_lf_watchdog_" + watchdogName + ".active = false;",
"self->_lf_watchdog_" + watchdogName + ".terminate = false;",
"self->_lf_watchdog_"
+ watchdogName
+ ".watchdog_function = "
Expand All @@ -154,23 +165,6 @@ protected static void generateWatchdogStruct(
* @param count The number of watchdogs found.
* @return The code that defines the table or a comment if count is 0.
*/
protected static String generateWatchdogTable(int count) {
if (count == 0) {
return String.join(
"\n",
"// No watchdogs found.",
"typedef void watchdog_t;",
"watchdog_t* _lf_watchdogs = NULL;",
"int _lf_watchdog_count = 0;");
}
return String.join(
"\n",
List.of(
"// Array of pointers to watchdog structs.",
"watchdog_t* _lf_watchdogs[" + count + "];",
"int _lf_watchdog_count = " + count + ";"));
}

/////////////////////////////////////////////////////////////////
// Private methods

Expand Down Expand Up @@ -260,16 +254,39 @@ private static String generateInitializationForWatchdog(
private static String generateFunction(
String header, String init, Watchdog watchdog, boolean suppressLineDirectives) {
var function = new CodeBuilder();
function.pr("#include " + StringUtil.addDoubleQuotes(CCoreFilesUtils.getCTargetSetHeader()));
function.pr(
"""
#ifdef __cplusplus
extern "C" {
#endif
#include "reactor_common.h"
#ifdef __cplusplus
}
#endif
""");
function.pr(header + " {");
function.indent();
function.pr(init);
function.pr(
"_lf_schedule(self->base.environment, (*" + watchdog.getName() + ").trigger, 0, NULL);");
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};");
function.pr("if (lf_tag_compare(tag, lf_tag()) <= 0) { ");
function.indent();
function.pr("tag = lf_tag();");
function.pr("tag.microstep++;");
function.unindent();
function.pr("}");
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.prSourceLineNumber(watchdog.getCode(), suppressLineDirectives);
function.pr(ASTUtils.toText(watchdog.getCode()));
function.prEndSourceLineNumber(suppressLineDirectives);
function.unindent();
function.pr("}");
function.pr(
"#include " + StringUtil.addDoubleQuotes(CCoreFilesUtils.getCTargetSetUndefHeader()));
return function.toString();
}

Expand Down
72 changes: 72 additions & 0 deletions test/C/src/concurrent/Watchdog.lf
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* 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.
* @author Benjamin Asch
* @author Edward A. Lee
*/
target C {
timeout: 11000 ms
}

reactor Watcher(timeout: time = 1500 ms) {
// Offset ameliorates startup time.
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

watchdog poodle(timeout) {=
instant_t p = lf_time_physical_elapsed();
lf_print("******** Watchdog timed out at elapsed physical time: " PRINTF_TIME, p);
self->count++;
=}

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(poodle) -> 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);
}
=}
}
42 changes: 42 additions & 0 deletions test/C/src/concurrent/WatchdogMultiple.lf
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* This tests that the order of reactions triggered when a watchdog expires is as expected.
* @author Erling Rennemo Jellum
*/
target C

reactor WithWatchdog {
state cnt: int = 0

watchdog watch(10 msec) {= =}

reaction(startup) -> watch {=
lf_watchdog_start(watch, 0);
=}

reaction(watch) {=
self->cnt++;
if (lf_time_logical_elapsed() != MSEC(10)) {
lf_print_error_and_exit("Watchdog handler triggered at wrong tag.");
}
=}

reaction(shutdown) {=
if (self->cnt != 1) {
lf_print_error_and_exit("Watchdog did not timeout");
}
=}
}

main reactor {
w1 = new WithWatchdog()
w2 = new WithWatchdog()
timer t(2 sec)

reaction(startup) {=
if (NUMBER_OF_WATCHDOGS != 2) {
lf_print_error_and_exit("NUMBER_OF_WATCHDOGS was %d", NUMBER_OF_WATCHDOGS);
}
=}

reaction(t) {= =}
}
57 changes: 57 additions & 0 deletions test/C/src/concurrent/WatchdogMutex.lf
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* This tests that the order of reactions triggered when a watchdog expires is as expected.
* @author Erling Rennemo Jellum
*/
target C

preamble {=
typedef enum {
INVALID = 0,
STARTUP_DONE=1,
WATCHDOG_HANDLE_DONE = 2,
WATCHDOG_TRIGGERED_REACTION_DONE = 3,
} state_t;
=}

main reactor {
state test: state_t = {= INVALID =}
timer t(1 sec)

watchdog watch(1 msec) {=
lf_print("Watchdog handler");
if(self->test != STARTUP_DONE) {
lf_print_error_and_exit("Startup reaction not done");
}
self->test = WATCHDOG_HANDLE_DONE;
=}

reaction(startup) -> watch {=
if (NUMBER_OF_WATCHDOGS != 1) {
lf_print_error_and_exit("NUMBER_OF_WATCHDOGS was %d", NUMBER_OF_WATCHDOGS);
}

lf_watchdog_start(watch, 0);
if(self->test != 0) {
lf_print_error_and_exit("Startup reaction interrupted");
}
lf_sleep(MSEC(100));
if(self->test != 0) {
lf_print_error_and_exit("Startup reaction interrupted");
}
self->test = STARTUP_DONE;
=}

reaction(watch) {=
lf_print("Watchdog trigger");
if(self->test != WATCHDOG_HANDLE_DONE) {
lf_print_error_and_exit("Watchdog handle not finished");
}
self->test = WATCHDOG_TRIGGERED_REACTION_DONE;
=}

reaction(t) {=
if(self->test != WATCHDOG_TRIGGERED_REACTION_DONE) {
lf_print_error_and_exit("Watchdog did not expire");
}
=}
}
Loading

0 comments on commit 86dac48

Please sign in to comment.