Skip to content

Port the link watchdog - #101

Open
jamiecounsell wants to merge 2 commits into
attermann:masterfrom
zerokelvin-dev:link-watchdog
Open

jamiecounsell wants to merge 2 commits into
attermann:masterfrom
zerokelvin-dev:link-watchdog

Conversation

@jamiecounsell

@jamiecounsell jamiecounsell commented Oct 25, 2026 •

Copy link
Copy Markdown

Note: this PR is stacked on #99

The link watchdog was still marked as TODO. Without it, a link request with no reply stayed pending forever, initiators never sent keepalives, and dead links were never found stale or closed.

I ported the reference loop as one pass per link, run from Transport::jobs() for each pending and active link:

  • pending/handshake links time out
  • active links send keepalives and go stale
  • stale links close after the grace period

The watchdog depends on three values that nothing was setting, so this sets them:

  • the keepalive interval, now derived from the RTT when a link comes up, as in the reference (adds KEEPALIVE_MIN/KEEPALIVE_MAX; KEEPALIVE keeps its value)
  • the time the last keepalive was sent
  • the time of the last proof received on the link

The watchdogs run after the job loop, not inside it. jobs() sets _jobs_running while its loop runs, and outbound() sleeps until it's cleared. The watchdog sends packets (keepalives, link closes), so calling it inside the loop would deadlock: outbound() waits for the loop to finish, and the loop waits for outbound(). The existing resource watchdogs had the same problem when re-requesting a missing part, so I moved them after the loop too.

Testing: running in SolarOS on a ThinkNode M9 (ESP32-S3, ESP-IDF) against Python RNS nodes over LoRa, exchanging LXMF messages. Native tests pass, and the series builds for ttgo-lora32-v21 and wiscore_rak4631.

Fixes #93

jamiecounsell and others added 2 commits October 25, 2026 12:49
validate_link_proof() had its signature check stubbed to false, so a proof
for a packet sent over a link was never accepted. The packet was never
marked delivered and its delivery callback never ran.

This restores the check from the Python reference. link is passed as
const Link& but Link::validate() isn't const, so it validates through a
copy of the link (copies share the same state).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019rUgoKJdB4U8fNS9HybY8S
The link watchdog was still marked as TODO. Without it, a link request
with no reply stayed pending forever, initiators never sent keepalives,
and dead links were never found stale or closed.

The watchdog is now one pass of the reference loop per link, run from
Transport::jobs() for each pending and active link: pending/handshake
links time out, active links send keepalives and go stale, and stale
links close after the grace period.

The watchdog depends on three values that nothing was setting, so this
sets them:

- the keepalive interval, derived from the RTT when a link comes up, as
  in the reference (adds KEEPALIVE_MIN/KEEPALIVE_MAX; KEEPALIVE keeps its
  value)
- the time the last keepalive was sent
- the time of the last proof received on the link

The watchdogs run after the job loop, not inside it. jobs() sets
_jobs_running while its loop runs, and outbound() sleeps until it's
cleared. The watchdog sends packets (keepalives, link closes), so calling
it inside the loop would deadlock: outbound() waits for the loop to
finish, and the loop waits for outbound(). The existing resource
watchdogs had the same problem when re-requesting a missing part, so they
move after the loop too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019rUgoKJdB4U8fNS9HybY8S
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Transport loop deadlocks when a resource transfer is interrupted

1 participant