ref: Use RunLoop observer for watchdog detection
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
: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_BothReportedStack 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::testShouldRemoveAttachmentsFromEventEnvelopeStack 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::testAddingScreenshotsStack 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::testResumeAndPauseAppHangTrackingFlake 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.
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 |
What's the status of this PR? I see it is outdated and has merge conflicts due to being open for a while now.