Skip to content

Commit 0da8bee

Browse files
authored
Merge pull request #3 from lf-lang/refine
Fix inconsistency, add tmux instructions, and add lff in CI test.
2 parents 14ae69b + 7060d0b commit 0da8bee

23 files changed

Lines changed: 346 additions & 246 deletions

.github/workflows/lf-ci.yml

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,21 @@ jobs:
3030
sudo apt-get update
3131
sudo apt-get install -y build-essential cmake
3232
33+
- name: Check LF files are formatted
34+
uses: lf-lang/action-check-lf-files@main
35+
with:
36+
check_mode: "format"
37+
search_dir: "src"
38+
compiler_ref: "master"
39+
3340
- name: Check LF files compile
3441
uses: lf-lang/action-check-lf-files@main
3542
with:
3643
check_mode: "compile"
3744
no_compile_flag: false
3845
search_dir: "src"
3946
compiler_ref: "master"
47+
skip_clone: true
4048

4149
- name: Smoke-run generated LF programs
4250
shell: bash
@@ -54,15 +62,12 @@ jobs:
5462
for file in "${lf_files[@]}"; do
5563
name="$(basename "$file" .lf)"
5664
57-
launch_script="bin/${name}_launch.sh"
5865
executable="bin/${name}"
5966
60-
if [[ -x "$launch_script" ]]; then
61-
runner="$launch_script"
62-
elif [[ -x "$executable" ]]; then
67+
if [[ -x "$executable" ]]; then
6368
runner="$executable"
6469
else
65-
echo "No generated executable or launch script found for $file."
70+
echo "No generated launcher found at $executable for $file."
6671
exit 1
6772
fi
6873

01-actor-model.md

Lines changed: 66 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ In the classical actor model, components communicate via **asynchronous message
2020

2121
Here is what our system looks like:
2222

23-
![DistibutedPowerGrid Actor](fig/DistibutedPowerGrid1_Actor.svg)
23+
![Step 1 actor model diagram](fig/Step1_Actor.svg)
2424

2525

2626
The squiggly arrows (`~>`) are **physical connections** in Lingua Franca: they use TCP for reliable in-order delivery on each link, but carry **no timestamp coordination** between links. Messages from California and New York may arrive at either grid manager in any order.
@@ -35,51 +35,98 @@ The core reactor is `SimpleGridManager`:
3535

3636
```lf
3737
reactor SimpleGridManager {
38-
input in1: int // commands arriving from local operator
39-
input in2: int // commands arriving from remote operator
38+
input in1: int // commands arriving from California
39+
input in2: int // commands arriving from New York
4040
output out: int // current balance reported back to local operator
4141
4242
state balance: int = 0
4343
4444
reaction(in1, in2) -> out {=
4545
if (in1->is_present) {
4646
self->balance += in1->value;
47-
lf_print("Local command %+d MW -> balance now %d MW",
47+
lf_print("California command %+d MW -> balance now %d MW",
4848
in1->value, self->balance);
4949
}
5050
if (in2->is_present) {
5151
self->balance += in2->value;
52-
lf_print("Remote command %+d MW -> balance now %d MW",
52+
lf_print("New York command %+d MW -> balance now %d MW",
5353
in2->value, self->balance);
5454
}
5555
lf_set(out, self->balance);
5656
=}
5757
}
5858
```
5959

60-
The top-level federated program wires everything together:
60+
The top-level federated program wires everything together. For the first exercise, the operator consoles are scripted with parameters, so you can change the trace without writing new reactors or timers:
6161

6262
```lf
6363
federated reactor {
64-
op1 = new GridInterface(...) // California operator console
65-
op2 = new GridInterface(...) // New York operator console
66-
gm1 = new SimpleGridManager()
67-
gm2 = new SimpleGridManager()
68-
69-
op1.command ~> gm1.in1 // California commands -> California manager (local)
70-
op2.command ~> gm2.in2 // New York commands -> New York manager (local)
71-
op1.command ~> gm2.in1 // California commands -> New York manager (remote)
72-
op2.command ~> gm1.in2 // New York commands -> California manager (remote)
73-
74-
gm1.out ~> op1.status
75-
gm2.out ~> op2.status
64+
gi1 = new ScriptedGridInterface(
65+
node_name="California",
66+
command_value=100,
67+
command_time=0 ms
68+
)
69+
gi2 = new ScriptedGridInterface(
70+
node_name="New York",
71+
command_value=-100,
72+
command_time=1 ms
73+
)
74+
gm1 = new SimpleGridManager(node_name="California manager")
75+
gm2 = new SimpleGridManager(node_name="New York manager")
76+
77+
gi1.command ~> gm1.in1 // California commands -> California manager (local)
78+
gi2.command ~> gm2.in2 // New York commands -> New York manager (local)
79+
gi1.command ~> gm2.in1 // California commands -> New York manager (remote)
80+
gi2.command ~> gm1.in2 // New York commands -> California manager (remote)
81+
82+
gm1.out ~> gi1.status
83+
gm2.out ~> gi2.status
7684
}
7785
```
7886

7987
Each grid manager receives commands from **both** operators and keeps its own copy of the balance. The local operator console gets the balance back from its local manager.
8088

8189
---
8290

91+
## Running Step 1
92+
93+
Compile the LF program with `lfc`:
94+
95+
```bash
96+
lfc src/Step1_Actor.lf
97+
```
98+
99+
Because this is a federated LF program, compilation generates a launcher under `bin/` named after the source file:
100+
101+
```bash
102+
./bin/Step1_Actor
103+
```
104+
105+
This launches the runtime infrastructure (RTI) and the four federates:
106+
107+
- `federate__gi1`: California grid interface
108+
- `federate__gi2`: New York grid interface
109+
- `federate__gm1`: California grid manager
110+
- `federate__gm2`: New York grid manager
111+
112+
To see each federate in its own terminal pane, run the launcher with `--tmux`:
113+
114+
```bash
115+
./bin/Step1_Actor --tmux
116+
```
117+
118+
If `tmux` is not installed, install it first, for example with `brew install tmux` on macOS or `sudo apt-get install tmux` on Ubuntu.
119+
120+
Inside the tmux view, the top pane is the RTI and the other panes are the federates. The program has a built-in timeout, so it should finish on its own. To leave and close the entire tmux session after the run, press `Ctrl+B`, then `D`. If you need to stop a still-running federation, press `Ctrl+C` in the RTI pane, then detach with `Ctrl+B`, then `D`.
121+
122+
Example tmux run:
123+
124+
![Step 1 tmux run](assets/step1-actor-tmux.png)
125+
126+
In the screenshot, the managers receive the California and New York commands in different orders, but both managers end with balance `0 MW`.
127+
128+
---
129+
83130
## Why This Works, Sometimes
84131

85132
The operation `balance += value` has a special mathematical property: it is **associative and commutative**. It doesn't matter what order the additions happen; the final sum is always the same.
@@ -108,7 +155,7 @@ That's what we explore next.
108155

109156
## Exercises
110157

111-
1. Trace through a scenario: California dispatches +100 MW at time 0, New York curtails −100 MW at time 1 ms. Show that both grid managers reach the same balance regardless of message arrival order.
158+
1. Trace through a scenario: California dispatches +100 MW at time 0 ms, New York curtails −100 MW at time 1 ms. Show that both grid managers reach the same balance regardless of message arrival order.
112159

113160
2. What would happen if TCP delivery were *not* guaranteed? How would the ACID 2.0 / CRDT properties need to change?
114161

02-inconsistency.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ reactor InconsistentGridManager {
4444

4545
Here is what our system looks like:
4646

47-
![DistibutedPowerGrid2_Inconsistency](fig/DistibutedPowerGrid2_Inconsistency.svg)
47+
![Step 2 inconsistency diagram](fig/Step2_Inconsistency.svg)
4848

4949

5050
---

03-timestamps.md

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,12 @@ The only syntactic change in the Lingua Franca program is replacing physical con
3030

3131
```lf
3232
// Before (Step 2): physical, unordered
33-
op1.command ~> gm1.in1
34-
op1.command ~> gm2.in1
33+
gi1.command ~> gm1.in1
34+
gi1.command ~> gm2.in1
3535
3636
// After (Step 3): logical, timestamp-ordered
37-
op1.command -> gm1.in1
38-
op1.command -> gm2.in1
37+
gi1.command -> gm1.in1
38+
gi1.command -> gm2.in1
3939
```
4040

4141
This small change has a profound effect: LF now gives both `gm1` and `gm2` the same logical timestamps and a deterministic rule for processing them, provided the coordination assumptions are met.
@@ -46,7 +46,7 @@ This small change has a profound effect: LF now gives both `gm1` and `gm2` the s
4646

4747
See [`src/Step3_Timestamps.lf`](src/Step3_Timestamps.lf). And here is what our system looks like:
4848

49-
![DistributedPowerGrid3 TimeStamped](fig/DistibutedPowerGrid3_TimeStamped.svg)
49+
![Step 3 timestamped diagram](fig/Step3_Timestamps.svg)
5050

5151

5252
Key differences from Step 2:
@@ -108,10 +108,23 @@ For two nodes in California and New York (cross-continental latency ~60–80 ms)
108108
Tardy events, if not handled, result in messages like this:
109109

110110
```
111-
Fed 1 (op2_main): ERROR: STP violation occurred in a trigger to reaction 3, and there is no handler.
111+
Fed 3 (gm2_main): ERROR: STP violation occurred in a trigger to reaction 1, and there is no handler.
112112
**** Invoking reaction at the wrong tag!
113113
```
114114

115+
To intentionally trigger this error in [`src/Step3_Timestamps.lf`](src/Step3_Timestamps.lf):
116+
117+
1. Temporarily remove the `tardy {= ... =}` block attached to the `GridManager` reaction.
118+
2. Make both `GridManager` `@maxwait` values very small, for example `@maxwait(1 us)` or `@maxwait(0 ms)`.
119+
3. Compile and run:
120+
121+
```bash
122+
lfc src/Step3_Timestamps.lf
123+
./bin/Step3_Timestamps
124+
```
125+
126+
With a very small `maxwait`, a grid manager may advance logical time before an earlier remote message arrives. Without a tardy handler, the runtime reports the STP violation. After observing the error, restore the `tardy` block and return `maxwait` to `100 ms`.
127+
115128
Tardy events may be handled with a **tardy handler**.
116129
For example, we can add the following to the `GridManager` reaction:
117130

04-conservative.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ This is the CAL theorem in action: **strong consistency costs availability when
100100

101101
See [`src/Step4_Conservative.lf`](src/Step4_Conservative.lf). Here is what our system looks like:
102102

103-
![DistibutedPowerGrid4 Chandy & Misra](fig/DistibutedPowerGrid4_ChandyMisra.svg)
103+
![Step 4 conservative coordination diagram](fig/Step4_Conservative.svg)
104104

105105
Key changes from Step 3:
106106
- `GridInterface` is wrapped in `GridServer`, which adds a null-message timer.

05-hybrid.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ The `GridManager` (from Step 4, with `maxwait = forever`) continues to maintain
2424

2525
And here is what our system looks like:
2626

27-
![DistibutedPowerGrid5 Hybrid](fig/DistibutedPowerGrid5_Hybrid.svg)
27+
![Step 5 hybrid design diagram](fig/Step5_Hybrid.svg)
2828
---
2929

3030
## The `QuickDispatch` Reactor

README.md

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# LF Tutorial @ CPS-IoT Week 2026 - Hands-on Session: Logical Time in Distributed Systems: A Power Grid Tutorial
22

3-
> **Based on:** ["Consistency vs. Availability in Distributed Cyber-Physical Systems" by Lee et al. (2023)](https://arxiv.org/abs/2301.08906)
3+
> **Based on:** ["Consistency vs. Availability in Distributed Cyber-Physical Systems" by Lee et al. (2023)](https://dl.acm.org/doi/10.1145/3609119)
44
> **Domain:** Distributed power grid control using Lingua Franca
55
66
---
@@ -9,7 +9,7 @@
99

1010
Modern power grids are distributed cyber-physical systems. Generation, transmission, and load are spread across vast geographic areas. Multiple control nodes must coordinate in real time, and they must **agree** on the state of the grid even when separated by hundreds of milliseconds of network latency.
1111

12-
This tutorial takes you through a series of progressively more sophisticated designs for a distributed grid controller, using the [Lingua Franca (LF)](https://lf-lang.org/) coordination language. Each design exposes a new problem and motivates the next solution, culminating in a system that achieves **eventual consistency** while bounding unavailability to a manageable risk.
12+
This tutorial takes you through a series of progressively more sophisticated designs for a distributed grid controller, using the [Lingua Franca (LF)](https://lf-lang.org/) coordination language. Each design exposes a new problem and motivates the next solution, ending with a hybrid design that separates fast, low-risk commands from slower, strongly consistent decisions.
1313

1414
The design journey mirrors the consistency-vs-availability tradeoff captured by the **CAL theorem**: stronger consistency requires more waiting, more assumptions about latency, or carefully chosen fault handling. Each design makes an explicit, application-specific compromise.
1515

@@ -48,9 +48,10 @@ This follows the paper's focus on shared physical state in real-time systems: in
4848
- **Physical connections** (`~>`) vs **logical connections** (`->`) in LF
4949
- **Eventual consistency** via ACID 2.0 / CRDTs
5050
- **Logical timestamps** and the notion of *logical time*
51-
- **STA** (Safe To Advance) and **STAA** (Safe To Assume Absent) parameters
51+
- **`maxwait`** as a practical safe-to-advance bound
52+
- **Tardy handlers** for messages that arrive too late
5253
- **Conservative coordination** (Chandy-Misra null messages)
53-
- **The CAL theorem**: Consistency–Availability–Latency tradeoff
54+
- **The CAL theorem**: consistency, availability, and latency tradeoff
5455
- **Fault handlers** for bounded unavailability
5556

5657
---
@@ -60,6 +61,8 @@ This follows the paper's focus on shared physical state in real-time systems: in
6061
- Basic familiarity with concurrent programming concepts
6162
- Some exposure to distributed systems (helpful but not required)
6263
- Lingua Franca installed: see [lf-lang.org](https://lf-lang.org/docs/installation)
64+
- A C build toolchain and CMake, since the examples use `target C`
65+
- `tmux` if you want to run federates in separate terminal panes
6366

6467
---
6568

@@ -83,23 +86,44 @@ After creation, clone **your** new repository locally and follow [Running the Co
8386

8487
## Running the Code
8588

86-
Each `.lf` file in the `src/` directory can be compiled and run with:
89+
All `.lf` files in this repository are federated LF programs. Compile an example with `lfc`:
8790

8891
```bash
8992
lfc src/<filename>.lf
90-
./bin/<program_name>
9193
```
9294

93-
For federated programs (Steps 3 onward), each federate runs as a separate process. The LF compiler generates a launch script:
95+
For example:
9496

9597
```bash
96-
lfc src/<filename>.lf
97-
./bin/<program_name>_launch.sh
98+
lfc src/Step1_Actor.lf
99+
```
100+
101+
Compilation generates a launcher under `bin/` with the same base name as the source file, without the `.lf` extension. For `src/Step1_Actor.lf`, the launcher is:
102+
103+
```bash
104+
./bin/Step1_Actor
98105
```
99106

100-
# References
107+
That launcher starts the runtime infrastructure (RTI) and all federates for the example. There is no `_launch.sh` script in this repository.
108+
109+
To run a different step, replace the filename and launcher name:
110+
111+
```bash
112+
lfc src/Step5_Hybrid.lf
113+
./bin/Step5_Hybrid
114+
```
115+
116+
The launcher also supports tmux panes, which can make federated output easier to read:
117+
118+
```bash
119+
./bin/Step1_Actor --tmux
120+
```
121+
122+
Step 1 exits on its own because it has a short timeout. Other steps may keep running until you stop them with `Ctrl+C` in the launcher or RTI pane.
123+
124+
## References
101125

102-
[1] E. A. Lee, R. Akella, S. Bateni, S. Lin, M. Lohstroh, and C. Menard, "Consistency vs. Availability in Distributed Cyber-Physical Systems," arXiv:2301.08906, 2023. [Online]. Available: https://arxiv.org/abs/2301.08906
126+
[1] E. A. Lee, R. Akella, S. Bateni, S. Lin, M. Lohstroh, and C. Menard, "Consistency vs. Availability in Distributed Cyber-Physical Systems," arXiv:2301.08906, 2023. [Online]. Available: https://dl.acm.org/doi/10.1145/3609119
103127

104128
[2] T. Zhao, Z. Li and Z. Ding, "Consensus-Based Distributed Optimal Energy Management With Less Communication in a Microgrid," in IEEE Transactions on Industrial Informatics, vol. 15, no. 6, pp. 3356-3367, June 2019, doi: 10.1109/TII.2018.2871562.
105129

assets/step1-actor-tmux.png

185 KB
Loading

fig/DistibutedPowerGrid1_Actor.svg

Lines changed: 0 additions & 1 deletion
This file was deleted.

fig/DistibutedPowerGrid2_Inconsistency.svg

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)