Problem
Customers initialize Sentry twice at startup (minimal init early to catch crashes, full init once async config is available — does exactly this). A customer cold-start trace (Galaxy A14 / API 34, TTID 1723 ms, method-level tracing) shows re-init is expensive:
io.sentry.Sentry#init runs twice: main thread @396 ms (16.7 ms) and a coroutine thread @587 ms (47.7 ms).
- The re-init triggers two
io.sentry.SentryExecutorService#close calls that each block for the full 2000 ms (shutdownTimeoutMillis default) — awaitTermination never completes, likely because the old executor queue is stuffed with scope-persistence work (or a task never terminates). Both ran on old SentryExecutorS threads, so no main-thread block here, but two threads spin for 2 s and any work queued behind them is lost/delayed.
- The second init spawns a complete duplicate thread set (2× transport
SentryAsyncConn, 2× frame-metrics etricsCollector, 3× SentryExecutorS observed).
Proposal (investigate)
- Find out why
close hits the full timeout (long-running queued task? persistence backlog? non-interruptible IO) — Scopes.close → SentryOptions/SentryExecutorService.close(shutdownTimeoutMillis).
- Fast-close path for the re-init case: drain-or-abandon semantics instead of a blocking
awaitTermination(2000) (crash-relevant tasks excepted).
- Investigate reusing executor/transport/collector threads across re-init when compatible options allow, instead of tearing down + respawning the world.
- Consider documenting/supporting the "init twice" pattern explicitly (relates to
InitUtil.shouldInit priorities and forceInit).
Notes
Problem
Customers initialize Sentry twice at startup (minimal init early to catch crashes, full init once async config is available — does exactly this). A customer cold-start trace (Galaxy A14 / API 34, TTID 1723 ms, method-level tracing) shows re-init is expensive:
io.sentry.Sentry#initruns twice: main thread @396 ms (16.7 ms) and a coroutine thread @587 ms (47.7 ms).io.sentry.SentryExecutorService#closecalls that each block for the full 2000 ms (shutdownTimeoutMillisdefault) —awaitTerminationnever completes, likely because the old executor queue is stuffed with scope-persistence work (or a task never terminates). Both ran on oldSentryExecutorSthreads, so no main-thread block here, but two threads spin for 2 s and any work queued behind them is lost/delayed.SentryAsyncConn, 2× frame-metricsetricsCollector, 3×SentryExecutorSobserved).Proposal (investigate)
closehits the full timeout (long-running queued task? persistence backlog? non-interruptible IO) —Scopes.close→SentryOptions/SentryExecutorService.close(shutdownTimeoutMillis).awaitTermination(2000)(crash-relevant tasks excepted).InitUtil.shouldInitpriorities andforceInit).Notes
median.perfetto-trace, analysis scratchpadmedian.perfetto-trace_analysis_v2.md. The macrobenchmark (Add Jetpack Macrobenchmark for SDK init performance #5679) won't catch this — the sample app inits once.