Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
101 changes: 101 additions & 0 deletions docs/assets/code/c/src/TransientFederates.lf
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
target C {
timeout: 3 s
}

preamble {=
#include <stdlib.h>
#include <stdio.h>
=}

/** Persistent upstream federate. Sends 0, 1, 2, ... every 500 ms. */
reactor Up(period: time = 500 ms) {
output out: int
timer t(0, period)
state count: int = 0

reaction(t) -> out {=
lf_set(out, self->count);
lf_print("Up sending %d", self->count);
self->count++;
=}
}

/**
* Launches a federate executable after `launch_time`.
* The executable name is `federate__` followed by `fed_instance_name`.
*/
reactor TransientExec(launch_time: time = 0, fed_instance_name: char* = "instance") {
timer t(launch_time)

reaction(t) {=
char cmd[576];
snprintf(cmd, sizeof(cmd), "%s/bin/federate__%s -i %s",
LF_FED_PACKAGE_DIRECTORY,
self->fed_instance_name,
lf_get_federation_id());
Comment thread
edwardalee marked this conversation as resolved.
Outdated
lf_print("Launching: %s", cmd);
if (system(cmd) != 0) {
lf_print_error_and_exit("Failed to launch federate__%s", self->fed_instance_name);
}
=}
}

/**
* Transient federate that forwards inputs from `Up` to `Down`.
* After two inputs it leaves the federation by calling `lf_stop()`.
*/
reactor Middle {
input in: int
output out: int
output join: int
state count: int = 0

reaction(startup) -> join {=
tag_t t = lf_tag_start_effective();
lf_print("Middle joined at effective start tag (" PRINTF_TIME ", %u)",
t.time - lf_time_start(), t.microstep);
lf_set(join, 0);
=}

reaction(in) -> out {=
self->count++;
lf_print("Middle forwarding %d (count %d)", in->value, self->count);
lf_set(out, in->value);
if (self->count == 2) {
lf_stop();
}
=}
}

/** Persistent downstream federate. Continues even while Middle is absent. */
reactor Down(period: time = 500 ms) {
timer t(0, period)
input in: int
input join: int

reaction(t) {=
lf_print("Down timer at (" PRINTF_TIME ", %u)",
lf_time_logical_elapsed(), lf_tag().microstep);
=}

reaction(join) {=
lf_print("Down observed Middle join");
=}

reaction(in) {=
lf_print("Down received %d from Middle", in->value);
=}
}

federated reactor {
midExec = new TransientExec(launch_time = 1 s, fed_instance_name = "mid")
up = new Up()
down = new Down()

@transient
mid = new Middle()

up.out -> mid.in
mid.join -> down.join
mid.out -> down.in
}
102 changes: 102 additions & 0 deletions docs/assets/code/py/src/TransientFederates.lf
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
target Python {
timeout: 3 s
}

preamble {=
import os
import subprocess
=}
Comment thread
ChadliaJerad marked this conversation as resolved.

# Persistent upstream federate. Sends 0, 1, 2, ... every 500 ms.
reactor Up(period=500 ms) {
output out
timer t(0, period)
state count = 0

reaction(t) -> out {=
out.set(self.count)
print("Up sending {}".format(self.count))
self.count += 1
=}
}

# Launches a federate executable after `launch_time`.
# The executable name is `federate__` followed by `fed_instance_name`.
# `federation_name` must match the .lf file name (without the extension).
reactor TransientExec(
launch_time=0,
federation_name="TransientFederates",
fed_instance_name="instance") {
timer t(launch_time)

reaction(t) {=
exe = os.path.join(
lf.package_directory(), "fed-gen", self.federation_name, "bin",
"federate__" + self.fed_instance_name)
cmd = [exe, "-i", lf.get_federation_id()]
print("Launching:", cmd)
result = subprocess.run(cmd)
if result.returncode != 0:
sys.exit("Failed to launch " + self.fed_instance_name)
=}
}

# Transient federate that forwards inputs from `Up` to `Down`.
# After two inputs it leaves the federation by calling `lf.stop()`.
reactor Middle {
input inp
output out
output join
state count = 0

reaction(startup) -> join {=
t = lf.tag_start_effective()
print("Middle joined at effective start tag ({}, {})".format(
t.time - lf.time.start(), t.microstep))
join.set(0)
=}

reaction(inp) -> out {=
self.count += 1
print("Middle forwarding {} (count {})".format(inp.value, self.count))
out.set(inp.value)
if self.count == 2:
lf.stop()
=}
}

# Persistent downstream federate. Continues even while Middle is absent.
reactor Down(period=500 ms) {
timer t(0, period)
input inp
input join

reaction(t) {=
print("Down timer at ({}, {})".format(
lf.time.logical_elapsed(), lf.tag().microstep))
=}

reaction(join) {=
print("Down observed Middle join")
=}

reaction(inp) {=
print("Down received {} from Middle".format(inp.value))
=}
}

federated reactor {
midExec = new TransientExec(
launch_time = 1 s,
federation_name = "TransientFederates",
fed_instance_name = "mid")
up = new Up()
down = new Down()

@transient
mid = new Middle()

up.out -> mid.inp
mid.join -> down.join
mid.out -> down.inp
}
12 changes: 12 additions & 0 deletions docs/glossary/glossary.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ description: Glossary of terms used in the Lingua Franca documentation.

Glossary of terms used in the Lingua Franca (LF) documentation.

### Federate
A top-level reactor instance in a [federation](#federation). The compiler generates a separate program for each federate.

### Federation
A distributed Lingua Franca program, specified with a `federated reactor`. The compiler generates a separate program for each top-level reactor instance (each **federate**) plus, for most targets, an RTI that coordinates startup and shutdown. See [Distributed Execution](../writing-reactors/distributed-execution.mdx).

### Persistent Federate
A federate that must be present when a federation starts and that remains until the federation ends. Federates are persistent unless they are marked `@transient`. See [Transient Federates](../writing-reactors/transient-federates.mdx).

### Transient Federate
A federate marked `@transient`. It need not be present when the federation starts, and it may join and leave during execution. Supported for the C and Python targets. See [Transient Federates](../writing-reactors/transient-federates.mdx).

### LF File
A source file with the `.lf` or `.ulf` extension, representing a Lingua Franca (LF) program. The `.ulf` extension is used for [micro-LF](https://micro-lf.org) programs, and the `.lf` extension is used for all other LF programs.

Expand Down
10 changes: 8 additions & 2 deletions docs/reference/target-language-details.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1706,8 +1706,9 @@ There are also some useful functions for accessing physical time:
- `instant_t lf_time_physical()`: Get the current physical time.
- `instant_t lf_time_physical_elapsed()`: Get the physical time elapsed since program start.
- `instant_t lf_time_start()`: Get the starting physical and logical time.
- `tag_t lf_tag_start_effective()`: Get the tag at which this federate effectively started. For a [transient federate](../writing-reactors/transient-federates.mdx) that joins a running federation, this may be later than the federation start tag.

The last of these is both a physical and logical time because, at the start of execution, the starting logical time is set equal to the current physical time as measured by a local clock.
The last two of these relate to start time. `lf_time_start()` is both a physical and logical time because, at the start of execution, the starting logical time is set equal to the current physical time as measured by a local clock.

A reaction can examine the current logical time (which is constant during the execution of the reaction). For example, consider the [GetTime](https://github.com/lf-lang/lingua-franca/blob/master/test/C/src/GetTime.lf) example:

Expand Down Expand Up @@ -1951,8 +1952,9 @@ There are also some useful functions for accessing physical time:
- `lf.time.physical() -> int`: Get the current physical time.
- `lf.time.physical_elapsed() -> int`: Get the physical time elapsed since program start.
- `lf.time.start() -> int`: Get the starting physical and logical time.
- `lf.tag_start_effective() -> Tag`: Get the tag at which this federate effectively started. For a [transient federate](../writing-reactors/transient-federates.mdx) that joins a running federation, this may be later than the federation start tag.

The last of these is both a physical and a logical time because, at the start of execution, the starting logical time is set equal to the current physical time as measured by a local clock.
The start time from `lf.time.start()` is both a physical and a logical time because, at the start of execution, the starting logical time is set equal to the current physical time as measured by a local clock.

A reaction can examine the current logical time (which is constant during the execution of the reaction). For example, consider the [GetTime.lf](https://github.com/lf-lang/lingua-franca/blob/master/test/Python/src/GetTime.lf) example:

Expand Down Expand Up @@ -2820,13 +2822,17 @@ For micro-LF documentation, see [micro-lf.org](https://micro-lf.org).

A reaction may request that the execution stop after all events with the current timestamp have been processed by calling the built-in method `request_stop()`, which takes no arguments. In a non-federated execution, the actual last tag of the program will be one microstep later than the tag at which `request_stop()` was called. For example, if the current tag is `(2 seconds, 0)`, the last (stop) tag will be `(2 seconds, 1)`. In a federated execution, however, the stop time will likely be larger than the current logical time. All federates are assured of stopping at the same logical time.

To stop only the calling federate, without requesting that the rest of the federation stop, call `lf_stop()`. That is used by [transient federates](../writing-reactors/transient-federates.mdx) to leave a running federation.

> The [timeout](<../writing-reactors/termination.mdx#timeout>) target property will take precedence over this function. For example, if a program has a timeout of `2 seconds` and `request_stop()` is called at the `(2 seconds, 0)` tag, the last tag will still be `(2 seconds, 0>)`.

</ShowIf>
<ShowIf py>

A reaction may request that the execution stop after all events with the current timestamp have been processed by calling the built-in method `lf.request_stop()`, which takes no arguments. In a non-federated execution, the actual last tag of the program will be one microstep later than the tag at which `lf.request_stop()` was called. For example, if the current tag is `(2 seconds, 0)`, the last (stop) tag will be `(2 seconds, 1)`. In a federated execution, however, the stop time will likely be larger than the current logical time. All federates are assured of stopping at the same logical time.

To stop only the calling federate, without requesting that the rest of the federation stop, call `lf.stop()`. That is used by [transient federates](../writing-reactors/transient-federates.mdx) to leave a running federation.

> The [timeout](<../writing-reactors/termination.mdx#timeout>) target property will take precedence over this function. For example, if a program has a timeout of `2 seconds` and `request_stop()` is called at the `(2 seconds, 0)` tag, the last tag will still be `(2 seconds, 0>)`.

</ShowIf>
Expand Down
4 changes: 4 additions & 0 deletions docs/sidebars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ const sidebars: SidebarsConfig = {
"type": "doc",
"id": "writing-reactors/distributed-execution"
},
{
"type": "doc",
"id": "writing-reactors/transient-federates"
},
{
"type": "doc",
"id": "writing-reactors/polyglot"
Expand Down
4 changes: 4 additions & 0 deletions docs/writing-reactors/distributed-execution.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,10 @@ The time value is specified by a positive integer followed by units, one of `ns`
</ShowOnly>

When a federate receives the starting time from the RTI, then it will wait until its local physical clock matches or exceeds that starting time. Thus, to the extent that the machines have [synchronized clocks](#clock-synchronization), the federates will all start executing at roughly the same physical time, a physical time close to the starting logical time.

<ShowOnly c py>
By default, the RTI waits for **every** federate to register before choosing that start time. You can instead mark some federates `@transient` so that they need not be present at startup and can join and leave during execution. See [Transient Federates](./transient-federates.mdx).
</ShowOnly>
</ShowOnly>

<ShowOnly uc>
Expand Down
2 changes: 1 addition & 1 deletion docs/writing-reactors/polyglot.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ A [federated](./distributed-execution.mdx) Lingua Franca program normally has al

:::note

The Polyglot target is preliminary. It currently supports only `C` and `Python` federates (a `CCpp` federate is treated as `C`). The main reactor must be a `federated reactor`, and the federation is coordinated by the same RTI used by single-language federations. All of the concepts described in [Distributed Execution](./distributed-execution.mdx) — the RTI, coordinated start and shutdown, centralized and decentralized coordination, clock synchronization, and security — apply to Polyglot federations as well.
The Polyglot target is preliminary. It currently supports only `C` and `Python` federates (a `CCpp` federate is treated as `C`). The main reactor must be a `federated reactor`, and the federation is coordinated by the same RTI used by single-language federations. All of the concepts described in [Distributed Execution](./distributed-execution.mdx) — the RTI, coordinated start and shutdown, centralized and decentralized coordination, clock synchronization, and security — apply to Polyglot federations as well. [Transient federates](./transient-federates.mdx) are supported because both C and Python support them.
Comment thread
edwardalee marked this conversation as resolved.
Outdated

:::

Expand Down
4 changes: 4 additions & 0 deletions docs/writing-reactors/termination.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ When the RTI receives a **STOP_REQUEST** message from a federate, it forwards it

When a federate receives a **STOP_REQUEST** message, it replies with its current logical time _t_, completes its current tag (if one is progress), and blocks, waiting for a **STOP_GRANTED** message from the RTI. When it gets the reply with payload _s_, if _s_ > _t_, then it sets `timeout` = _s_ and continues executing, using the timeout mechanism (see above) to stop. If _s_ = _t_, then it schedules the shutdown phase to occur one microstep later, as in the unfederated case.

<ShowOnly c py>
To stop **only the calling federate** without stopping the federation, call <ShowOnly c inline>`lf_stop()`</ShowOnly><ShowOnly py inline>`lf.stop()`</ShowOnly>. That is the mechanism [transient federates](./transient-federates.mdx) use to leave while the rest of the federation continues.
</ShowOnly>

</ShowOnly>

## External Signal
Expand Down
Loading
Loading