Skip to content

fix: avoid task lock during breadcrumbs - #8497

Draft
philprime wants to merge 3 commits into
mainfrom
test/reproduce-network-tracker-hang
Draft

fix: avoid task lock during breadcrumbs#8497
philprime wants to merge 3 commits into
mainfrom
test/reproduce-network-tracker-hang

Conversation

@philprime

@philprime philprime commented Jul 21, 2026

Copy link
Copy Markdown
Member

📜 Description

Avoid holding or reacquiring the NSURLSessionTask monitor while Session Replay processes network response details and attaches them to a network breadcrumb.

captureResponseDetails: now uses @synchronized(task) only to retrieve the associated SentryReplayNetworkDetails. Header access and body parsing happen after releasing the monitor. addBreadcrumbForSessionTask: no longer reacquires the task monitor to read that associated object.

SentryReplayNetworkDetails publishes completed request and response values through a nonblocking internal state mutex. Serialization copies one consistent snapshot before building its dictionary. If optional replay state is busy, the SDK skips that publication or snapshot rather than blocking a URLSession callback or main-thread cancellation.

Deterministic regression tests coordinate two threads on the same task monitor and concurrently update and serialize replay details. They cover the main-thread setState: breadcrumb path, the response-processing critical section, and consistent network-detail snapshots.

💡 Motivation and Context

NSURLSessionTask.cancel can invoke Sentry's swizzled setState: synchronously on the main thread. The tracker then adds a breadcrumb and previously waited on @synchronized(sessionTask) if a URLSession worker was capturing Session Replay response details for the same task. Because response header and body processing happened inside that monitor, this contention could keep the main thread blocked long enough to trigger a fatal app hang.

The reproduced sequence is:

sequenceDiagram
    participant Worker as URLSession worker
    participant Monitor as NSURLSessionTask monitor
    participant Main as Main thread

    Worker->>Monitor: captureResponseDetails acquires monitor
    Worker->>Worker: Process response headers and body
    Main->>Main: NSURLSessionTask.cancel
    Main->>Main: Sentry swizzled setState
    Main->>Monitor: addBreadcrumbForSessionTask waits
    Worker-->>Monitor: Release monitor
    Monitor-->>Main: Continue cancellation
Loading

This evidence establishes lock contention causing an app hang. It does not by itself establish a permanent deadlock.

Fixes #8355

Sentry issue: https://sentry.sentry.io/issues/6827836853/

💚 How did you test it?

The breadcrumb contention test failed before the fix because setState: took about 1.07 seconds while another thread held the task monitor. The response-processing test also failed because another thread could not acquire the task monitor within 0.5 seconds while response headers were read. The concurrent snapshot test produced 96,803 mismatched status and response-size pairs before internal state synchronization and zero afterward.

After the fix:

  • The related network tracker, replay details, and breadcrumb converter suites passed 101 tests.
  • make build-ios FOR_AGENTS=true succeeded.
  • make analyze succeeded.
  • make format completed successfully.

📝 Checklist

You have to check all boxes before merging:

  • I added tests to verify the changes.
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled.
  • I updated the docs if needed.
  • I updated the wizard if needed.
  • Review from the native team if needed.
  • No breaking change or entry added to the changelog.
  • No breaking change for hybrid SDKs or communicated to hybrid SDKs.
  • Public API changes reviewed by another Mobile SDK team member or implemented according to the develop docs spec.
  • If I added a new public API, I also added it to the SentryObjC wrapper.

Keep Session Replay response processing outside the URLSession task monitor and avoid reacquiring that monitor while adding network-detail breadcrumbs. This prevents synchronous cancellation from waiting on optional replay enrichment.

Fixes #8355
@philprime philprime self-assigned this Jul 21, 2026
@philprime philprime added the ready-to-merge (deprecated) Use run-full-ci instead. label Jul 21, 2026
@philprime

Copy link
Copy Markdown
Member Author

@sentry review

Comment thread Sources/Sentry/SentryNetworkTracker.m
@sentry

sentry Bot commented Jul 21, 2026

Copy link
Copy Markdown

📲 Install Builds

iOS

🔗 App Name App ID Version Configuration
SDK-Size io.sentry.sample.SDK-Size 9.22.0 (1) Release

⚙️ sentry-cocoa Build Distribution Settings

Publish request and response details through a nonblocking internal state mutex and serialize consistent snapshots. Skip optional replay enrichment under contention rather than blocking URLSession callbacks.
@github-actions

Copy link
Copy Markdown
Contributor

🚨 Detected changes in high risk code 🚨

High-risk code can easily blow up and is hard to test. We had severe bugs in the past. Be extra careful when changing these files, and have an extra careful look at these:

  • Sources/Sentry/SentryNetworkTracker.m

@philprime

Copy link
Copy Markdown
Member Author

@sentry review

@github-actions

Copy link
Copy Markdown
Contributor

Performance metrics 🚀

  Plain With Sentry Diff
Startup time 1242.24 ms 1273.32 ms 31.08 ms
Size 24.14 KiB 1.24 MiB 1.22 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
1b1997e 1222.20 ms 1262.02 ms 39.83 ms
fc5cefc 1222.44 ms 1254.20 ms 31.77 ms
92bcc8f 1233.43 ms 1270.20 ms 36.77 ms
91f06a6 1213.64 ms 1259.58 ms 45.93 ms
c2a01c0 1218.69 ms 1250.67 ms 31.98 ms
2509eff 1219.50 ms 1253.57 ms 34.07 ms
a781a3d 1210.98 ms 1244.31 ms 33.33 ms
f213b0b 1220.62 ms 1251.59 ms 30.97 ms
119dc37 1217.64 ms 1250.44 ms 32.80 ms
7814719 1220.98 ms 1254.14 ms 33.16 ms

App size

Revision Plain With Sentry Diff
1b1997e 24.14 KiB 1.22 MiB 1.20 MiB
fc5cefc 24.14 KiB 1.17 MiB 1.14 MiB
92bcc8f 24.14 KiB 1.15 MiB 1.13 MiB
91f06a6 24.14 KiB 1.24 MiB 1.21 MiB
c2a01c0 24.14 KiB 1.23 MiB 1.21 MiB
2509eff 24.14 KiB 1.17 MiB 1.15 MiB
a781a3d 24.14 KiB 1.16 MiB 1.13 MiB
f213b0b 24.14 KiB 1.17 MiB 1.14 MiB
119dc37 24.14 KiB 1.16 MiB 1.13 MiB
7814719 24.14 KiB 1.15 MiB 1.13 MiB

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-merge (deprecated) Use run-full-ci instead.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fatal App Hang in SentryNetworkTracker urlSessionTask:setState: main-thread lock

1 participant