Skip to content

Commit 09bf416

Browse files
committed
Edited decentralized blog post with new example
1. Added aircraft door example to better explain maxwait=forever 2. General refinement of the post
1 parent 22a815a commit 09bf416

File tree

3 files changed

+183
-100
lines changed

3 files changed

+183
-100
lines changed
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
---
2+
slug: decentralized-consistency
3+
title: "Decentralized Consistency"
4+
authors: [fra-p, eal, rcakella]
5+
tags: [lingua franca, federation, decentralized, consistency, STA]
6+
---
7+
8+
The design of [distributed applications](/docs/writing-reactors/distributed-execution) in Lingua Franca requires care, particularly if the coordination of the federation is [decentralized](/docs/writing-reactors/distributed-execution#decentralized-coordination). The intent of this post is to illustrate and handle the challenges arising from designing distributed applications in Lingua Franca, with the help of two realistic use cases.
9+
10+
## Aircraft door use case
11+
Aircraft doors on passenger flights are currently managed manually by flight attendants.
12+
Before the take-off, the flight attendants _arm_ the door: if the door is opened in this state, an evacuation slide is automatically inflated and deployed for emergency landing.
13+
When the aircraft lands in normal and safe conditions, before opening the door, the flight attendants _disarm_ it to avoid the deployment of the evacuation slide.
14+
Flight attendants, however, are allowed to disarm the door _only_ when they see from the porthole the ramp that will allow the passengers to disembark the aircraft.
15+
16+
![AircraftDoor diagram](../static/img/blog/AircraftDoor.svg)
17+
18+
Consider the above Lingua Franca program that implements a simplified system to remotely open the aircraft door that is in the _armed_ state.
19+
The door implements two independent remote services, door _disarming_ and door _opening_, encoded by two different reactions in the `Door` reactor.
20+
We imagine that the pilot interacting with the cockpit issues the command to open the door that triggers the door opening service.
21+
We would also like to automate the disarming of the door using a camera. When the camera determines that the ramp is attached to the aircraft, it triggers the disarming service of the door.
22+
There are different ways to design and refactor the above system, for example, by removing the direct connection between the `Cockpit` and `Door` reactors. Our design choice is meant to highlight that door _disarming_ and _opening_ are two different and independent remote services triggered by two different commands issued by two different system actors. Therefore, each actor has an independent connection to the door to request its service.
23+
24+
The purpose of the system is to open the door in reaction to the command from the cockpit only in normal conditions, that is, when the ramp is present and the door is not armed. The door, upon receiving the command from the cockpit, should wait for clearance from the camera before opening.
25+
26+
This is an example of an application that cannot safely proceed its processing without assurance on its inputs. In fact, if the door processes immediately the command from the cockpit, and the door is still _armed_ because the input from the camera has not come yet, the evacuation slide will be deployed as if it was an emergency landing. The door, then, has to wait for both inputs before invoking the _opening_ service.
27+
28+
### In-order message processing
29+
The order in which messages are processed is crucial in this application. When the _disarm_ and _open_ commands arrive together, the _disarm_ service needs to be invoked before opening the door, otherwise the escape slide will be deployed and that is not the desired behavior.
30+
Lingua Franca guarantees determinism in the execution order of reactions with simultaneous inputs, and the order is given by the the order of declaration of the reactions inside the reactor. It is then sufficient to declare the `disarm` reaction _before_ the `open` one. The diagram confirms the execution order by labeling the `disarm` reaction with 1 and the `open` reaction with 2.
31+
32+
The more challenging situation is when the inputs do not arrive together, and this is discussed in the following section.
33+
34+
### Consistency with decentralized coordination
35+
The application is implemented as a federated program with decentralized coordination, which means that the advancement of logical time in each single federate is not subject to approval from any centralized entities, but it is done locally based on the input it receives from the other federates.
36+
37+
Let us consider the case when the `Door` reactor receives the _open_ command from the `Cockpit` reactor, but not yet the _disarm_ command from the `Camera` reactor. As previously observed, the `Door` cannot proceed to open the door, because it needs to wait for the `Camera` to send the _disarm_ command.
38+
But how long should it wait?
39+
40+
Lingua Franca allows you to customize the waiting time. Each federate has a parameter called [`maxwait`](/docs/writing-reactors/distributed-execution#safe-to-advance-sta) that controls how long the federate should wait for inputs from other federates before processing an input it has just received.
41+
More precisely, the `maxwait` is how much time a federate waits before advancing its tag to that of the just received event, when it is not known if the other input ports will receive data with the same or an earlier tag. At the expiration of the `maxwait`, the federate assumes that those unresolved ports will not receive any data with earlier tags, and advances its logical time to the tag of the received event.
42+
43+
In our example, we want the door to _indefinitely wait_ for both _disarm_ and _open_ commands to arrive before processing any of them. In Lingua Franca, this is obtained by setting `maxwait = forever`, and it means that the `Door` reactor cannot safely proceed without assurance about the inputs.
44+
45+
The implementation of the `Door` reactor and its instantiation are shown below:
46+
47+
```lf-c
48+
reactor Door {
49+
input open: bool
50+
input disarm: bool
51+
state isDisarmed: bool = false
52+
state isOpen: bool = false
53+
54+
reaction(disarm) {=
55+
if (!self->isDisarmed) {
56+
self->isDisarmed = true;
57+
printf("Door disarmed\n");
58+
}
59+
=} maxwait {=
60+
printf("STP violation\n");
61+
=}
62+
63+
reaction(open) {=
64+
if (self->isDisarmed) {
65+
printf("Door open - normal mode\n");
66+
} else {
67+
// This should never happen
68+
printf("Door open - !emergency mode!\n");
69+
}
70+
=} maxwait {=
71+
printf("STP violation\n");
72+
=}
73+
}
74+
75+
federated reactor {
76+
c = new Cockpit()
77+
v = new Camera()
78+
79+
@maxwait(forever)
80+
d = new Door()
81+
82+
c.open -> d.open
83+
c.open -> v.check_ramp
84+
v.ramp_present -> d.disarm
85+
}
86+
```
87+
The reaction triggered by the `open` command prints on the standard output whether the door was _disarmed_ or not at the time of opening. We do not expect emergency openings with `maxwait` set to `forever`.
88+
89+
The `maxwait` parameter is specified at instantiation time within the main reactor. Right before creating the instance of the `Door` reactor for which we want to set the parameter, we use the `@maxwait` annotation that takes as input the new `maxwait` value. The reactions of the `Door` reactor that are triggered by remote inputs are associated with a [fault handler](/docs/writing-reactors/distributed-execution#safe-to-process-stp-violation-handling) that is invoked in the case of timing inconsistencies during input processing. This event will be thoroughly discussed in another blog post.
90+
91+
## Automatic emergency braking use case
92+
![AutomaticEmergencyBrakingSystem diagram](../static/img/blog/AutomaticEmergencyBrakingSystem.svg)
93+
94+
Consider the above Lingua Franca implementation of an automatic emergency braking system, one of the most critical ADAS systems which modern cars are equipped with.
95+
The controller system modeled by the `AutomaticEmergencyBraking` reactor reads data coming from two sensors, a lidar and a radar, and uses both to detect if objects or pedestrians cross the trajectory the car, thus performing _sensor fusion_.
96+
When one of the two sensors signals the presence of an object at a distance shorter than a configurable threshold, the controller triggers the brake to stop the car and avoid crashing into it.
97+
98+
The sensors are modeled with their own timer that triggers the generation of data. The clocks of all federates are automatically synchronized by the [clock synchronization algorithm](/docs/writing-reactors/distributed-execution#clock-synchronization) of the Lingua Franca runtime.
99+
Typically, in a real use case of this kind, the clock of sensor devices cannot be controlled by Lingua Franca, but a way to work around this limitation is to resample the data collected by sensors with the timing given by a clock that the runtime can control.
100+
The sensor reactors of our application are then modeling this resampling of sensor data that fits well with the Lingua Franca semantics for time determinism.
101+
102+
The lidar sensor has a sampling frequency that is twice that of the radar, and this is reflected by the timer in the corresponding reactors: the lidar timer has a period of 50ms, while that of the radar 100ms.
103+
Their deadline is equal to their period and is enforced using the dedicated `DeadlineCheck` reactors, following the guidelines of how to [work with deadlines](/blog/deadlines).
104+
105+
The sensor behavior in the application is simulated in a way that each sensor constantly produces distance values above the threshold (i.e., no objects in the way), and then at a random time it sends a distance value below the threshold, indicating the presence of a close object. When the `AutomaticEmergencyBraking` reactor receives that message, it signals the `BrakingSystem` reactor to brake the car, and the whole system shuts down.
106+
107+
### Desired system properties
108+
Availability is a crucial property of this application, because we want the automatic emergency braking system to brake as fast as possible when a close object is detected. Consistency is also necessary, as sensor fusion happens with sensor data produced at the same logical time. Even if this is not implemented in our simplified example, sensor fusion in a more general scenario helps rule out false positives, i.e., cases in which one of the sensors erroneously detects a close object that would induce an unnecessary and dangerous braking. False positives are caused by the weaknesses of the specific sensor. For example, rainy or foggy weather reduce the accuracy of lidar sensors. The key concept is to gather data produced at the same logical time by all sensors and combine them to have a more accurate estimate of possible collisions. Consistency and in-order data processing are then required.
109+
110+
#### Consistency challenge
111+
The application is once agin implemented as a federated program with decentralized coordination.
112+
Consistency problems may arise when a federate receives data from two or more federates, as it is the case of the `AutomaticEmergencyBraking` reactor.
113+
The controller expects to receive input from both sensors at times 0ms, 100ms, 200ms, etc. Let's consider as an example the case where the remote connection between the controller and the radar has a slightly larger delay than that between the controller and the lidar. The lidar input will then always arrive slightly earlier than the radar one. When the controller receives the lidar input, should it process the data immediately, or should it wait for the radar input to come? Sensor fusion requires consistency: if the controller processes the input from the lidar and then the radar data comes, the control action elaborated upon the arrival of the lidar data does not take into account both sensors, even though it should. Hence, in our use case, the `AutomaticEmergencyBraking` reactor needs to wait for both inputs before processing new data.
114+
115+
In our application, we aim to process all incoming data with the same logical time to realize sensor fusion. Hence, we set `maxwait = forever` to _indefinitely wait_ for the radar input before processing the radar.
116+
117+
#### Availability challenge
118+
However, setting `maxwait` to `forever` creates problems when only the lidar input is expected (50ms, 150ms, 250ms, etc): the controller cannot process that input until an input from the radar comes, because `maxwait` will never expire. For example, if the single lidar input comes at time 50ms, it has to wait until time 100ms before being processed. If that input was signaling the presence of a close object, the detection would be delayed by 50ms, which may potentially mean crashing into the object. The automatic emergency braking system must be available, otherwise it might not brake in time to avoid collisions.
119+
The ideal `maxwait` value for maximum availability in the time instants with only the lidar input is 0, because if a single input is expected, no wait is necessary.
120+
121+
Summing up, consistency for sensor fusion requires `maxwait = forever` when inputs from both sensors are expected, while availability calls for `maxwait = 0` when only the lidar input is coming. The two values are at odds, and any value in between would mean sacrificing both properties at the same time.
122+
123+
### Dynamic adjustment of `maxwait`
124+
The knowledge of the timing properties of the application under analysis enables the _a priori_ determination of the time instants when both inputs are expected and those when only the lidar has new data available.
125+
Lingua Franca allows to dynamically change the `maxwait` in the reaction body using the `lf_set_maxwait` API, that takes as input parameter the new `maxwait` value to set.
126+
This capability of the language permits the automatic emergency braking federate to:
127+
* start with `maxwait` statically set to `forever`, because at time 0 (startup) both sensors produce data;
128+
* set `maxwait` to 0 after processing both inputs with the same logical time, because the next data will be sent by the lidar only;
129+
* set `maxwait` back to `forever` after processing the radar input alone, because the next data will be sent by both sensors.
130+
131+
This dynamic solution guarantees both consistency and availability in all input cases.
132+
The implementation and the instantiation of the `AutomaticEmergencyBraking` reactor are shown below:
133+
134+
```lf-c
135+
reactor AutomaticEmergencyBraking(dist_thld: float = 20.0) {
136+
input lidar_in: float
137+
input radar_in: float
138+
output brake: int
139+
state n_invocs: int = 0
140+
141+
reaction (lidar_in, radar_in) -> brake {=
142+
if (lf_is_present(lidar_in) && lidar_in->value < self->dist_thld) {
143+
printf("Lidar has detected close object -> signaling braking\n");
144+
lf_set(brake, 1);
145+
lf_request_stop();
146+
} else if (lf_is_present(radar_in) && radar_in->value < self->dist_thld) {
147+
printf("Radar has detected close object -> signaling braking\n");
148+
lf_set(brake, 1);
149+
lf_request_stop();
150+
}
151+
152+
self->n_invocs++;
153+
if (self->n_invocs % 2) {
154+
lf_set_maxwait(0);
155+
} else {
156+
lf_set_maxwait(FOREVER);
157+
}
158+
=} deadline(100ms) {=
159+
printf("AEB deadline violated\n");
160+
=} maxwait {=
161+
printf("STP violation on AEB\n");
162+
=}
163+
164+
federated reactor {
165+
lidar = new Lidar()
166+
radar = new Radar()
167+
168+
@maxwait(forever)
169+
aeb = new AutomaticEmergencyBraking()
170+
171+
brake = new BrakingSystem()
172+
173+
lidar.lidar_data -> aeb.lidar_in
174+
radar.radar_data -> aeb.radar_in
175+
aeb.brake -> brake.signal
176+
}
177+
}
178+
```
179+
180+
The `dist_thld` parameter is the distance threshold from detected objects below which the `AutomaticEmergencyBraking` reactor activates the brakes.
181+
The reaction body reads the distance reported by both the lidar and the radar, and if any of these is less than the threshold, it sends a signal to the `BrakingSystem` reactor.
182+
The `n_invocs` integer state variable counts the number of times the reaction of the `AutomaticEmergencyBraking` reactor is invoked. This variable is used to determine how many inputs the reaction will see at the next invocation and set the `maxwait` accordingly. Even invocation numbers mean that the next reaction invocation will happen with both sensor inputs present, so `maxwait` is set to `forever`; with odd invocation numbers, the next reaction invocation will see new data from the lidar only, and `maxwait` is then set to 0.

0 commit comments

Comments
 (0)