Skip to content

Start the sender in start(), not in the constructor - #44

Merged
PetrHeinz merged 2 commits into
mainfrom
claude/start-owns-sender
Sep 29, 2026
Merged

PetrHeinz merged 2 commits into
mainfrom
claude/start-owns-sender

Conversation

@PetrHeinz

Copy link
Copy Markdown
Member

The constructor creates the scheduled sender, so every LogtailAppender object runs a logtail-appender thread from the moment it exists, whether or not start() ever runs. stop() is the only thing that shuts that thread down, and since 0.3.8 it returns early for an appender that was never started. So an appender whose start fails, whose logback config has an error, or that code builds and never starts keeps a daemon thread waking every batch interval, and in an app server that thread keeps the undeployed webapp in memory. It showed up in Tomcat as "appears to have started a thread named [logtail-appender] but has failed to stop it" when an appender's start failed during shutdown; new LogtailAppender(); stop(); is enough to reproduce it.

  • start() does the starting: it returns early when the appender is already started (as stop() does when it is not), registers the shutdown hook and then creates the sender with the configured batch interval. Registering the hook first means a start() that fails there, for example while the JVM is already shutting down, creates no thread either.
  • The constructor only sets up what does not run: the logger and the JSON mapper.
  • setBatchInterval() stores the interval and reschedules the sender only when the appender is started. Before start(), which is when logback's configuration sets it, there is nothing to reschedule; after stop() it used to throw RejectedExecutionException on the shut-down executor.
  • Side effects of the same change: a second start() no longer registers a second shutdown hook that stop() could not remove, and an appender started again after stop() gets a sender again instead of only sending when a batch fills up.

The first commit only adds LogtailAppenderLifecycleTest and is expected to fail on CI with four of its six tests; the two interval tests pin behaviour that already works.

🤖 Generated with Claude Code

PetrHeinz and others added 2 commits September 29, 2026 15:02
An appender that never starts must leave no sender thread behind, a second
start() must not register a second shutdown hook, the batch interval must
only reschedule a running sender, and a restarted appender must send on its
schedule again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
start() returns early when the appender is already started, registers the
shutdown hook and then creates the sender with the configured interval; the
constructor no longer starts a thread. setBatchInterval() only reschedules a
running sender, so logback's configuration before start() just stores the
value and a stopped appender no longer throws RejectedExecutionException.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@PetrHeinz
PetrHeinz marked this pull request as ready for review September 29, 2026 13:04
@PetrHeinz
PetrHeinz merged commit 64cef67 into main Sep 29, 2026
5 checks passed
@PetrHeinz
PetrHeinz deleted the claude/start-owns-sender branch September 29, 2026 13:05
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