Skip to content

Commit f187cfe

Browse files
committed
fix(s001): the CI serve step curled the heartbeat port, and the heartbeat fired
Run 34080826739 answered the Windows question and the answer supersedes a claim I have made five times. **Defect 10.** `grep -o '127.0.0.1:[0-9]*' | head -1` takes the first loopback address in serve.log. On Windows that is ExTauri.ShutdownManager's heartbeat socket, not the endpoint: the module selects a Unix domain socket on macOS and Linux and TCP on Windows, because the BEAM cannot listen on a Unix socket there. The two runners that passed printed no loopback line before the endpoint's, so head -1 was right by luck. Now matches the endpoint line. **The heartbeat fired.** [warning] [ExTauri.ShutdownManager] Heartbeat timeout (1824ms) - Tauri frontend appears to have exited 1824 ms, well inside AC8's 5 s bound. I have written in NOTES.md, in PROOF.md under AC8, in the Answer on the F1 issue and in the G3 record that the heartbeat has never been observed to fire. That is now false. Earlier measurements missed it because the dev path killed the sidecar in ~15 ms by a faster route and the production-shape runs never armed it. My own curl armed it: connecting to the heartbeat port is a frontend connection as far as the ShutdownManager can tell. So 'Windows does not serve' would also have been wrong — the app was serving and shut down because I connected to its shutdown channel. **Follow-up F2**, recorded not fixed: on Windows the heartbeat is an unauthenticated loopback TCP port whose number is written to a file for discovery. Any local process that connects and disconnects shuts Trinity down — a stray curl did it by accident. Design question for slice 100 alongside F1; macOS and Linux use a Unix socket with filesystem permissions. mix gate exit=0. Signed-off-by: Ayla Croft <aylacroft@proton.me>
1 parent f95ca4d commit f187cfe

2 files changed

Lines changed: 84 additions & 1 deletion

File tree

‎.github/workflows/package.yml‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,8 +204,19 @@ jobs:
204204
start=$(now_ms)
205205
PHX_SERVER=true "./burrito_out/${{ matrix.artifact }}" --no-halt > serve.log 2>&1 &
206206
PID=$!
207+
# Match the ENDPOINT line, not any loopback address. `grep -o '127.0.0.1:[0-9]*' |
208+
# head -1` picked the wrong port on Windows and only on Windows: there,
209+
# ExTauri.ShutdownManager's heartbeat listens on a **TCP** socket because the BEAM
210+
# cannot listen on a Unix domain socket on that OS, so serve.log reads
211+
#
212+
# [ExTauri.ShutdownManager] Started - heartbeat monitoring active on 127.0.0.1:58911
213+
# Running TrinityWeb.Endpoint with Bandit 1.12.5 at 127.0.0.1:58912 (http)
214+
#
215+
# and the first match is the heartbeat. macOS and Linux use a Unix socket, print no
216+
# loopback line before the endpoint, and were right by luck.
207217
for _ in $(seq 1 240); do
208-
PORT=$(grep -o '127\.0\.0\.1:[0-9]*' serve.log | head -1 | cut -d: -f2 || true)
218+
PORT=$(grep -oE 'Running TrinityWeb\.Endpoint with Bandit [0-9.]+ at 127\.0\.0\.1:[0-9]+' serve.log \
219+
| head -1 | grep -oE '[0-9]+$' || true)
209220
[ -n "${PORT:-}" ] && break
210221
sleep 0.5
211222
done

‎slices/001-packaging-spike/NOTES.md‎

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1521,3 +1521,75 @@ clean runner: a file I made by hand, an environment I never switched, a `date` e
15211521

15221522
**Windows has still never been observed serving.** That is the one CI question slice 001 leaves
15231523
open, and it is recorded as open rather than inferred from the two runners that do.
1524+
1525+
---
1526+
1527+
## The Windows serve answer, and it supersedes a claim I made five times
1528+
1529+
Dated 2026-09-07, run `34080826739`, job `windows x86_64`. The bounded loop and the
1530+
`tail -40 serve.log` on failure — both added an hour earlier for exactly this — turned a hang
1531+
into an answer, and the answer is better than a pass would have been.
1532+
1533+
```
1534+
no HTTP response on port 58911 after 60 s
1535+
--- serve.log ---
1536+
[warning] ExTauri currently targets OTP 27 but you are running OTP 28.
1537+
[info] [ExTauri.ShutdownManager] Started - heartbeat monitoring active on 127.0.0.1:58911
1538+
[error] Exqlite.Connection ("db_conn_1") failed to connect: ** (Exqlite.Error) database is locked
1539+
[info] Running TrinityWeb.Endpoint with Bandit 1.12.5 at 127.0.0.1:58912 (http)
1540+
[warning] [ExTauri.ShutdownManager] Heartbeat timeout (1824ms) - Tauri frontend appears to have exited
1541+
```
1542+
1543+
### Defect 10: I curled the wrong port, and only Windows could show it
1544+
1545+
`grep -o '127\.0\.0\.1:[0-9]*' serve.log | head -1` takes the **first** loopback address in the
1546+
log. On Windows that is **the heartbeat socket, 58911**, not the endpoint, **58912**.
1547+
1548+
`ExTauri.ShutdownManager` selects its transport from the OS — a **Unix domain socket** on macOS
1549+
and Linux, **TCP on loopback** on Windows, because the BEAM cannot listen on a Unix socket
1550+
there. So on the two runners that passed, no loopback line is printed before the endpoint's and
1551+
`head -1` was **right by luck**. Fixed to match the endpoint line itself.
1552+
1553+
### And the heartbeat fired. **This supersedes "it has never been observed to fire."**
1554+
1555+
I have written, in `NOTES.md`, in `PROOF.md` under AC8, in the Answer on the F1 issue, and in
1556+
the G3 record, that *across every measurement in this slice the heartbeat's timer has never been
1557+
observed to fire.* **That is now false, and the line that made it false is above:**
1558+
1559+
```
1560+
[warning] [ExTauri.ShutdownManager] Heartbeat timeout (1824ms) - Tauri frontend appears to have exited
1561+
```
1562+
1563+
**1824 ms, inside the 1500 ms + 500 ms check interval the module documents, and well inside
1564+
AC8's 5 s bound.** The mechanism works. Every earlier measurement missed it for a reason that
1565+
was true at the time and was never the whole story:
1566+
1567+
* the dev path killed the sidecar in ~15 ms by a faster route, so the timer had no chance;
1568+
* the production-shape runs had no frontend at all, so the timeout was never armed.
1569+
1570+
Here it armed and fired, and my own `curl` is what armed it: connecting to 58911 **is** a
1571+
frontend connection as far as the ShutdownManager can tell. `curl` connected, sent an HTTP
1572+
request the heartbeat protocol does not understand, closed, and 1824 ms later the app shut
1573+
itself down — which is why nothing ever answered on 58912.
1574+
1575+
**So the "Windows does not serve" reading would have been wrong too.** The app was serving. It
1576+
shut down because I connected to its heartbeat.
1577+
1578+
### Follow-up F2: on Windows the heartbeat is an unauthenticated loopback TCP port
1579+
1580+
Stated as a finding rather than fixed here, because it is a design question for slice 100 and
1581+
not packaging wiring.
1582+
1583+
On Windows the shutdown channel is a TCP socket on `127.0.0.1` with no authentication and no
1584+
protocol check that I can see from this evidence. **Any local process that connects to it and
1585+
disconnects will shut Trinity down**, and the port is discoverable — the module writes it to
1586+
`<tmpdir>/tauri_heartbeat_<app_name>.port` so the frontend can find it. A stray `curl` did it by
1587+
accident in CI.
1588+
1589+
That is a denial-of-service surface on the one OS where the transport is TCP, and it belongs
1590+
with the liveness contract F1 already sends to slice 100. On macOS and Linux the Unix socket
1591+
carries filesystem permissions and does not have this shape.
1592+
1593+
**Three of my claims in this slice have been corrected by a measurement rather than by review**:
1594+
"reparented to init", "AC4 needs a machine", and now "the heartbeat never fires". Each was
1595+
stated with evidence that was real and partial.

0 commit comments

Comments
 (0)