Skip to content

Commit

Permalink
fix: Updating AppHang state on main thread (#2793)
Browse files Browse the repository at this point in the history
Move updating the app hang state to a background thread for anrStopped.

Fixes GH-2791
  • Loading branch information
philipphofmann authored Mar 15, 2023
1 parent ad7cec6 commit 06548c0
Show file tree
Hide file tree
Showing 6 changed files with 36 additions and 6 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

### Fixes

- Updating AppHang state on main thread (#2793)

## 8.3.1

### Fixes
Expand Down
19 changes: 16 additions & 3 deletions Samples/iOS-Swift/iOS-Swift/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -251,18 +251,31 @@ class ViewController: UIViewController {
@IBAction func anrFillingRunLoop(_ sender: Any) {
let buttonTitle = self.anrFillingRunLoopButton.currentTitle
var i = 0

func sleep(timeout: Double) {
let group = DispatchGroup()
group.enter()
let queue = DispatchQueue(label: "delay", qos: .background, attributes: [])

queue.asyncAfter(deadline: .now() + timeout) {
group.leave()
}

group.wait()
}

dispatchQueue.async {
for _ in 0...100_000 {
for _ in 0...30 {
i += Int.random(in: 0...10)
i -= 1

DispatchQueue.main.async {
self.anrFillingRunLoopButton.setTitle("Work in Progress \(i)", for: .normal)
sleep(timeout: 0.1)
self.anrFillingRunLoopButton.setTitle("Title \(i)", for: .normal)
}
}

DispatchQueue.main.async {
DispatchQueue.main.sync {
self.anrFillingRunLoopButton.setTitle(buttonTitle, for: .normal)
}
}
Expand Down
2 changes: 1 addition & 1 deletion SentryTestUtils/TestSentryDispatchQueueWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public class TestSentryDispatchQueueWrapper: SentryDispatchQueueWrapper {
/// - SeeAlso: `delayDispatches`, which controls whether the block should execute immediately or with the requested delay.
public var dispatchAfterExecutesBlock = false

var dispatchAsyncInvocations = Invocations<() -> Void>()
public var dispatchAsyncInvocations = Invocations<() -> Void>()
public var dispatchAsyncExecutesBlock = true
public override func dispatchAsync(_ block: @escaping () -> Void) {
dispatchAsyncCalled += 1
Expand Down
8 changes: 7 additions & 1 deletion Sources/Sentry/SentryANRTracker.m
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,13 @@ - (void)detectANRs

if (reported) {
SENTRY_LOG_WARN(@"ANR stopped.");
[self ANRStopped];

// The ANR stopped, don't block the main thread with calling ANRStopped listeners.
// While the ANR code reports an ANR and collects the stack trace, the ANR might
// stop simultaneously. In that case, the ANRs stack trace would contain the
// following code running on the main thread. To avoid this, we offload work to a
// background thread.
[self.dispatchQueueWrapper dispatchAsyncWithBlock:^{ [self ANRStopped]; }];
}

reported = NO;
Expand Down
3 changes: 3 additions & 0 deletions Sources/Sentry/include/SentryANRTracker.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ SENTRY_NO_INIT

@end

/**
* The ``SentryANRTracker`` calls the methods from background threads.
*/
@protocol SentryANRTrackerDelegate <NSObject>

- (void)anrDetected;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,9 @@ class SentryANRTrackerTests: XCTestCase, SentryANRTrackerDelegate {

func testMultipleANRs_MultipleReported() {
anrDetectedExpectation.expectedFulfillmentCount = 3
let expectedANRStoppedInvocations = 2
anrStoppedExpectation.isInverted = false
anrStoppedExpectation.expectedFulfillmentCount = 2
anrStoppedExpectation.expectedFulfillmentCount = expectedANRStoppedInvocations

fixture.dispatchQueue.blockBeforeMainBlock = {
self.advanceTime(bySeconds: self.fixture.timeoutInterval)
Expand All @@ -105,6 +106,7 @@ class SentryANRTrackerTests: XCTestCase, SentryANRTrackerDelegate {
start()

wait(for: [anrDetectedExpectation, anrStoppedExpectation], timeout: waitTimeout)
XCTAssertEqual(expectedANRStoppedInvocations, fixture.dispatchQueue.dispatchAsyncInvocations.count)
}

func testAppSuspended_NoANR() {
Expand Down

0 comments on commit 06548c0

Please sign in to comment.