Skip to content

Commit

Permalink
chore: Duplicate ANRTracker classes (#4262)
Browse files Browse the repository at this point in the history
This is the first step for #3492, which is part of the EPIC AppHang improvements #4261.
  • Loading branch information
philipphofmann committed Aug 12, 2024
1 parent 594c2e6 commit e0abb7e
Show file tree
Hide file tree
Showing 16 changed files with 934 additions and 0 deletions.
24 changes: 24 additions & 0 deletions Sentry.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@
620203B22C59025E0008317C /* SentryFileContents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 620203B12C59025E0008317C /* SentryFileContents.swift */; };
620379DB2AFE1415005AC0C1 /* SentryBuildAppStartSpans.h in Headers */ = {isa = PBXBuildFile; fileRef = 620379DA2AFE1415005AC0C1 /* SentryBuildAppStartSpans.h */; };
620379DD2AFE1432005AC0C1 /* SentryBuildAppStartSpans.m in Sources */ = {isa = PBXBuildFile; fileRef = 620379DC2AFE1432005AC0C1 /* SentryBuildAppStartSpans.m */; };
621A9D5A2C64F18900B5D7D6 /* SentryANRTrackingIntegrationV2.h in Headers */ = {isa = PBXBuildFile; fileRef = 621A9D592C64F18900B5D7D6 /* SentryANRTrackingIntegrationV2.h */; };
621A9D5C2C64F1AE00B5D7D6 /* SentryANRTrackingIntegrationV2.m in Sources */ = {isa = PBXBuildFile; fileRef = 621A9D5B2C64F1AE00B5D7D6 /* SentryANRTrackingIntegrationV2.m */; };
621A9D5F2C64F3BC00B5D7D6 /* SentryANRTrackingIntegrationV2Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621A9D5D2C64F3B700B5D7D6 /* SentryANRTrackingIntegrationV2Tests.swift */; };
621AE74B2C626C230012E730 /* SentryANRTrackerV2.h in Headers */ = {isa = PBXBuildFile; fileRef = 621AE74A2C626C230012E730 /* SentryANRTrackerV2.h */; };
621AE74D2C626C510012E730 /* SentryANRTrackerV2.m in Sources */ = {isa = PBXBuildFile; fileRef = 621AE74C2C626C510012E730 /* SentryANRTrackerV2.m */; };
621D9F2F2B9B0320003D94DE /* SentryCurrentDateProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621D9F2E2B9B0320003D94DE /* SentryCurrentDateProvider.swift */; };
621F61F12BEA073A005E654F /* SentryEnabledFeaturesBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621F61F02BEA073A005E654F /* SentryEnabledFeaturesBuilder.swift */; };
62262B862BA1C46D004DA3DD /* SentryStatsdClient.h in Headers */ = {isa = PBXBuildFile; fileRef = 62262B852BA1C46D004DA3DD /* SentryStatsdClient.h */; };
Expand Down Expand Up @@ -130,6 +135,7 @@
62E081AB29ED4322000F69FC /* SentryBreadcrumbTestDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E081AA29ED4322000F69FC /* SentryBreadcrumbTestDelegate.swift */; };
62E146D02BAAE47600ED34FD /* LocalMetricsAggregator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E146CF2BAAE47600ED34FD /* LocalMetricsAggregator.swift */; };
62E146D22BAAF55B00ED34FD /* LocalMetricsAggregatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E146D12BAAF55B00ED34FD /* LocalMetricsAggregatorTests.swift */; };
62EF86A12C626D39004E058B /* SentryANRTrackerV2Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621AE74E2C626CF70012E730 /* SentryANRTrackerV2Tests.swift */; };
62F05D2B2C0DB1F100916E3F /* SentryLogTestHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 62F05D2A2C0DB1F100916E3F /* SentryLogTestHelper.m */; };
62F226B729A37C120038080D /* SentryBooleanSerialization.m in Sources */ = {isa = PBXBuildFile; fileRef = 62F226B629A37C120038080D /* SentryBooleanSerialization.m */; };
62F4DDA12C04CB9700588890 /* SentryBaggageSerializationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62F4DDA02C04CB9700588890 /* SentryBaggageSerializationTests.swift */; };
Expand Down Expand Up @@ -1051,6 +1057,12 @@
620203B12C59025E0008317C /* SentryFileContents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryFileContents.swift; sourceTree = "<group>"; };
620379DA2AFE1415005AC0C1 /* SentryBuildAppStartSpans.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryBuildAppStartSpans.h; path = include/SentryBuildAppStartSpans.h; sourceTree = "<group>"; };
620379DC2AFE1432005AC0C1 /* SentryBuildAppStartSpans.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryBuildAppStartSpans.m; sourceTree = "<group>"; };
621A9D592C64F18900B5D7D6 /* SentryANRTrackingIntegrationV2.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryANRTrackingIntegrationV2.h; path = include/SentryANRTrackingIntegrationV2.h; sourceTree = "<group>"; };
621A9D5B2C64F1AE00B5D7D6 /* SentryANRTrackingIntegrationV2.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryANRTrackingIntegrationV2.m; sourceTree = "<group>"; };
621A9D5D2C64F3B700B5D7D6 /* SentryANRTrackingIntegrationV2Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryANRTrackingIntegrationV2Tests.swift; sourceTree = "<group>"; };
621AE74A2C626C230012E730 /* SentryANRTrackerV2.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryANRTrackerV2.h; path = include/SentryANRTrackerV2.h; sourceTree = "<group>"; };
621AE74C2C626C510012E730 /* SentryANRTrackerV2.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryANRTrackerV2.m; sourceTree = "<group>"; };
621AE74E2C626CF70012E730 /* SentryANRTrackerV2Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryANRTrackerV2Tests.swift; sourceTree = "<group>"; };
621D9F2E2B9B0320003D94DE /* SentryCurrentDateProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryCurrentDateProvider.swift; sourceTree = "<group>"; };
621F61F02BEA073A005E654F /* SentryEnabledFeaturesBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryEnabledFeaturesBuilder.swift; sourceTree = "<group>"; };
62262B852BA1C46D004DA3DD /* SentryStatsdClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryStatsdClient.h; path = include/SentryStatsdClient.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2834,6 +2846,10 @@
7B127B0E27CF6F4700A71ED2 /* SentryANRTrackingIntegration.m */,
7BCFA71427D0BAB7008C662C /* SentryANRTracker.h */,
7BCFA71527D0BB50008C662C /* SentryANRTracker.m */,
621A9D592C64F18900B5D7D6 /* SentryANRTrackingIntegrationV2.h */,
621A9D5B2C64F1AE00B5D7D6 /* SentryANRTrackingIntegrationV2.m */,
621AE74A2C626C230012E730 /* SentryANRTrackerV2.h */,
621AE74C2C626C510012E730 /* SentryANRTrackerV2.m */,
);
name = ANR;
sourceTree = "<group>";
Expand All @@ -2842,7 +2858,9 @@
isa = PBXGroup;
children = (
7B2A70D727D5F07F008B0D15 /* SentryANRTrackerTests.swift */,
621AE74E2C626CF70012E730 /* SentryANRTrackerV2Tests.swift */,
7BFA69F527E0840400233199 /* SentryANRTrackingIntegrationTests.swift */,
621A9D5D2C64F3B700B5D7D6 /* SentryANRTrackingIntegrationV2Tests.swift */,
);
path = ANR;
sourceTree = "<group>";
Expand Down Expand Up @@ -3987,6 +4005,7 @@
03F84D2727DD414C008FE43F /* SentryMachLogging.hpp in Headers */,
63295AF51EF3C7DB002D4490 /* SentryNSDictionarySanitize.h in Headers */,
D8739D172BEEA33F007D2F66 /* SentryLevelHelper.h in Headers */,
621A9D5A2C64F18900B5D7D6 /* SentryANRTrackingIntegrationV2.h in Headers */,
8E4A037825F6F52100000D77 /* SentrySampleDecision.h in Headers */,
63FE717920DA4C1100CDBAE8 /* SentryCrashReportStore.h in Headers */,
0AAE202128ED9BCC00D0CD80 /* SentryReachability.h in Headers */,
Expand Down Expand Up @@ -4085,6 +4104,7 @@
7BC852332458802C005A70F0 /* SentryDataCategoryMapper.h in Headers */,
7BDB03B7251364F800BAE198 /* SentryDispatchQueueWrapper.h in Headers */,
7BF9EF842722D07B00B5BBEF /* SentryObjCRuntimeWrapper.h in Headers */,
621AE74B2C626C230012E730 /* SentryANRTrackerV2.h in Headers */,
639889B71EDECFA800EA7442 /* SentryBreadcrumbTracker.h in Headers */,
632331F9240506DF008D91D6 /* SentryScope+Private.h in Headers */,
D8603DD8284F894C000E1227 /* SentryBaggage.h in Headers */,
Expand Down Expand Up @@ -4493,6 +4513,7 @@
7B7D873624864C9D00D2ECFF /* SentryCrashDefaultMachineContextWrapper.m in Sources */,
63FE712F20DA4C1100CDBAE8 /* SentryCrashSysCtl.c in Sources */,
7B3B473825D6CC7E00D01640 /* SentryNSError.m in Sources */,
621AE74D2C626C510012E730 /* SentryANRTrackerV2.m in Sources */,
D8ACE3C82762187200F5A213 /* SentryNSDataTracker.m in Sources */,
7BE3C77D2446112C00A38442 /* SentryRateLimitParser.m in Sources */,
51B15F7E2BE88A7C0026A2F2 /* URLSessionTaskHelper.swift in Sources */,
Expand Down Expand Up @@ -4547,6 +4568,7 @@
8ECC674725C23A20000E2BF6 /* SentrySpanContext.m in Sources */,
7B18DE4228D9F794004845C6 /* SentryNSNotificationCenterWrapper.m in Sources */,
639FCFA91EBC80CC00778193 /* SentryFrame.m in Sources */,
621A9D5C2C64F1AE00B5D7D6 /* SentryANRTrackingIntegrationV2.m in Sources */,
D858FA672A29EAB3002A3503 /* SentryBinaryImageCache.m in Sources */,
D8AFC0572BDA895400118BE1 /* UIRedactBuilder.swift in Sources */,
8E564AEA267AF22600FE117D /* SentryNetworkTracker.m in Sources */,
Expand Down Expand Up @@ -4912,6 +4934,7 @@
D855AD62286ED6A4002573E1 /* SentryCrashTests.m in Sources */,
D8AFC0012BD252B900118BE1 /* SentryOnDemandReplayTests.swift in Sources */,
0A9415BA28F96CAC006A5DD1 /* TestSentryReachability.swift in Sources */,
62EF86A12C626D39004E058B /* SentryANRTrackerV2Tests.swift in Sources */,
D880E3A728573E87008A90DB /* SentryBaggageTests.swift in Sources */,
7B16FD022654F86B008177D3 /* SentrySysctlTests.swift in Sources */,
7BAF3DB5243C743E008A5414 /* SentryClientTests.swift in Sources */,
Expand Down Expand Up @@ -4995,6 +5018,7 @@
7BB7E7C729267A28004BF96B /* EmptyIntegration.swift in Sources */,
7B965728268321CD00C66E25 /* SentryCrashScopeObserverTests.swift in Sources */,
626866742BA89683006995EA /* BucketMetricsAggregatorTests.swift in Sources */,
621A9D5F2C64F3BC00B5D7D6 /* SentryANRTrackingIntegrationV2Tests.swift in Sources */,
7BD86ECB264A6DB5005439DB /* TestSysctl.swift in Sources */,
D861301C2BB5A267004C0F5E /* SentrySessionReplayTests.swift in Sources */,
7B0DC73428869BF40039995F /* NSMutableDictionarySentryTests.swift in Sources */,
Expand Down
210 changes: 210 additions & 0 deletions Sources/Sentry/SentryANRTrackerV2.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
#import "SentryANRTrackerV2.h"
#import "SentryCrashWrapper.h"
#import "SentryDependencyContainer.h"
#import "SentryDispatchQueueWrapper.h"
#import "SentryLog.h"
#import "SentrySwift.h"
#import "SentryThreadWrapper.h"
#import <stdatomic.h>

NS_ASSUME_NONNULL_BEGIN

typedef NS_ENUM(NSInteger, SentryANRTrackerState) {
kSentryANRTrackerNotRunning = 1,
kSentryANRTrackerRunning,
kSentryANRTrackerStarting,
kSentryANRTrackerStopping
};

@interface
SentryANRTrackerV2 ()

@property (nonatomic, strong) SentryCrashWrapper *crashWrapper;
@property (nonatomic, strong) SentryDispatchQueueWrapper *dispatchQueueWrapper;
@property (nonatomic, strong) SentryThreadWrapper *threadWrapper;
@property (nonatomic, strong) NSHashTable<id<SentryANRTrackerV2Delegate>> *listeners;
@property (nonatomic, assign) NSTimeInterval timeoutInterval;

@end

@implementation SentryANRTrackerV2 {
NSObject *threadLock;
SentryANRTrackerState state;
}

- (instancetype)initWithTimeoutInterval:(NSTimeInterval)timeoutInterval
crashWrapper:(SentryCrashWrapper *)crashWrapper
dispatchQueueWrapper:(SentryDispatchQueueWrapper *)dispatchQueueWrapper
threadWrapper:(SentryThreadWrapper *)threadWrapper
{
if (self = [super init]) {
self.timeoutInterval = timeoutInterval;
self.crashWrapper = crashWrapper;
self.dispatchQueueWrapper = dispatchQueueWrapper;
self.threadWrapper = threadWrapper;
self.listeners = [NSHashTable weakObjectsHashTable];
threadLock = [[NSObject alloc] init];
state = kSentryANRTrackerNotRunning;
}
return self;
}

- (void)detectANRs
{
NSUUID *threadID = [NSUUID UUID];

@synchronized(threadLock) {
[self.threadWrapper threadStarted:threadID];

if (state != kSentryANRTrackerStarting) {
[self.threadWrapper threadFinished:threadID];
return;
}

NSThread.currentThread.name = @"io.sentry.app-hang-tracker";
state = kSentryANRTrackerRunning;
}

__block atomic_int ticksSinceUiUpdate = 0;
__block BOOL reported = NO;

NSInteger reportThreshold = 5;
NSTimeInterval sleepInterval = self.timeoutInterval / reportThreshold;

SentryCurrentDateProvider *dateProvider = SentryDependencyContainer.sharedInstance.dateProvider;

// Canceling the thread can take up to sleepInterval.
while (YES) {
@synchronized(threadLock) {
if (state != kSentryANRTrackerRunning) {
break;
}
}

NSDate *blockDeadline = [[dateProvider date] dateByAddingTimeInterval:self.timeoutInterval];

atomic_fetch_add_explicit(&ticksSinceUiUpdate, 1, memory_order_relaxed);

[self.dispatchQueueWrapper dispatchAsyncOnMainQueue:^{
atomic_store_explicit(&ticksSinceUiUpdate, 0, memory_order_relaxed);

if (reported) {
SENTRY_LOG_WARN(@"ANR stopped.");

// 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;
}];

[self.threadWrapper sleepForTimeInterval:sleepInterval];

// The blockDeadline should be roughly executed after the timeoutInterval even if there is
// an ANR. If the app gets suspended this thread could sleep and wake up again. To avoid
// false positives, we don't report ANRs if the delta is too big.
NSTimeInterval deltaFromNowToBlockDeadline =
[[dateProvider date] timeIntervalSinceDate:blockDeadline];

if (deltaFromNowToBlockDeadline >= self.timeoutInterval) {
SENTRY_LOG_DEBUG(
@"Ignoring ANR because the delta is too big: %f.", deltaFromNowToBlockDeadline);
continue;
}

if (atomic_load_explicit(&ticksSinceUiUpdate, memory_order_relaxed) >= reportThreshold
&& !reported) {
reported = YES;

if (![self.crashWrapper isApplicationInForeground]) {
SENTRY_LOG_DEBUG(@"Ignoring ANR because the app is in the background");
continue;
}

SENTRY_LOG_WARN(@"ANR detected.");
[self ANRDetected];
}
}

@synchronized(threadLock) {
state = kSentryANRTrackerNotRunning;
[self.threadWrapper threadFinished:threadID];
}
}

- (void)ANRDetected
{
NSArray *localListeners;
@synchronized(self.listeners) {
localListeners = [self.listeners allObjects];
}

for (id<SentryANRTrackerV2Delegate> target in localListeners) {
[target anrDetected];
}
}

- (void)ANRStopped
{
NSArray *targets;
@synchronized(self.listeners) {
targets = [self.listeners allObjects];
}

for (id<SentryANRTrackerV2Delegate> target in targets) {
[target anrStopped];
}
}

- (void)addListener:(id<SentryANRTrackerV2Delegate>)listener
{
@synchronized(self.listeners) {
[self.listeners addObject:listener];

@synchronized(threadLock) {
if (self.listeners.count > 0 && state == kSentryANRTrackerNotRunning) {
if (state == kSentryANRTrackerNotRunning) {
state = kSentryANRTrackerStarting;
[NSThread detachNewThreadSelector:@selector(detectANRs)
toTarget:self
withObject:nil];
}
}
}
}
}

- (void)removeListener:(id<SentryANRTrackerV2Delegate>)listener
{
@synchronized(self.listeners) {
[self.listeners removeObject:listener];

if (self.listeners.count == 0) {
[self stop];
}
}
}

- (void)clear
{
@synchronized(self.listeners) {
[self.listeners removeAllObjects];
[self stop];
}
}

- (void)stop
{
@synchronized(threadLock) {
SENTRY_LOG_INFO(@"Stopping ANR detection");
state = kSentryANRTrackerStopping;
}
}

@end

NS_ASSUME_NONNULL_END
Loading

0 comments on commit e0abb7e

Please sign in to comment.