Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/assets/code/c/src/test/SlowingClockTest.lf
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ main reactor(start:time(100 msec), incr:time(100 msec)) {
=}
reaction(a) -> a {=
instant_t elapsed_logical_time = lf_time_logical_elapsed();
printf("Logical time since start: \%lld nsec.\n",
printf("Logical time since start: %lld nsec.\n",
elapsed_logical_time
);
if (elapsed_logical_time != self->expected_time) {
printf("ERROR: Expected time to be: \%lld nsec.\n",
printf("ERROR: Expected time to be: %lld nsec.\n",
self->expected_time
);
exit(1);
Expand All @@ -33,7 +33,7 @@ main reactor(start:time(100 msec), incr:time(100 msec)) {
reaction(shutdown) {=
if (self->expected_time != MSEC(1500)) {
printf("ERROR: Expected the next expected time to be: 1500000000 nsec.\n");
printf("It was: \%lld nsec.\n", self->expected_time);
printf("It was: %lld nsec.\n", self->expected_time);
exit(2);
} else {
printf("Test passes.\n");
Expand Down
22 changes: 11 additions & 11 deletions docs/assets/code/uc/src/test/SlowingClockTest.lf
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,24 @@
* The use of the logical action ensures the elapsed time jumps exactly from
* 0 to 100, 300, 600, and 1000 msec.
*/
target uC {
timeout: 1 sec,
fast: true,
};
main reactor(start:time(100 msec), incr:time(100 msec)) {
target uC
@platform("Native")
@timeout(1 sec)
@fast(true)
main reactor(start:time = 100 msec, incr:time = 100 msec) {
logical action a;
state interval:time(start);
state expected_time:time(start);
state interval:time = start;
state expected_time:time = start;
reaction(startup) -> a {=
lf_schedule(a, self->start);
=}
reaction(a) -> a {=
instant_t elapsed_logical_time = lf_time_logical_elapsed();
printf("Logical time since start: \%lld nsec.\n",
interval_t elapsed_logical_time = env->get_elapsed_logical_time(env);
printf("Logical time since start: %lld nsec.\n",
elapsed_logical_time
);
if (elapsed_logical_time != self->expected_time) {
printf("ERROR: Expected time to be: \%lld nsec.\n",
printf("ERROR: Expected time to be: %lld nsec.\n",
self->expected_time
);
exit(1);
Expand All @@ -33,7 +33,7 @@ main reactor(start:time(100 msec), incr:time(100 msec)) {
reaction(shutdown) {=
if (self->expected_time != MSEC(1500)) {
printf("ERROR: Expected the next expected time to be: 1500000000 nsec.\n");
printf("It was: \%lld nsec.\n", self->expected_time);
printf("It was: %lld nsec.\n", self->expected_time);
exit(2);
} else {
printf("Test passes.\n");
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/security.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
ShowIf, ShowIfs, ShowOnly
} from '@site/src/components/LinguaFrancaMultiTargetUtils';

<LanguageSelector c py />
<LanguageSelector c py uc cpp ts rs/>

By default, there is no secure authentication when a federate joins a federation, and data exchanged between federates is not encrypted. For targets that support it, Lingua Franca provides robust, end-to-end communication security that encrypts all message exchanges and ensures only authorized federates can participate.

Expand Down
23 changes: 1 addition & 22 deletions docs/reference/target-declaration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
ShowIf, ShowIfs, ShowOnly,
} from '@site/src/components/LinguaFrancaMultiTargetUtils';

<LanguageSelector c cpp py rs ts />
<LanguageSelector c uc cpp py rs ts />

# Target Declaration

Expand Down Expand Up @@ -89,27 +89,6 @@ c={
workers: <non-negative integer>,
};`
}
uc={
`target uC {
auth: <true or false>
build: <string>,
build-type: <Debug, Release, RelWithDebInfo, or MinSizeRel>,
cmake-args: <object of key-value pairs>,
cmake-include: <string or list of strings>,
compiler: <string>,
compiler-flags: <string or list of strings>,
docker: <true or false>,
fast: <true or false>,
files: <string or list of strings>,
logging: <error, warning, info, log, debug>,
no-compile: <true or false>,
protobufs: <string or list of strings>,
single-threaded: <true or false>,
timeout: <time>,
trace-plugin: <object with package, library, and optional path>,
workers: <non-negative integer>,
};`
}
cpp={
`target Cpp {
build-type: <Debug, Release, RelWithDebInfo, or MinSizeRel>,
Expand Down
44 changes: 33 additions & 11 deletions docs/writing-reactors/distributed-execution.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ Each federate communicates only with its neighboring federates, those to which i

In decentralized coordination, when one federate communicates with another, it does so directly through a dedicated communication channel without going through any centralized server. Moreover, it does not consult any other entity to advance logical time. Instead, each federate uses its local physical clock and a record of the most recently tags on each input port to decide when to advance its logical time and process events.

Specifically, an input is **known** up to and including tag _g_ if a message with tag _g_ or greater has been received on that input port, or if the input port is not connected (in which case, it is known to be absent).
Specifically, an input is **known** up to and including logical time _g_ if a message with tag _g_ or greater has been received on that input port, or if the input port is not connected (in which case, it is known to be absent).
With decentralized coordination, you can bound the amount of time that a federate will wait for
an input to become known using the **maxwait** attribute<ShowOnly c py ts inline> (formerly called **safe-to-advance**, or **STA**)</ShowOnly>.
The federate can advance to tag _g_=(_t_,_m_) when either of the following conditions is satisfied:
Expand All @@ -321,24 +321,39 @@ The `maxwait` is specified as an attribute of the instantiation of a reactor, as
```
</ShowOnly>
<ShowOnly uc>
2. The federate's physical clock matches or exceeds _t_ + _m_, where _m_ is the maximum over the `maxwait` of the federate and the `maxwait` of each input port that is unknown at _g_.
2. The federate's physical clock matches or exceeds _t_ + _M_, where _M_ is the maximum of the `maxwait` values of each input port that is unknown at _t_.

The `maxwait` is specified as an attribute of the instantiation of a reactor or of a connection, as in the following example:
The `maxwait` is specified as an attribute of the connection to the input port, as in the following example:

```lf
@maxwait(0)
my_instance = new MyReactor()
@maxwait(10 ms)
upstream1.out -> my_instance.in1
upstream2.out -> my_instance.in2
```

The default `maxwait` is `forever`.
To override the default, you can specify a `maxwait` value on the instantiation of the reactor.
Hence, the following specification gives the same result as the specification above:

```lf
@maxwait(10 ms)
my_instance = new MyReactor()
upstream1.out -> my_instance.in1
@maxwait(forever)
upstream2.out -> my_instance.in2
```
In the above example, the instance will not advance to tag _g_=(_t_,_m_) until `in2` is known at _g_ because of the `@maxwait(forever)`.
Once `in2` is known at _g_, it will wait at most 10 ms beyond the logical time _t_ for input `in1` to become

In the both cases, the `my_instance` federate will not advance to tag _g_=(_t_,_m_) until `in2` is known at _g_.
Once `in2` is known at _g_,
the federate will wait at most 10 ms beyond the logical time _t_ for input `in1` to become
known, after which, if `in1` is still unknown, the federate will assume it is absent at _g_.
The `@maxwait(0)` on the federate instance overrides the default maxwait of `forever`.
</ShowOnly>

Note that the microstep _m_ is not used to determine at which physical time to assume an input is absent at tag _g_=(_t_,_m_).
Hence, microsteps should be used with caution (or avoided altogether) on federate-to-federate connections.
If you must use microsteps, the `maxwait` should be set to the time you wish to wait for the largest microstep of the inputs you are expecting.

By default, the maxwait is `forever`. A maxwait of `forever` is OK for any federate where either:

1. It has no inputs.
Expand All @@ -349,7 +364,7 @@ At the other extreme, a maxwait of zero is OK for any federate where:

1. The federate has no inputs.
2. Every logical connection into the federate has a sufficiently large `after` clause.
3. The federate has only one upstream federate sending it messages, and it has no local timers or actions.
3. The federate has only one input port, and it has no local timers or actions.

The use of `after` provides a design style similar to **logical execution time** (**LET**).
If the value of the `after` delay on each connection exceeds the sum of the [clock synchronization](#clock-synchronization) error _E_, a bound _L_ on the network latency, and the time lag on the sender _D_ (the physical time at which it sends the message minus the timestamp of the message),
Expand Down Expand Up @@ -384,6 +399,13 @@ When the message finally arrives, it will be **tardy**, resulting in a **safe-to
With the above program, this will cause a warning to be printed; the message will be processed at the earliest possible logical time, typically one microstep after the latest timer tick.
We will show below how to catch this violation and handle it.

The timer in `PrintTimer` enables the reactor to detect the missing message before it has arrived.
The tardy handler will be invoked only after the message arrives, if it ever arrives, but the reaction to the timer
will be invoked on time because of the `maxwait` of zero.
If the reaction is modified to observe the input defined in the `Print` base class,
it can check whether the message arrived on time, and if not, handle the fault immediately, rather than
waiting for the tardy message to arrive.

An alternative to the `after` delays is to add a bounded **maxwait** to downstream federates, as in the following example:

import C_DecentralizedTimerSTA from '../assets/code/c/src/DecentralizedTimerSTA.lf';
Expand All @@ -393,15 +415,15 @@ import Py_DecentralizedTimerSTA from '../assets/code/py/src/DecentralizedTimerST
<NoSelectorTargetCodeBlock c={C_DecentralizedTimerSTA} uc={UC_DecentralizedTimerSTA} py={Py_DecentralizedTimerSTA} ts={"DecentralizedTimerSTA.lf missing for TypeScript"} lf />

Note that now there is no `after` delay and the timer has offset zero.
The attribute `@maxwait(10 ms)` gives a time value, and the federate waits up to this specified amount of time (in physical time) beyond a logical time _t_ before advancing its tag to _g_ = (_t_, _m_).
The attribute `@maxwait(10 ms)` gives a time value, and the federate waits at most up to this specified amount of time (in physical time) beyond a logical time _t_ before advancing its tag to _g_ = (_t_, _m_).
In the above example, when the destination `PrintTimer` reactor wants to advance its logical time to 0, 1s, 2s, etc., it will wait at most for its physical clock to exceed these values plus the `maxwait` value.
If the input arrives before the wait expires, then it proceeds to commit to the specified logical time and invoke its two reactions (the one inherited from `Print` first followed by the one defined locally).
If the wait expires before the input arrives, it will assume the input is absent and invoke only the local reaction.
When the input later arrives (it is tardy), it will experience an STP violation.
If you change the `maxwait` value to, say, 1us, you will likely see such violations.
Just as with the use of `after`, if the `maxwait` exceeds the sum of network latency, clock synchronization error, and execution times, then all events will be processed in tag order, and no messages will be tardy.

The default maxwait is `forever` (or it can be explicitly specifies with the attribute `@maxwait(forever)`).
The default maxwait is `forever` (or it can be explicitly specified with the attribute `@maxwait(forever)`).
If we use this default for the PrintTimer above, there will never be STP violations.
The PrintTimer will not advance to tag _g_ until it has received an input with tag _g_ or greater.

Expand All @@ -410,7 +432,7 @@ If the alignment of the remote data with whatever the timer reaction does is imp
This choice emphasizes consistency between the sending and the receiving federate and makes the program deterministic.

Using a bounded `maxwait` instead, such as 10 ms, ensures that the timer reaction will be invoked in bounded time even if inputs get delayed or lost.
This choice ensures bounded unavailability at the risk of inconsistency.
This choice bounds unavailability at the risk of inconsistency.

Using a `maxwait` of zero ensures that timer-triggered reactions will execute immediately.
This choice emphasizes availability.
Expand Down
Loading