SIGN IN SIGN UP

perf: Avoid per-transaction Timer thread in SentryTracer (JAVA-596) (#5670)

* perf: Avoid per-transaction Timer thread in SentryTracer (JAVA-570)

Transactions with an idle or deadline timeout each created a
java.util.Timer, which spawns a thread synchronously on the calling
thread (often the main thread on Android). At scale (screen loads, HTTP
spans) this was the dominant source of SDK thread churn.

Schedule the idle/deadline timeouts on a dedicated, shared
ISentryExecutorService held by SentryOptions instead, so no thread is
created per transaction. It is kept separate from the main executor so
timeout callbacks (which finish transactions) don't contend with cached
event sending, and it is not prewarmed: its single worker thread is
spawned lazily on the first scheduled timeout and reused thereafter. The
dedicated executor uses removeOnCancelPolicy so cancelled timeouts (idle
timers are rescheduled per child span) don't accumulate in its queue. On
finish only the scheduled futures are cancelled; the executor is closed
with the SDK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* changelog

* fix(core): Keep timer executor alive across SDK restart (JAVA-570)

The shared timer executor introduced for transaction idle/deadline
timeouts was shut down on every Scopes.close(), including SDK restart.
This cancelled the pending idle timeout of any transaction started
before the restart (e.g. an in-flight activity transaction), so it
never auto-finished and its envelope was never sent.

Only close the timer executor on a full close, not on restart, matching
the pre-existing per-transaction Timer behaviour. Enable core-thread
timeout on the timer executor so the instance abandoned by a restart
self-terminates once idle instead of leaking a thread.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(core): Look up shared timer executor on demand (JAVA-570)

SentryTracer cached the shared timer executor in a field for the
lifetime of the transaction. Replace that field with a boolean flag
tracking whether timeouts may still be scheduled, and fetch the
executor from the options each time one is scheduled. This ensures the
tracer always uses the executor currently held by the options (e.g. the
fresh one installed after an SDK restart) rather than a stale reference.

Also make the timer executor's keep-alive duration a constructor
argument backed by the named TIMER_KEEP_ALIVE_SECONDS constant, and
raise it from 10s to 30s so the shared worker thread is less likely to
be torn down and respawned between transactions under normal use.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
N
Nelson Osacky committed
bcd3e76a8033a30cf291fc84c726d40205b21db2
Parent: dd1eb0c
Committed by GitHub <noreply@github.com> on 7/15/2026, 9:16:35 AM