Skip to content

Commit 4ffade0

Browse files
sbgaiaedwardalee
andauthored
Add action scheduling policies (#334)
* Add "update" to SPACING_VIOLATION_POLICIES in LFValidator * Add DelayedActionDuplicate.lf test checking all action policies * Add ActionPolicy enumeration * Add ActionPolicy parameter to macros_internal.h * Add ActionPolicy to UcActionGenerator * Add inline functions for event comparison * Update ActionLib constructors to use "defer" parameter * Add get_event_queue method to Scheduler interface * Add action scheduling policies and remove recursive heapify from event queue * Apply code formatting * Add more event queue tests * Free previous payload with "replace" policy * Update Sender action constructor to use 'defer' policy * Update src/queues.c Co-authored-by: Edward A. Lee <eal@berkeley.edu> * Add javadoc to event.h * Add update policy tests * Fix update policy * Add docs * Apply code formatting * Refactor comments * Refactor action update policies: move event cancel/replace from Action into Scheduler * Add unit tests for defer, drop, replace, and update action policies * Apply code formatting * Add minSpacing validation for "defer" policy in LFValidator * Refactor event comparison functions to explicitly return boolean values * Refactor type assignment in FederatedInputConnection_ctor for implicit conversion 'bool' -> 'int' * Add caching for Xtext ANTLR generator in build action * Revert changes: implicit conversion 'bool' -> 'int' * Refactor action policies to use ACTION_POLICY constants and update error return values for consistency * Refactor FederatedInputConnection_ctor to use explicit if-else for connection type assignment * Fix clang-tidy warnings * Remove clang-tidy installation from memory workflow dependencies * Fix memory report crash when base and PR branches have different tests * Add "update" to SPACING_VIOLATION_POLICIES in LFValidator * Add DelayedActionDuplicate.lf test checking all action policies * Add ActionPolicy enumeration * Add ActionPolicy parameter to macros_internal.h * Add ActionPolicy to UcActionGenerator * Add inline functions for event comparison * Update ActionLib constructors to use "defer" parameter * Add get_event_queue method to Scheduler interface * Add action scheduling policies and remove recursive heapify from event queue * Apply code formatting * Add more event queue tests * Free previous payload with "replace" policy * Update Sender action constructor to use 'defer' policy * Update src/queues.c Co-authored-by: Edward A. Lee <eal@berkeley.edu> * Add javadoc to event.h * Add update policy tests * Fix update policy * Add docs * Apply code formatting * Refactor comments * Refactor action update policies: move event cancel/replace from Action into Scheduler * Add unit tests for defer, drop, replace, and update action policies * Apply code formatting * Add minSpacing validation for "defer" policy in LFValidator * Refactor event comparison functions to explicitly return boolean values * Add caching for Xtext ANTLR generator in build action * Refactor action policies to use ACTION_POLICY constants and update error return values for consistency * Fix clang-tidy warnings * Remove clang-tidy installation from memory workflow dependencies * Fix memory report crash when base and PR branches have different tests * Fix event matching logic in find_matching_event_idx function * Set maxNumberOfPendingEvents default value to SIZE_MAX for void actions * Update maxNumPendingEvents default value to 20 and add VoidAction test case * Enforce action policy when the payload_buffer is full * Restore max number of pending events to 1 when not specified * Fix clang-tidy errors * Add unit tests for action buffer policies: DEFER, DROP, REPLACE, and UPDATE * Refactor action minSpacing validation and generation with `defer` policy * Refactor target properties with annotations * Apply code formatting * Fix max_pending_events annotation * Revert changes when policy is defer and minSpacing is 0 * Fix action scheduling when minSpacing is equals to 0 and defer policy * Fix minSpacing reference in reactor constructor code generation * Add imports for Action and ActionOrigin in LFValidator * Apply code formatting --------- Co-authored-by: Edward A. Lee <eal@berkeley.edu>
1 parent 9bfd495 commit 4ffade0

30 files changed

Lines changed: 1140 additions & 116 deletions

.github/actions/lingua-franca/action.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,10 @@ runs:
88
with:
99
distribution: temurin
1010
java-version: 17
11+
- name: Cache Xtext ANTLR generator
12+
uses: actions/cache@v4
13+
with:
14+
path: lfc/core/.antlr-generator-3.2.0-patch.jar
15+
key: antlr-generator-3.2.0-patch
1116
- name: Gradle Build Action
1217
uses: gradle/gradle-build-action@v2.8.0

.github/workflows/memory.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
- name: Install deps
2121
run: |
2222
sudo apt-get install lcov pipx -y
23-
sudo pipx install clang-format clang-tidy
23+
sudo pipx install clang-format
2424
- name: Generate memory usage report (base branch)
2525
id: base
2626
uses: ./.github/actions/memory-report

examples/zephyr/basic_federated/federated_sender/src/sender.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ typedef struct {
2323
} msg_t;
2424

2525
LF_DEFINE_ACTION_STRUCT(Sender, act, PhysicalAction, 1, 0, 0, 10, bool);
26-
LF_DEFINE_ACTION_CTOR(Sender, act, PhysicalAction, 1, 0, 0, 10, bool);
26+
LF_DEFINE_ACTION_CTOR(Sender, act, PhysicalAction, ACTION_POLICY_DEFER, 1, 0, 0, 10, bool);
2727
LF_DEFINE_REACTION_STRUCT(Sender, r, 1);
2828
LF_DEFINE_REACTION_CTOR(Sender, r, 0, NULL, NULL);
2929

include/reactor-uc/action.h

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,30 @@ typedef struct LogicalAction LogicalAction;
1212

1313
typedef enum { LOGICAL_ACTION, PHYSICAL_ACTION } ActionType;
1414

15+
/** @brief Policy for handling scheduled events that violate the specified minimum
16+
* interarrival time, or that exceed the action's pending-event capacity.
17+
*
18+
* The same policy is applied in both situations:
19+
*
20+
* - `ACTION_POLICY_DEFER` (default): adjust the tag so the minimum interarrival time is
21+
* satisfied. If the buffer is full, the new event is dropped (no slot is available to
22+
* defer into) and `LF_VALUE_BUFFER_FULL` is returned.
23+
* - `ACTION_POLICY_DROP`: drop the event. Returns `LF_OK` for min_spacing drops, or
24+
* `LF_VALUE_BUFFER_FULL` when the buffer was full.
25+
* - `ACTION_POLICY_REPLACE`: attempt to replace the payload of the preceding event. If
26+
* the preceding event has already been popped off the event queue, falls back to
27+
* `ACTION_POLICY_DEFER` (or to dropping with `LF_VALUE_BUFFER_FULL` when the buffer is
28+
* full). Naturally handles buffer-full because it does not require a new slot.
29+
* - `ACTION_POLICY_UPDATE`: cancel the preceding event (if still queued) and schedule
30+
* the new one in its place. Naturally handles buffer-full because cancelling frees a
31+
* slot.
32+
*/
33+
typedef enum { ACTION_POLICY_DEFER, ACTION_POLICY_DROP, ACTION_POLICY_REPLACE, ACTION_POLICY_UPDATE } ActionPolicy;
34+
1535
struct Action {
1636
Trigger super;
1737
ActionType type;
38+
ActionPolicy policy; // Policy for handling events that violate min_spacing
1839
interval_t min_offset; // The minimum offset from the current time that an event can be scheduled on this action.
1940
interval_t min_spacing; // The minimum spacing between two events scheduled on this action.
2041
instant_t last_event_time; // Logical time of most recent event scheduled on this action.
@@ -36,27 +57,27 @@ struct Action {
3657
lf_ret_t (*schedule)(Action* self, interval_t offset, const void* value);
3758
};
3859

39-
void Action_ctor(Action* self, ActionType type, interval_t min_offset, interval_t min_spacing, Reactor* parent,
40-
Reaction** sources, size_t sources_size, Reaction** effects, size_t effects_size, Reaction** observers,
41-
size_t observers_size, void* value_ptr, size_t value_size, void* payload_buf, bool* payload_used_buf,
42-
size_t event_bound);
60+
void Action_ctor(Action* self, ActionType type, ActionPolicy policy, interval_t min_offset, interval_t min_spacing,
61+
Reactor* parent, Reaction** sources, size_t sources_size, Reaction** effects, size_t effects_size,
62+
Reaction** observers, size_t observers_size, void* value_ptr, size_t value_size, void* payload_buf,
63+
bool* payload_used_buf, size_t event_bound);
4364

4465
struct LogicalAction {
4566
Action super;
4667
};
4768

48-
void LogicalAction_ctor(LogicalAction* self, interval_t min_offset, interval_t min_spacing, Reactor* parent,
49-
Reaction** sources, size_t sources_size, Reaction** effects, size_t effects_size,
50-
Reaction** observers, size_t observers_size, void* value_ptr, size_t value_size,
51-
void* payload_buf, bool* payload_used_buf, size_t event_bound);
69+
void LogicalAction_ctor(LogicalAction* self, ActionPolicy policy, interval_t min_offset, interval_t min_spacing,
70+
Reactor* parent, Reaction** sources, size_t sources_size, Reaction** effects,
71+
size_t effects_size, Reaction** observers, size_t observers_size, void* value_ptr,
72+
size_t value_size, void* payload_buf, bool* payload_used_buf, size_t event_bound);
5273

5374
struct PhysicalAction {
5475
Action super;
5576
MUTEX_T mutex;
5677
};
5778

58-
void PhysicalAction_ctor(PhysicalAction* self, interval_t min_offset, interval_t min_spacing, Reactor* parent,
59-
Reaction** sources, size_t sources_size, Reaction** effects, size_t effects_size,
60-
Reaction** observers, size_t observers_size, void* value_ptr, size_t value_size,
61-
void* payload_buf, bool* payload_used_buf, size_t event_bound);
79+
void PhysicalAction_ctor(PhysicalAction* self, ActionPolicy policy, interval_t min_offset, interval_t min_spacing,
80+
Reactor* parent, Reaction** sources, size_t sources_size, Reaction** effects,
81+
size_t effects_size, Reaction** observers, size_t observers_size, void* value_ptr,
82+
size_t value_size, void* payload_buf, bool* payload_used_buf, size_t event_bound);
6283
#endif

include/reactor-uc/error.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ typedef enum {
2020
LF_EVENT_QUEUE_FULL,
2121
LF_VALUE_BUFFER_FULL,
2222
LF_NETWORK_CHANNEL_RETRY,
23-
LF_NETWORK_CHANNEL_EMPTY
23+
LF_NETWORK_CHANNEL_EMPTY,
24+
LF_EVENT_NOT_FOUND
2425
} lf_ret_t;
2526

2627
/**

include/reactor-uc/event.h

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,71 @@ struct SystemEventHandler {
7474
void EventPayloadPool_ctor(EventPayloadPool* self, char* buffer, bool* used, size_t element_size, size_t capacity,
7575
size_t reserved);
7676

77+
/**
78+
* @brief Get the tag of an arbitrary event.
79+
* @param arbitrary_event Pointer to the arbitrary event.
80+
* @return The tag of the event.
81+
*/
82+
static inline tag_t get_tag(ArbitraryEvent* arbitrary_event) { return arbitrary_event->event.super.tag; }
83+
84+
/**
85+
* @brief Compare the tags of two arbitrary events.
86+
* @param evt1 Pointer to the first arbitrary event.
87+
* @param evt2 Pointer to the second arbitrary event.
88+
* @return true if the tags are the same, false otherwise.
89+
*/
90+
static inline bool events_same_tag(ArbitraryEvent* evt1, ArbitraryEvent* evt2) {
91+
return lf_tag_compare(get_tag(evt1), get_tag(evt2)) == 0;
92+
}
93+
94+
/**
95+
* @brief Compare the triggers of two arbitrary events.
96+
* @param evt1 Pointer to the first arbitrary event.
97+
* @param evt2 Pointer to the second arbitrary event.
98+
* @return true if the triggers of both events are the same, false otherwise.
99+
*/
100+
static inline bool events_same_trigger(ArbitraryEvent* evt1, ArbitraryEvent* evt2) {
101+
if (evt1->event.super.type != EVENT || evt2->event.super.type != EVENT) {
102+
return false;
103+
}
104+
Event* event1 = &evt1->event;
105+
Event* event2 = &evt2->event;
106+
return event1->trigger == event2->trigger;
107+
}
108+
109+
/**
110+
* @brief Compare the handlers of two arbitrary events.
111+
* @param evt1 Pointer to the first arbitrary event.
112+
* @param evt2 Pointer to the second arbitrary event.
113+
* @return true if the handlers of both events are the same, false otherwise.
114+
*/
115+
static inline bool events_same_handler(ArbitraryEvent* evt1, ArbitraryEvent* evt2) {
116+
if (evt1->event.super.type != SYSTEM_EVENT || evt2->event.super.type != SYSTEM_EVENT) {
117+
return false;
118+
}
119+
SystemEvent* event1 = &evt1->system_event;
120+
SystemEvent* event2 = &evt2->system_event;
121+
return event1->handler == event2->handler;
122+
}
123+
124+
/**
125+
* @brief Compare the tags and triggers of two arbitrary events.
126+
* @param evt1 Pointer to the first arbitrary event.
127+
* @param evt2 Pointer to the second arbitrary event.
128+
* @return true if the tags and triggers of both events are the same, false otherwise.
129+
*/
130+
static inline bool events_same_tag_and_trigger(ArbitraryEvent* evt1, ArbitraryEvent* evt2) {
131+
return (events_same_tag(evt1, evt2) && events_same_trigger(evt1, evt2)) != 0;
132+
}
133+
134+
/**
135+
* @brief Compare the tags and handlers of two arbitrary events.
136+
* @param evt1 Pointer to the first arbitrary event.
137+
* @param evt2 Pointer to the second arbitrary event.
138+
* @return true if the tags and handlers of both events are the same, false otherwise.
139+
*/
140+
static inline bool events_same_tag_and_handler(ArbitraryEvent* evt1, ArbitraryEvent* evt2) {
141+
return (events_same_tag(evt1, evt2) && events_same_handler(evt1, evt2)) != 0;
142+
}
143+
77144
#endif

include/reactor-uc/macros_internal.h

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -312,21 +312,22 @@
312312
Reaction* observers[(ObserverSize)]; \
313313
} ReactorName##_##ActionName;
314314

315-
#define LF_DEFINE_ACTION_CTOR(ReactorName, ActionName, ActionType, EffectSize, SourceSize, ObserverSize, \
315+
#define LF_DEFINE_ACTION_CTOR(ReactorName, ActionName, ActionType, ActionPolicy, EffectSize, SourceSize, ObserverSize, \
316316
MaxPendingEvents, BufferType) \
317317
void ReactorName##_##ActionName##_ctor(ReactorName##_##ActionName* self, Reactor* parent, interval_t min_delay, \
318318
interval_t min_spacing) { \
319-
ActionType##_ctor(&self->super, min_delay, min_spacing, parent, self->sources, (SourceSize), self->effects, \
320-
(EffectSize), self->observers, ObserverSize, &self->value, sizeof(self->value), \
319+
ActionType##_ctor(&self->super, ActionPolicy, min_delay, min_spacing, parent, self->sources, (SourceSize), \
320+
self->effects, (EffectSize), self->observers, ObserverSize, &self->value, sizeof(self->value), \
321321
(void*)&self->payload_buf, self->payload_used_buf, (MaxPendingEvents)); \
322322
}
323323

324-
#define LF_DEFINE_ACTION_CTOR_VOID(ReactorName, ActionName, ActionType, EffectSize, SourceSize, ObserverSize, \
325-
MaxPendingEvents) \
324+
#define LF_DEFINE_ACTION_CTOR_VOID(ReactorName, ActionName, ActionType, ActionPolicy, EffectSize, SourceSize, \
325+
ObserverSize, MaxPendingEvents) \
326326
void ReactorName##_##ActionName##_ctor(ReactorName##_##ActionName* self, Reactor* parent, interval_t min_delay, \
327327
interval_t min_spacing) { \
328-
ActionType##_ctor(&self->super, min_delay, min_spacing, parent, self->sources, (SourceSize), self->effects, \
329-
(EffectSize), self->observers, ObserverSize, NULL, 0, NULL, NULL, (MaxPendingEvents)); \
328+
ActionType##_ctor(&self->super, ActionPolicy, min_delay, min_spacing, parent, self->sources, (SourceSize), \
329+
self->effects, (EffectSize), self->observers, ObserverSize, NULL, 0, NULL, NULL, \
330+
(MaxPendingEvents)); \
330331
}
331332

332333
#define LF_ACTION_INSTANCE(ReactorName, ActionName) ReactorName##_##ActionName ActionName

include/reactor-uc/queues.h

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,49 @@
1111
typedef struct EventQueue EventQueue;
1212
typedef struct ReactionQueue ReactionQueue;
1313

14+
/**
15+
* @brief Min-heap priority event queue ordered by tag.
16+
*/
1417
struct EventQueue {
18+
/** @brief Return the tag of the earliest event in the queue, or FOREVER_TAG
19+
* if empty.
20+
*/
1521
tag_t (*next_tag)(EventQueue* self);
22+
/**
23+
* @brief Insert an event into the queue. Returns LF_EVENT_QUEUE_FULL if the
24+
* queue is full.
25+
*/
1626
lf_ret_t (*insert)(EventQueue* self, AbstractEvent* event);
27+
/** @brief Remove and return the earliest event in the queue. Returns
28+
* LF_EVENT_QUEUE_EMPTY if the queue is empty.
29+
*/
1730
lf_ret_t (*pop)(EventQueue* self, AbstractEvent* event);
31+
/** @brief Return true if the queue contains no events. */
1832
bool (*empty)(EventQueue* self);
19-
void (*heapify_locked)(EventQueue* self, size_t idx);
33+
/** @brief Restore the heap invariant for the entire queue. Should be called after bulk
34+
* insertion of events. */
35+
void (*build_heap)(EventQueue* self);
36+
/** @brief Restore the heap invariant downward from @p idx. */
37+
void (*heapify)(EventQueue* self, size_t idx);
38+
/** @brief Find an event with the same tag and trigger as @p event, or NULL if not found. */
39+
ArbitraryEvent* (*find_equal_same_tag)(EventQueue* self, AbstractEvent* event);
40+
/** @brief Remove the event equal to @p event from the queue. Returns
41+
* LF_EVENT_NOT_FOUND if no such event exists, LF_OK otherwise.
42+
*/
43+
lf_ret_t (*remove)(EventQueue* self, AbstractEvent* event);
2044

21-
size_t size;
22-
size_t capacity;
23-
ArbitraryEvent* array;
24-
MUTEX_T mutex;
45+
size_t size; /**< @brief Current number of events in the queue. */
46+
size_t capacity; /**< @brief Maximum number of events the queue can hold. */
47+
ArbitraryEvent* array; /**< @brief Backing array of the event queue. */
48+
MUTEX_T mutex; /**< @brief Mutex protecting concurrent access. */
2549
};
2650

51+
/**
52+
* @brief Initialize an EventQueue.
53+
* @param self The EventQueue to initialize.
54+
* @param array Backing array of at least @p capacity ArbitraryEvent elements.
55+
* @param capacity Maximum number of events the queue can hold.
56+
*/
2757
void EventQueue_ctor(EventQueue* self, ArbitraryEvent* array, size_t capacity);
2858

2959
struct ReactionQueue {

include/reactor-uc/scheduler.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,18 @@ struct Scheduler {
5757
* queue.
5858
*/
5959
void (*prepare_timestep)(Scheduler* self, tag_t tag);
60+
61+
/**
62+
* @brief Cancel a pending event for a `trigger` at `event_time`. Returns
63+
* LF_EVENT_NOT_FOUND if no matching event exists.
64+
*/
65+
lf_ret_t (*cancel_event)(Scheduler* self, Trigger* trigger, instant_t event_time);
66+
67+
/**
68+
* @brief Replace the payload of a pending event for a `trigger` at
69+
* `event_time`. Returns LF_EVENT_NOT_FOUND if no matching event exists.
70+
*/
71+
lf_ret_t (*replace_event_payload)(Scheduler* self, Trigger* trigger, instant_t event_time, const void* new_value);
6072
};
6173

6274
Scheduler* Scheduler_new(Environment* env, EventQueue* event_queue, EventQueue* system_event_queue,

lfc/core/src/main/java/org/lflang/validation/LFValidator.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@
5050
import org.lflang.InferredType;
5151
import org.lflang.ModelInfo;
5252
import org.lflang.ast.ASTUtils;
53+
import org.lflang.lf.Action;
54+
import org.lflang.lf.ActionOrigin;
5355
import org.lflang.lf.Attribute;
5456
import org.lflang.lf.BracedListExpression;
5557
import org.lflang.lf.BuiltinTrigger;
@@ -98,6 +100,27 @@ public class LFValidator extends BaseLFValidator {
98100
// NOTE: please list methods in alphabetical order, and follow a naming convention checkClass,
99101
// where Class is the AST class.
100102

103+
@Check(CheckType.FAST)
104+
public void checkAction(Action action) {
105+
checkName(action.getName(), Literals.VARIABLE__NAME);
106+
if (action.getOrigin() == ActionOrigin.NONE) {
107+
error(
108+
"Action must have modifier {@code logical} or {@code physical}.",
109+
Literals.ACTION__ORIGIN);
110+
}
111+
if (action.getPolicy() != null && !SPACING_VIOLATION_POLICIES.contains(action.getPolicy())) {
112+
error(
113+
"Unrecognized spacing violation policy: "
114+
+ action.getPolicy()
115+
+ ". Available policies are: "
116+
+ String.join(", ", SPACING_VIOLATION_POLICIES)
117+
+ ".",
118+
Literals.ACTION__POLICY);
119+
}
120+
checkExpressionIsTime(action.getMinDelay(), Literals.ACTION__MIN_DELAY);
121+
checkExpressionIsTime(action.getMinSpacing(), Literals.ACTION__MIN_SPACING);
122+
}
123+
101124
public void checkReactorName(String name) throws IOException {
102125
// Check for illegal names.
103126
checkName(name, Literals.REACTOR_DECL__NAME);
@@ -874,7 +897,8 @@ private boolean sameType(Type type1, Type type2) {
874897
"Reserved words in the target language are not allowed for objects (inputs, outputs, actions,"
875898
+ " timers, parameters, state, reactor definitions, and reactor instantiation): ";
876899

877-
private static List<String> SPACING_VIOLATION_POLICIES = List.of("defer", "drop", "replace");
900+
private static List<String> SPACING_VIOLATION_POLICIES =
901+
List.of("defer", "drop", "replace", "update");
878902

879903
private static String UNDERSCORE_MESSAGE =
880904
"Names of objects (inputs, outputs, actions, timers, parameters, "

0 commit comments

Comments
 (0)