Skip to content

fix(nostr): exponential reconnect backoff so unreachable relays stop exhausting the thread pool (#191) - #201

Merged
ThomasFarstrike merged 1 commit into
MicroPythonOS:mainfrom
jnuyens:nostr-reconnect-backoff
Jul 14, 2026
Merged

ThomasFarstrike merged 1 commit into
MicroPythonOS:mainfrom
jnuyens:nostr-reconnect-backoff

Conversation

@jnuyens

@jnuyens jnuyens commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Fix #2 for #191. Builds on the reconnect rework already merged to main.

What #191 asked for

The Nostr relay service retried unreachable relays with no backoff. Each attempt resolves DNS and spawns a _thread, and the ESP32-S3 pool is tiny, so hammering a dead relay starved other apps' networking (this is what broke audio playback in The Free Lantern Player). The DNS cache (#189) was fix #1; this is fix #2.

What was still wrong on main

main already normalises reconnect_interval and gates on _network_available(), which handles the "Wi-Fi fully down" case. But two gaps remained when Wi-Fi is up and a specific relay is unreachable or flapping:

  1. Clean-close tight loop. When a relay accepts the socket then drops it, _connect_and_run() returns without raising, so the except branch (with its sleep) never runs. The while self.running: loop reconnects with zero delay and hammers.
  2. No backoff. The exception path waits a fixed reconnect_interval (3s) forever, so a permanently dead relay keeps retrying every 3s.

The fix

Exponential backoff in _async_main, seeded from reconnect_interval:

  • Wait before every reconnect (not just after an exception), which closes the clean-close zero-delay loop.
  • connected_ok tracks whether the just-ended session was actually live: reset the delay to the seed interval after a live close, otherwise double it toward a 300s ceiling. An unreachable relay backs off (3s -> 6 -> 12 -> ... -> 300) instead of spinning; a healthy relay that blips recovers fast.
  • No relay is abandoned, so one that recovers reconnects on its own.

The backoff math is a pure helper (_next_backoff) so it is unit-testable without a network. reconnect_interval <= 0 still disables reconnect, and _network_available() gating is unchanged.

Scope is exactly the Nostr relay service: WebSocketApp is only used by nostr/relay.py (nostr app + NWC both go through it).

Testing

  • New tests/test_uaiowebsocket_backoff.py: 5/5 pass on the desktop build (./tests/unittest.sh tests/test_uaiowebsocket_backoff.py), network-free. Covers geometric growth, the 300s cap, reset-on-live-connection, and a custom seed interval as the floor.
  • mpy-cross compiles clean; ASCII-only, MicroPython-compatible.

@ThomasFarstrike

Copy link
Copy Markdown
Contributor

Oh that's very cool! Thanks for adding a unit test too - the more, the merrier! ;-)

@ThomasFarstrike
ThomasFarstrike merged commit 31bfb92 into MicroPythonOS:main Jul 14, 2026
7 checks passed
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.

Build process for ESP32 is broken Just wanted to let you know.

2 participants