Skip to content

fix: use shared idle tracker for relay connections#424

Merged
9seconds merged 3 commits into9seconds:masterfrom
dolonet:fix/relay-idle-timeout-shared-tracker
Mar 30, 2026
Merged

fix: use shared idle tracker for relay connections#424
9seconds merged 3 commits into9seconds:masterfrom
dolonet:fix/relay-idle-timeout-shared-tracker

Conversation

@dolonet
Copy link
Copy Markdown
Contributor

@dolonet dolonet commented Mar 30, 2026

Summary

Fixes #423 — media downloads broken in v2.2.5 due to per-direction idle timeout.

connIdleTimeout (introduced in #420) set ReadDeadline/WriteDeadline independently for each direction. During media downloads, the client→telegram direction is idle at the application level while telegram→client is actively streaming. After IdleTimeout (default 1 min), the idle direction's ReadDeadline fires, which tears down the relay and kills the transfer.

The current behavior doesn't match the IdleTimeout documentation:

"if we have any message which will pass to either direction, a timer is reset"

The actual implementation tracked each direction independently — activity in one direction did not prevent timeout in the other.

Fix

Replace the per-direction timeout with a shared idleTracker — one atomic timestamp per connection pair. Both pump goroutines update it on any successful Read/Write. When ReadDeadline fires on an idle direction, we check the shared tracker: if the other direction was recently active, retry instead of closing.

The connection is torn down only when both directions are idle for the full timeout period — matching the documented contract.

Overhead

  • One atomic.Int64 (8 bytes) per connection pair
  • One atomic.Store (~1 ns) per Read/Write with data
  • Zero extra goroutines
  • Retry loop on idle direction wakes at most once per timeout period

Test plan

  • All existing tests pass
  • Verify media downloads work with default 1-min idle timeout
  • Verify idle connections still get cleaned up after timeout

connIdleTimeout previously set per-direction deadlines independently.
During media downloads the client→telegram direction can be idle at the
application level while telegram→client is actively streaming data.
After IdleTimeout (default 1 min) the idle direction's ReadDeadline
fires, tearing down the entire relay and breaking media transfers.

Replace the per-direction timeout with a shared atomic timestamp that
both pump goroutines update on any successful Read or Write. When a
ReadDeadline fires on the idle direction, we check the shared tracker:
if the other direction was recently active, we retry instead of closing.
The connection is only torn down when both directions are idle for the
full timeout period.

This matches the documented IdleTimeout contract: "if we have any
message which will pass to either direction, a timer is reset."

Overhead: one atomic.Int64 (8 bytes) per connection pair, one
atomic.Store (~1 ns) per Read/Write with data, zero extra goroutines.

Fixes 9seconds#423
@9seconds
Copy link
Copy Markdown
Owner

Да, спасибо. Заняло где-то с минуту понять, в чем же оригинальная проблема, но кажется фикс корректный

@9seconds
Copy link
Copy Markdown
Owner

Могу я попросить вас добавить тест в conns_internal_test?

Cover shared idle tracker behavior:
- tracker lifecycle (new, idle after timeout, touch resets)
- read/write with data touches tracker
- read retries on timeout when tracker is not idle
- read closes on timeout when tracker is idle
- shared tracker prevents false timeout across directions
@dolonet
Copy link
Copy Markdown
Contributor Author

dolonet commented Mar 30, 2026

Добавил — IdleTrackerTestSuite и ConnIdleTimeoutTestSuite в conns_internal_test.go. Покрыл основные кейсы: жизненный цикл трекера, retry при таймауте когда другое направление активно, закрытие при реальном idle, и shared tracker между двумя соединениями.

@9seconds
Copy link
Copy Markdown
Owner

Спасибо! Я сейчас тогда соберу новый PGO (на горячем пути все-таки) и сделаю релиз

@9seconds 9seconds merged commit b926a05 into 9seconds:master Mar 30, 2026
6 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.

Перестали грузиться медиа

2 participants