Skip to content

Give the backend calls a deadline - #266

Merged
ramonski merged 1 commit into
masterfrom
fix/http-reads-without-a-timeout
Sep 14, 2026
Merged

ramonski merged 1 commit into
masterfrom
fix/http-reads-without-a-timeout

Conversation

@ramonski

Copy link
Copy Markdown
Member

http::get and http::post connected with TcpStream::connect and read with read_to_string, neither of which has a timeout. A backend that accepts the connection and then never answers blocks the caller forever.

The caller that matters is the tray ticker. self_heal_from_backend() runs inside spawn_ticker, which is tauri::async_runtime::spawn, so a hung read pins a Tokio worker and the menu-bar pill stops updating until the app is restarted.

That is the exact outcome the surrounding code already works to prevent. Two comments in spawn_ticker explain the catch_unwind guards:

a panic in the renderer […] just skips this tick instead of killing the whole ticker task. Without this guard a single bad frame freezes the menu-bar pill indefinitely until the user restarts the app.

The guard covers panics. A hang arrived through the other door, and a blocking socket read with no deadline is the more likely of the two for a local HTTP call.

toggle_timer has the same call on its own std::thread::spawn, so there it leaks a thread rather than freezing the tray.

Five seconds on connect, read and write. Generous for a local FastAPI answering /api/clocks/active, short enough that a stuck one costs a single tick.

Tests. The crate had none; it has two now. connect_to takes the address so the test drives the real function rather than a copy of it — with the timeouts removed, a_silent_backend_does_not_block_forever fails after 30 seconds instead of passing in 5.

test http::tests::a_silent_backend_does_not_block_forever ... ok
test http::tests::a_normal_response_is_parsed ... ok
test result: ok. 2 passed

http::get and http::post connected with
TcpStream::connect and read with read_to_string, neither
of which has a timeout. A backend that accepts the
connection and then never answers blocks the caller
forever.

The caller that matters is the tray ticker.
self_heal_from_backend runs inside spawn_ticker, which is
tauri::async_runtime::spawn, so a hung read pins a Tokio
worker and the menu-bar pill stops updating until the app
is restarted.

That is the exact outcome the surrounding code already
works to prevent. Its catch_unwind comment says a single
bad frame would otherwise freeze the menu-bar pill
indefinitely until the user restarts the app. The guard
covers panics; a hang arrived through the other door, and
for a local HTTP call it is the more likely of the two.

toggle_timer makes the same call on its own thread, so
there it leaks a thread rather than freezing the tray.

Five seconds on connect, read and write.

The crate had no tests; it has two. connect_to takes the
address so the test drives the real function rather than a
copy of it: with the timeouts removed, the silent-backend
test fails after 30 seconds instead of passing in 5.
@ramonski
ramonski merged commit aa83ae4 into master Sep 14, 2026
@ramonski
ramonski deleted the fix/http-reads-without-a-timeout branch September 14, 2026 14:51
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.

1 participant