Skip to content

fix(logging): leave Bugsnag breadcrumbs off the tracing thread - #1663

Merged
bmc08gt merged 1 commit into
code/cashfrom
fix/breadcrumb-sink-off-main
Oct 2, 2026
Merged

bmc08gt merged 1 commit into
code/cashfrom
fix/breadcrumb-sink-off-main

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Oct 2, 2026

Copy link
Copy Markdown
Collaborator

Play's ANR list for 2026.9.2 (4581) puts 16 of 21 ANRs in BugsnagBreadcrumbSink.record, tagged lock contention, while handling an FCM push (c2dm.intent.RECEIVE) or NotificationService. trace() runs every sink on the caller's thread, so a trace on main waited on whatever Bugsnag was waiting on, for 10s or more.

  • The sink queues breadcrumbs, and one worker on its own bugsnag-breadcrumbs thread hands them to Bugsnag in order. It isn't on Dispatchers.IO because that pool is saturated during cold start (perf(startup): cut the wait between launch and the wallet tab #1505), which is when these ANRs fire.
  • The queue holds 100, Bugsnag's own breadcrumb limit, and drops the oldest, so a worker stuck behind the same lock can't grow it.
  • TraceType.Error breadcrumbs still go to Bugsnag on the caller's thread. trace() reports the error right after the sinks run and the report snapshots breadcrumbs then, so a queued one would be missing from its own report. Client.notify already leaves its own breadcrumb on that thread (Client.java:978), so this adds no new way to block.

Which lock the main thread waited on isn't known yet. The Java side of leaveBreadcrumb in 6.27.0 takes no monitor, so the NDK state behind NativeBridge.addBreadcrumb is the likely candidate. This change removes main's exposure regardless of the owner.

Bugsnag never reported these ANRs: its ANR plugin arms by posting to the main looper (AnrPlugin.kt:55), which can't run while main is blocked in record. Expect Bugsnag's ANR counts to change shape once this ships.

Trade-offs: Bugsnag timestamps a queued breadcrumb when the worker delivers it, normally well under a millisecond late, and an error breadcrumb can land ahead of ones still queued.

Play's ANR list for 2026.9.2 (4581) puts 16 of 21 ANRs in
BugsnagBreadcrumbSink.record, tagged lock contention, while handling an
FCM push or NotificationService. trace() runs every sink on the caller's
thread, so a trace on main waited on whatever Bugsnag was waiting on, for
10s or more.

The sink now queues breadcrumbs and one worker on its own thread hands
them to Bugsnag in order. The queue holds 100, Bugsnag's own breadcrumb
limit, and drops the oldest, so a worker stuck behind the same lock can't
grow it. It isn't on Dispatchers.IO because that pool is saturated during
cold start, which is when these ANRs fire.

Error breadcrumbs still go to Bugsnag on the caller's thread. trace()
reports the error right after the sinks run and the report snapshots
breadcrumbs then, so a queued one would be missing from its own report.
That adds no new way to block: Client.notify leaves its own breadcrumb on
the same thread.

Bugsnag never reported these ANRs. Its ANR plugin arms by posting to the
main looper (AnrPlugin.kt:55, 6.27.0), which can't run while main is
blocked in record. Expect Bugsnag's ANR counts to change shape once this
ships.
@bmc08gt bmc08gt self-assigned this Oct 2, 2026
@github-actions github-actions Bot added the type: fix Bug fix label Oct 2, 2026
@bmc08gt
bmc08gt merged commit 52cbca9 into code/cash Oct 2, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: fix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant