sentry-cocoa icon indicating copy to clipboard operation
sentry-cocoa copied to clipboard

ref: Use RunLoop observer for watchdog detection

Open noahsmartin opened this issue 3 months ago • 3 comments

This introduces a new HangTracker class that observers the run loop and notifies anything registered with it when the runloop is late. The idea is to eventually replace all uses of CADisplayLink with this. They are not exactly the same, display link works on vsync events and a runloop observer hooks directly into the runloop lifecycle.

In this PR I use the new HangTracker in the watchdog integration. This seemed like a pretty small use of display links and was a good opportunity to have a first crack at removing them. I also don't think this change requires any kind of new opt-in options, since it's all internal to how the integration works.

The SDK default behavior before this change is to enable the watchdog integration, which means by default the SDK spins the CPU in the background constantly waking up to process these vsync events. Since we are in the final stretch of quality quarter I wanted to get rid of this behavior and replace it with a passive observer, like this.

A few interesting implementation details:

  • The hang tracker turns itself on and off by removing the runloop observer when nothing is subscribing to changes anymore.
  • The majority of the code is Swift only, with a small ObjcBridge class wrapping the main implementation
  • I used protocol based decency injection rather than subclassing, since this code can now be fully "swifty"

I also manually tested in the swift sample app by making sure when the debugger wasn't attached watchdog events still get sent when I force-quite the app with Xcode. Also verified that if I simulate a hang with the sample app and then force quit while the hang is in progress, watchdog events are not sent.

Closes #6252

noahsmartin avatar Sep 22 '25 19:09 noahsmartin

:x: 2 Tests Failed:

Tests completed Failed Passed Skipped
3980 2 3978 11
View the top 3 failed test(s) by shortest run time
SentryTests.SentryANRTrackerV2Tests::testFullyBlockingFollowedByFullyBlocking_BothReported
Stack Traces | 0s run time
.../Integrations/ANR/SentryANRTrackerV2Tests.swift:276 - Asynchronous wait failed: Exceeded timeout of 10 seconds, with unfulfilled expectations: "Test Delegate ANR Detection".
SentryTests.SentrySpotlightTransportTests::testShouldRemoveAttachmentsFromEventEnvelope
Stack Traces | 0s run time
.../SentryTests/Networking/SentrySpotlightTransportTests.swift:106 - XCTAssertTrue failed - Expected body size to be in range of 1098...1138, but was 1139
iOS_Swift_UITests.UserFeedbackUITests::testAddingScreenshots
Stack Traces | 0s run time
.../iOS-Swift/iOS-Swift-UITests/UserFeedbackUITests.swift:670 - XCTUnwrap failed: expected non-nil value of type "String"
View the full list of 1 :snowflake: flaky test(s)
SentryTests.SentrySDKInternalTests::testResumeAndPauseAppHangTracking

Flake rate in main: 14.00% (Passed 43 times, Failed 7 times)

Stack Traces | 0s run time
.../Tests/SentryTests/SentrySDKInternalTests.swift:632 - XCTAssertEqual failed: ("1") is not equal to ("0") - The SDK should capture an AppHang after resuming the tracking, but it didn't.

To view more test analytics, go to the Test Analytics Dashboard 📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

codecov[bot] avatar Sep 22 '25 19:09 codecov[bot]

Performance metrics :rocket:

  Plain With Sentry Diff
Startup time 1236.58 ms 1265.17 ms 28.59 ms
Size 23.75 KiB 996.49 KiB 972.75 KiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
1fecbb8f103a6358c953746f63378248bb1c30de 1242.78 ms 1265.40 ms 22.62 ms
d05d866b98077cf5b250e23a21c43f987c5021ae 1211.78 ms 1230.96 ms 19.18 ms
32e719764d64829696a1dabd6e8de2f54fdd157a 1226.91 ms 1245.48 ms 18.56 ms
0e9c5ae30627b85210d6e220cee323a446296183 1226.10 ms 1254.14 ms 28.04 ms
397b9c9ca13e8112485c2c4349f3a56f93f22b95 1230.23 ms 1249.29 ms 19.06 ms
718c37291eba5b055e740d41b22a27cd02073bb4 1220.09 ms 1235.15 ms 15.06 ms
45482a6a1b84ca4ee3c987137c8f89c1e28eaf5d 1225.88 ms 1254.27 ms 28.39 ms
f97a070c7a30ad642fd7add3fe91604920aa8348 1218.88 ms 1253.12 ms 34.24 ms
5846fc56d0e70d3cdbaa082892d0ec0765ae5cb6 1223.25 ms 1248.79 ms 25.54 ms
43597ba400daa59034f323b22bbc6695c185e2fa 1214.88 ms 1243.52 ms 28.65 ms

App size

Revision Plain With Sentry Diff
1fecbb8f103a6358c953746f63378248bb1c30de 23.75 KiB 969.28 KiB 945.53 KiB
d05d866b98077cf5b250e23a21c43f987c5021ae 23.75 KiB 878.60 KiB 854.85 KiB
32e719764d64829696a1dabd6e8de2f54fdd157a 23.75 KiB 866.69 KiB 842.94 KiB
0e9c5ae30627b85210d6e220cee323a446296183 23.75 KiB 969.29 KiB 945.54 KiB
397b9c9ca13e8112485c2c4349f3a56f93f22b95 23.75 KiB 959.44 KiB 935.70 KiB
718c37291eba5b055e740d41b22a27cd02073bb4 23.75 KiB 920.65 KiB 896.90 KiB
45482a6a1b84ca4ee3c987137c8f89c1e28eaf5d 23.75 KiB 919.91 KiB 896.16 KiB
f97a070c7a30ad642fd7add3fe91604920aa8348 23.75 KiB 858.68 KiB 834.93 KiB
5846fc56d0e70d3cdbaa082892d0ec0765ae5cb6 23.75 KiB 912.77 KiB 889.02 KiB
43597ba400daa59034f323b22bbc6695c185e2fa 23.75 KiB 880.32 KiB 856.58 KiB

Previous results on branch: hangTrackerAbstraction

Startup times

Revision Plain With Sentry Diff
ac854e265607c81cd2a309dcffee8428b9e513d7 1220.81 ms 1250.20 ms 29.39 ms
1e7ad41fd83ff965f79f4c79f0f2a9081f5b2fc1 1245.79 ms 1280.67 ms 34.88 ms
547ce2a45d2b391187ae343d8ce062f95fc419d1 1226.90 ms 1257.82 ms 30.92 ms
fee2eea59cebf9684f65f376307e136e1d75565d 1225.57 ms 1243.82 ms 18.25 ms

App size

Revision Plain With Sentry Diff
ac854e265607c81cd2a309dcffee8428b9e513d7 23.75 KiB 995.72 KiB 971.97 KiB
1e7ad41fd83ff965f79f4c79f0f2a9081f5b2fc1 23.75 KiB 994.33 KiB 970.58 KiB
547ce2a45d2b391187ae343d8ce062f95fc419d1 23.75 KiB 995.83 KiB 972.08 KiB
fee2eea59cebf9684f65f376307e136e1d75565d 23.75 KiB 996.53 KiB 972.78 KiB

github-actions[bot] avatar Sep 22 '25 20:09 github-actions[bot]

What's the status of this PR? I see it is outdated and has merge conflicts due to being open for a while now.

philprime avatar Dec 04 '25 12:12 philprime