Skip to content

Back off between failed connection attempts in the HTTP outlet - #50

Merged
PetrHeinz merged 3 commits into
mainfrom
claude/reconnect-backoff
Sep 30, 2026
Merged

PetrHeinz merged 3 commits into
mainfrom
claude/reconnect-backoff

Conversation

@PetrHeinz

@PetrHeinz PetrHeinz commented Sep 30, 2026 •

Copy link
Copy Markdown
Member

When the ingesting host refuses or drops the connection, the HTTP device's outlet thread reconnects immediately, in a tight loop. Against a refused port that is thousands of connection attempts per second on one CPU core for as long as the host stays unreachable, and on Ruby before 3.0 under ruby -w every attempt also prints instance variable @debug_logger not initialized to stderr.

This PR makes the outlet wait before reconnecting after a failed connection:

  • 1 second after the first failure, doubling on every consecutive failure, capped at 30 seconds;
  • the same wait applies when a request fails on an open connection (the request is still re-queued and dropped after 3 attempts, as before);
  • a delivered request starts the wait over at 1 second;
  • closing the device still returns at once while the outlet waits: it kills the outlet thread, which interrupts the sleep.

Logtail::Config also initialises @debug_logger to nil, so reading the unset debug logger no longer warns under ruby -w.

The first commit contains only the new tests and is expected to fail on CI; the second commit makes them pass, and the third gives the thread-based spec more time for its first attempt on a cold TruffleRuby.

End to end in ruby:4.0-slim, with the gem from this branch (Bundler git source at 2bd7a73) against released 0.1.19:

  • refused port (127.0.0.1:9), one log line, 20 s: 5 connection attempts at 0, 1, 3, 7 and 15 s and 0.01 s of CPU, against 110,809 attempts and 7.3 s of CPU with 0.1.19;
  • a host that accepts connections and drops every request: attempts 1 and 2 s apart, the request dropped after its third attempt, and closing during the following 4 s wait took 0.6 ms;
  • normal delivery: three marker lines arrived in a new source, closing took 0.50 s (the same as 0.1.19).

🤖 Generated with Claude Code

PetrHeinz and others added 3 commits September 30, 2026 19:56
The HTTP device's outlet thread reconnects immediately after a refused or
dropped connection, which spins a CPU core at thousands of connection
attempts per second while the ingesting host is unreachable. These tests
pin the intended behaviour:

- after each failed connection the outlet waits 1, 2, 4, 8, 16, then 30
  seconds before reconnecting;
- a request failing on an open connection waits the same way and is still
  dropped after 3 attempts;
- a delivered request starts the wait over at 1 second;
- close stops the outlet at once while it waits.

A config spec pins that reading the unset debug logger does not warn under
ruby -w (Ruby before 3.0), which the outlet does on every attempt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When the ingesting host refused or dropped the connection, the outlet
thread reconnected at once, in a busy loop: thousands of connection
attempts per second on one CPU core for as long as the host stayed
unreachable.

The outlet now waits before reconnecting after a failed connection, be it
an exception from Net::HTTP#start or a request that failed on an open
connection (deliver_requests returning false): 1 second, doubling on
every consecutive failure up to 30 seconds. A delivered request starts the
wait over at 1 second. Requests are still re-queued and dropped after 3
attempts. close kills the outlet thread, which interrupts the sleep, so it
does not wait for the backoff.

Logtail::Config initialises @debug_logger to nil, so reading the unset
debug logger, which the outlet does on every attempt, no longer warns
under ruby -w on Ruby before 3.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A cold TruffleRuby can take a while to run the new thread's first
connection attempt; the 1 s poll left little headroom on CI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@PetrHeinz
PetrHeinz marked this pull request as ready for review September 30, 2026 18:07
@PetrHeinz
PetrHeinz merged commit eec949c into main Sep 30, 2026
13 checks passed
@PetrHeinz
PetrHeinz deleted the claude/reconnect-backoff branch September 30, 2026 18:07
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