Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Duplicate ANRTracker classes #4262

Merged
merged 2 commits into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading