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

fix: Multiple issues in SentryReachability #3338

Merged
merged 14 commits into from
Oct 11, 2023
Merged

Conversation

philipphofmann
Copy link
Member

@philipphofmann philipphofmann commented Oct 9, 2023

📜 Description

The tests can launch multiple threads of the ANR tracker, which call dealloc of SentryReachability simultaneously, leading to a segfault while iterating over the observers in SentryReachability. This is fixed now by using some synchronization around the observer's access. Furthermore, the bookkeeping of the observers is implemented now with weak references using an NSHashTable. Instead of relying on dealloc to be called for removing the observers, each observer must now call removeObserver after subscribing. If an observer doesn't call removeObserver no memory is leaked cause the SentryReachability uses weak references. Previously, the list of observers used a dictionary with the description method of Objective-C as the key, which is suboptimal cause the description could change between adding and removing the observer, leading to memory leaks when the observer's description changed between invocations.

💡 Motivation and Context

A test in CI contained the following crash report.

Crash Report

Process:               xctest [2374]
Path:                  /Applications/Xcode_13.2.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Xcode/Agents/xctest
Identifier:            xctest
Version:               13.2.1 (19566)
Code Type:             X86-64 (Native)
Parent Process:        xcodebuild [2361]
Responsible:           xctest [2374]
User ID:               501

Date/Time:             2023-10-04 08:54:45.554 +0000
OS Version:            macOS 11.7.10 (20G1427)
Report Version:        12
Anonymous UUID:        28C488D7-2871-0137-7EA3-2D329D89124C


Time Awake Since Boot: 1000 seconds

System Integrity Protection: enabled

Crashed Thread:        15  io.sentry.app-hang-tracker

Exception Type:        EXC_BAD_ACCESS (SIGSEGV)
Exception Codes:       EXC_I386_GPFLT
Exception Note:        EXC_CORPSE_NOTIFY

Termination Signal:    Segmentation fault: 11
Termination Reason:    Namespace SIGNAL, Code 0xb
Terminating Process:   exc handler [2374]

Thread 0:: Dispatch queue: com.apple.main-thread
0   com.apple.CoreFoundation      	0x00007fff205dd1a0 -[NSDictionary allValues] + 146
1   io.sentry.Sentry              	0x000000010b2ad45b -[SentryReachability removeObserver:] + 155 (SentryReachability.m:146)
2   io.sentry.Sentry              	0x000000010b2a8722 -[SentryHttpTransport dealloc] + 114 (SentryHttpTransport.m:119)
3   io.sentry.Sentry              	0x000000010b25c5d9 -[SentryTransportAdapter .cxx_destruct] + 57 (SentryTransportAdapter.m:18)
4   libobjc.A.dylib               	0x00007fff2038ea04 object_cxxDestructFromClass(objc_object*, objc_class*) + 83
5   libobjc.A.dylib               	0x00007fff203875a1 objc_destructInstance + 94
6   libobjc.A.dylib               	0x00007fff20387505 _objc_rootDealloc + 62
7   io.sentry.Sentry              	0x000000010b242bef -[SentryClient .cxx_destruct] + 159 (SentryClient.m:72)
8   libobjc.A.dylib               	0x00007fff2038ea04 object_cxxDestructFromClass(objc_object*, objc_class*) + 83
9   libobjc.A.dylib               	0x00007fff203875a1 objc_destructInstance + 94
10  libobjc.A.dylib               	0x00007fff20387505 _objc_rootDealloc + 62
11  io.sentry.Sentry              	0x000000010b28edb5 -[SentryHub setClient:] + 37
12  io.sentry.Sentry              	0x000000010b28cc63 -[SentryHub bindClient:] + 83 (SentryHub.m:497)
13  io.sentry.Sentry              	0x000000010b2e4269 +[SentrySDK close] + 761 (SentrySDK.m:404)
14  libSentryTestUtils.a          	0x000000010b103cfe static TestCleanup.clearTestState() + 78 (ClearTestState.swift:15)
15  libSentryTestUtils.a          	0x000000010b103c7a clearTestState() + 42 (ClearTestState.swift:5)
16  io.sentry.Sentry.tests        	0x000000010f431109 SentryNetworkTrackerIntegrationTests.tearDown() + 105 (SentryNetworkTrackerIntegrationTests.swift:35)
17  io.sentry.Sentry.tests        	0x000000010f43112c @objc SentryNetworkTrackerIntegrationTests.tearDown() + 28
18  com.apple.dt.XCTestCore       	0x000000010a8d2348 __86-[XCTestCase _performTearDownSequenceWithSelector:permittingControlFlowInterruptions:]_block_invoke_2 + 21
19  com.apple.dt.XCTestCore       	0x000000010a8d25a0 __79-[XCTestCase _performFailableTearDownBlock:permittingControlFlowInterruptions:]_block_invoke + 38
20  com.apple.dt.XCTestCore       	0x000000010a896b86 -[XCTestCase(XCTIssueHandling) _caughtUnhandledDeveloperExceptionPermittingControlFlowInterruptions:caughtInterruptionException:whileExecutingBlock:] + 179
21  com.apple.dt.XCTestCore       	0x000000010a8d23cf -[XCTestCase _performFailableTearDownBlock:permittingControlFlowInterruptions:] + 131
22  com.apple.dt.XCTestCore       	0x000000010a8d1fa5 __86-[XCTestCase _performTearDownSequenceWithSelector:permittingControlFlowInterruptions:]_block_invoke + 126
23  com.apple.dt.XCTestCore       	0x000000010a8b6483 -[XCTContext _runActivityNamed:type:block:] + 301
24  com.apple.dt.XCTestCore       	0x000000010a8da513 -[XCTestCase startActivityWithTitle:type:block:] + 186
25  com.apple.dt.XCTestCore       	0x000000010a8d0c64 -[XCTestCase _performTearDownSequenceWithSelector:] + 90
26  com.apple.dt.XCTestCore       	0x000000010a8d0129 -[XCTestCase invokeTest] + 1205
27  com.apple.dt.XCTestCore       	0x000000010a8d190b __26-[XCTestCase performTest:]_block_invoke.379 + 38
28  com.apple.dt.XCTestCore       	0x000000010a896b86 -[XCTestCase(XCTIssueHandling) _caughtUnhandledDeveloperExceptionPermittingControlFlowInterruptions:caughtInterruptionException:whileExecutingBlock:] + 179
29  com.apple.dt.XCTestCore       	0x000000010a8d1288 __26-[XCTestCase performTest:]_block_invoke.358 + 523
30  com.apple.dt.XCTestCore       	0x000000010a8b6afb +[XCTContext runInContextForTestCase:markAsReportingBase:block:] + 220
31  com.apple.dt.XCTestCore       	0x000000010a8d0e9a -[XCTestCase performTest:] + 287
32  com.apple.dt.XCTestCore       	0x000000010a8850c5 -[XCTest runTest] + 57
33  com.apple.dt.XCTestCore       	0x000000010a8b9d0f -[XCTestSuite runTestBasedOnRepetitionPolicy:testRun:] + 156
34  com.apple.dt.XCTestCore       	0x000000010a8b9b85 __27-[XCTestSuite performTest:]_block_invoke + 252
35  com.apple.dt.XCTestCore       	0x000000010a8b9484 __59-[XCTestSuite _performProtectedSectionForTest:testSection:]_block_invoke + 24
36  com.apple.dt.XCTestCore       	0x000000010a8b6afb +[XCTContext runInContextForTestCase:markAsReportingBase:block:] + 220
37  com.apple.dt.XCTestCore       	0x000000010a8b943b -[XCTestSuite _performProtectedSectionForTest:testSection:] + 159
38  com.apple.dt.XCTestCore       	0x000000010a8b9730 -[XCTestSuite performTest:] + 222
39  com.apple.dt.XCTestCore       	0x000000010a8850c5 -[XCTest runTest] + 57
40  com.apple.dt.XCTestCore       	0x000000010a8b9d0f -[XCTestSuite runTestBasedOnRepetitionPolicy:testRun:] + 156
41  com.apple.dt.XCTestCore       	0x000000010a8b9b85 __27-[XCTestSuite performTest:]_block_invoke + 252
42  com.apple.dt.XCTestCore       	0x000000010a8b9484 __59-[XCTestSuite _performProtectedSectionForTest:testSection:]_block_invoke + 24
43  com.apple.dt.XCTestCore       	0x000000010a8b6afb +[XCTContext runInContextForTestCase:markAsReportingBase:block:] + 220
44  com.apple.dt.XCTestCore       	0x000000010a8b943b -[XCTestSuite _performProtectedSectionForTest:testSection:] + 159
45  com.apple.dt.XCTestCore       	0x000000010a8b9730 -[XCTestSuite performTest:] + 222
46  com.apple.dt.XCTestCore       	0x000000010a8850c5 -[XCTest runTest] + 57
47  com.apple.dt.XCTestCore       	0x000000010a8b9d0f -[XCTestSuite runTestBasedOnRepetitionPolicy:testRun:] + 156
48  com.apple.dt.XCTestCore       	0x000000010a8b9b85 __27-[XCTestSuite performTest:]_block_invoke + 252
49  com.apple.dt.XCTestCore       	0x000000010a8b9484 __59-[XCTestSuite _performProtectedSectionForTest:testSection:]_block_invoke + 24
50  com.apple.dt.XCTestCore       	0x000000010a8b6afb +[XCTContext runInContextForTestCase:markAsReportingBase:block:] + 220
51  com.apple.dt.XCTestCore       	0x000000010a8b943b -[XCTestSuite _performProtectedSectionForTest:testSection:] + 159
52  com.apple.dt.XCTestCore       	0x000000010a8b9730 -[XCTestSuite performTest:] + 222
53  com.apple.dt.XCTestCore       	0x000000010a8850c5 -[XCTest runTest] + 57
54  com.apple.dt.XCTestCore       	0x000000010a886725 __89-[XCTTestRunSession executeTestsWithIdentifiers:skippingTestsWithIdentifiers:completion:]_block_invoke + 374
55  com.apple.dt.XCTestCore       	0x000000010a8b6afb +[XCTContext runInContextForTestCase:markAsReportingBase:block:] + 220
56  com.apple.dt.XCTestCore       	0x000000010a886544 -[XCTTestRunSession executeTestsWithIdentifiers:skippingTestsWithIdentifiers:completion:] + 212
57  com.apple.dt.XCTestCore       	0x000000010a8f205c __72-[XCTExecutionWorker enqueueTestIdentifiersToRun:testIdentifiersToSkip:]_block_invoke_2 + 119
58  com.apple.dt.XCTestCore       	0x000000010a8f21a4 -[XCTExecutionWorker runWithError:] + 112
59  com.apple.dt.XCTestCore       	0x000000010a8b3ee5 __25-[XCTestDriver _runTests]_block_invoke + 61
60  com.apple.dt.XCTestCore       	0x000000010a88f25b -[XCTestObservationCenter _observeTestExecutionForBlock:] + 325
61  com.apple.dt.XCTestCore       	0x000000010a8b3c46 -[XCTestDriver _runTests] + 1397
62  com.apple.dt.XCTestCore       	0x000000010a88566a _XCTestMain + 125
63  xctest                        	0x000000010a3f1571 main + 355
64  libdyld.dylib                 	0x00007fff20502f3d start + 1

Thread 1:: SentryCrash Exception Handler (Secondary)
0   libsystem_kernel.dylib        	0x00007fff204b229a mach_msg_trap + 10
1   libsystem_kernel.dylib        	0x00007fff204b260c mach_msg + 60
2   libsystem_kernel.dylib        	0x00007fff204d197b thread_suspend + 80
3   io.sentry.Sentry              	0x000000010b29479a handleExceptions + 186 (SentryCrashMonitor_MachException.c:306)
4   libsystem_pthread.dylib       	0x00007fff204e78fc _pthread_start + 224
5   libsystem_pthread.dylib       	0x00007fff204e3443 thread_start + 15

Thread 2:
0   libsystem_pthread.dylib       	0x00007fff204e3420 start_wqthread + 0

Thread 3:
0   libsystem_pthread.dylib       	0x00007fff204e3420 start_wqthread + 0

Thread 4:
0   libsystem_kernel.dylib        	0x00007fff204b229a mach_msg_trap + 10
1   libsystem_kernel.dylib        	0x00007fff204b260c mach_msg + 60
2   com.apple.CoreFoundation      	0x00007fff205dfc45 __CFRunLoopServiceMachPort + 316
3   com.apple.CoreFoundation      	0x00007fff205de30b __CFRunLoopRun + 1332
4   com.apple.CoreFoundation      	0x00007fff205dd710 CFRunLoopRunSpecific + 567
5   com.apple.CoreFoundation      	0x00007fff20663fa8 CFRunLoopRun + 40
6   com.apple.DebugSymbols        	0x000000010af81e98 SpotlightQueryThread(void*) + 472
7   libsystem_pthread.dylib       	0x00007fff204e78fc _pthread_start + 224
8   libsystem_pthread.dylib       	0x00007fff204e3443 thread_start + 15

Thread 5:
0   libsystem_pthread.dylib       	0x00007fff204e3420 start_wqthread + 0

Thread 6:
0   libsystem_pthread.dylib       	0x00007fff204e3420 start_wqthread + 0

Thread 7:
0   libsystem_pthread.dylib       	0x00007fff204e3420 start_wqthread + 0

Thread 8:
0   libsystem_pthread.dylib       	0x00007fff204e3420 start_wqthread + 0

Thread 9:
0   libsystem_pthread.dylib       	0x00007fff204e3420 start_wqthread + 0

Thread 10:
0   libsystem_pthread.dylib       	0x00007fff204e3420 start_wqthread + 0

Thread 11:
0   libsystem_pthread.dylib       	0x00007fff204e3420 start_wqthread + 0

Thread 12:
0   libsystem_pthread.dylib       	0x00007fff204e3420 start_wqthread + 0

Thread 13:: io.sentry.app-hang-tracker
0   libsystem_kernel.dylib        	0x00007fff204b4b92 __semwait_signal + 10
1   libsystem_c.dylib             	0x00007fff20434c1a nanosleep + 196
2   com.apple.Foundation          	0x00007fff21326bc8 +[NSThread sleepForTimeInterval:] + 170
3   io.sentry.Sentry              	0x000000010b2ae650 -[SentryThreadWrapper sleepForTimeInterval:] + 64 (SentryThreadWrapper.m:9)
4   io.sentry.Sentry              	0x000000010b263816 -[SentryANRTracker detectANRs] + 1526 (SentryANRTracker.m:103)
5   com.apple.Foundation          	0x00007fff21293487 __NSThread__start__ + 1068
6   libsystem_pthread.dylib       	0x00007fff204e78fc _pthread_start + 224
7   libsystem_pthread.dylib       	0x00007fff204e3443 thread_start + 15

Thread 14:: io.sentry.app-hang-tracker
0   com.apple.CoreFoundation      	0x00007fff205dd19c -[NSDictionary allValues] + 142
1   io.sentry.Sentry              	0x000000010b2ad45b -[SentryReachability removeObserver:] + 155 (SentryReachability.m:146)
2   io.sentry.Sentry              	0x000000010b2acf59 -[SentryReachability dealloc] + 361 (SentryReachability.m:113)
3   io.sentry.Sentry              	0x000000010b2fc78f -[SentryDependencyContainer .cxx_destruct] + 63 (SentryDependencyContainer.m:42)
4   libobjc.A.dylib               	0x00007fff2038ea04 object_cxxDestructFromClass(objc_object*, objc_class*) + 83
5   libobjc.A.dylib               	0x00007fff203875a1 objc_destructInstance + 94
6   libobjc.A.dylib               	0x00007fff20387505 _objc_rootDealloc + 62
7   libobjc.A.dylib               	0x00007fff203a5343 AutoreleasePoolPage::releaseUntil(objc_object**) + 167
8   libobjc.A.dylib               	0x00007fff20387d94 objc_autoreleasePoolPop + 161
9   libobjc.A.dylib               	0x00007fff203a54b5 AutoreleasePoolPage::tls_dealloc(void*) + 91
10  libsystem_pthread.dylib       	0x00007fff204e598d _pthread_tsd_cleanup + 487
11  libsystem_pthread.dylib       	0x00007fff204e7f65 _pthread_exit + 70
12  libsystem_pthread.dylib       	0x00007fff204e5763 pthread_exit + 42
13  com.apple.Foundation          	0x00007fff21303aec +[NSThread exit] + 11
14  com.apple.Foundation          	0x00007fff2129349b __NSThread__start__ + 1088
15  libsystem_pthread.dylib       	0x00007fff204e78fc _pthread_start + 224
16  libsystem_pthread.dylib       	0x00007fff204e3443 thread_start + 15

Thread 15 Crashed:: io.sentry.app-hang-tracker
0   libobjc.A.dylib               	0x00007fff20385613 objc_retain + 19
1   com.apple.CoreFoundation      	0x00007fff205dd1a5 -[NSDictionary allValues] + 151
2   io.sentry.Sentry              	0x000000010b2ad45b -[SentryReachability removeObserver:] + 155 (SentryReachability.m:146)
3   io.sentry.Sentry              	0x000000010b2acf59 -[SentryReachability dealloc] + 361 (SentryReachability.m:113)
4   io.sentry.Sentry              	0x000000010b2fc78f -[SentryDependencyContainer .cxx_destruct] + 63 (SentryDependencyContainer.m:42)
5   libobjc.A.dylib               	0x00007fff2038ea04 object_cxxDestructFromClass(objc_object*, objc_class*) + 83
6   libobjc.A.dylib               	0x00007fff203875a1 objc_destructInstance + 94
7   libobjc.A.dylib               	0x00007fff20387505 _objc_rootDealloc + 62
8   libobjc.A.dylib               	0x00007fff203a5343 AutoreleasePoolPage::releaseUntil(objc_object**) + 167
9   libobjc.A.dylib               	0x00007fff20387d94 objc_autoreleasePoolPop + 161
10  libobjc.A.dylib               	0x00007fff203a54b5 AutoreleasePoolPage::tls_dealloc(void*) + 91
11  libsystem_pthread.dylib       	0x00007fff204e598d _pthread_tsd_cleanup + 487
12  libsystem_pthread.dylib       	0x00007fff204e7f65 _pthread_exit + 70
13  libsystem_pthread.dylib       	0x00007fff204e5763 pthread_exit + 42
14  com.apple.Foundation          	0x00007fff21303aec +[NSThread exit] + 11
15  com.apple.Foundation          	0x00007fff2129349b __NSThread__start__ + 1088
16  libsystem_pthread.dylib       	0x00007fff204e78fc _pthread_start + 224
17  libsystem_pthread.dylib       	0x00007fff204e3443 thread_start + 15

Thread 16:: io.sentry.app-hang-tracker
0   libobjc.A.dylib               	0x00007fff20385600 objc_retain + 0
1   com.apple.CoreFoundation      	0x00007fff205cfbbf -[NSDictionary allKeys] + 151
2   io.sentry.Sentry              	0x000000010b2ace53 -[SentryReachability dealloc] + 99 (SentryReachability.m:112)
3   io.sentry.Sentry              	0x000000010b2fc78f -[SentryDependencyContainer .cxx_destruct] + 63 (SentryDependencyContainer.m:42)
4   libobjc.A.dylib               	0x00007fff2038ea04 object_cxxDestructFromClass(objc_object*, objc_class*) + 83
5   libobjc.A.dylib               	0x00007fff203875a1 objc_destructInstance + 94
6   libobjc.A.dylib               	0x00007fff20387505 _objc_rootDealloc + 62
7   libobjc.A.dylib               	0x00007fff203a5343 AutoreleasePoolPage::releaseUntil(objc_object**) + 167
8   libobjc.A.dylib               	0x00007fff20387d94 objc_autoreleasePoolPop + 161
9   libobjc.A.dylib               	0x00007fff203a54b5 AutoreleasePoolPage::tls_dealloc(void*) + 91
10  libsystem_pthread.dylib       	0x00007fff204e598d _pthread_tsd_cleanup + 487
11  libsystem_pthread.dylib       	0x00007fff204e7f65 _pthread_exit + 70
12  libsystem_pthread.dylib       	0x00007fff204e5763 pthread_exit + 42
13  com.apple.Foundation          	0x00007fff21303aec +[NSThread exit] + 11
14  com.apple.Foundation          	0x00007fff2129349b __NSThread__start__ + 1088
15  libsystem_pthread.dylib       	0x00007fff204e78fc _pthread_start + 224
16  libsystem_pthread.dylib       	0x00007fff204e3443 thread_start + 15

Thread 17:: io.sentry.app-hang-tracker
0   com.apple.CoreFoundation      	0x00007fff205cfbbf -[NSDictionary allKeys] + 151
1   io.sentry.Sentry              	0x000000010b2ace53 -[SentryReachability dealloc] + 99 (SentryReachability.m:112)
2   io.sentry.Sentry              	0x000000010b2fc78f -[SentryDependencyContainer .cxx_destruct] + 63 (SentryDependencyContainer.m:42)
3   libobjc.A.dylib               	0x00007fff2038ea04 object_cxxDestructFromClass(objc_object*, objc_class*) + 83
4   libobjc.A.dylib               	0x00007fff203875a1 objc_destructInstance + 94
5   libobjc.A.dylib               	0x00007fff20387505 _objc_rootDealloc + 62
6   libobjc.A.dylib               	0x00007fff203a5343 AutoreleasePoolPage::releaseUntil(objc_object**) + 167
7   libobjc.A.dylib               	0x00007fff20387d94 objc_autoreleasePoolPop + 161
8   libobjc.A.dylib               	0x00007fff203a54b5 AutoreleasePoolPage::tls_dealloc(void*) + 91
9   libsystem_pthread.dylib       	0x00007fff204e598d _pthread_tsd_cleanup + 487
10  libsystem_pthread.dylib       	0x00007fff204e7f65 _pthread_exit + 70
11  libsystem_pthread.dylib       	0x00007fff204e5763 pthread_exit + 42
12  com.apple.Foundation          	0x00007fff21303aec +[NSThread exit] + 11
13  com.apple.Foundation          	0x00007fff2129349b __NSThread__start__ + 1088
14  libsystem_pthread.dylib       	0x00007fff204e78fc _pthread_start + 224
15  libsystem_pthread.dylib       	0x00007fff204e3443 thread_start + 15

Thread 18:: io.sentry.app-hang-tracker
0   libsystem_kernel.dylib        	0x00007fff204b4b92 __semwait_signal + 10
1   libsystem_c.dylib             	0x00007fff20434c1a nanosleep + 196
2   com.apple.Foundation          	0x00007fff21326bc8 +[NSThread sleepForTimeInterval:] + 170
3   io.sentry.Sentry              	0x000000010b2ae650 -[SentryThreadWrapper sleepForTimeInterval:] + 64 (SentryThreadWrapper.m:9)
4   io.sentry.Sentry              	0x000000010b263816 -[SentryANRTracker detectANRs] + 1526 (SentryANRTracker.m:103)
5   com.apple.Foundation          	0x00007fff21293487 __NSThread__start__ + 1068
6   libsystem_pthread.dylib       	0x00007fff204e78fc _pthread_start + 224
7   libsystem_pthread.dylib       	0x00007fff204e3443 thread_start + 15

Thread 19:: io.sentry.app-hang-tracker
0   libsystem_kernel.dylib        	0x00007fff204b4b92 __semwait_signal + 10
1   libsystem_c.dylib             	0x00007fff20434c1a nanosleep + 196
2   com.apple.Foundation          	0x00007fff21326bc8 +[NSThread sleepForTimeInterval:] + 170
3   io.sentry.Sentry              	0x000000010b2ae650 -[SentryThreadWrapper sleepForTimeInterval:] + 64 (SentryThreadWrapper.m:9)
4   io.sentry.Sentry              	0x000000010b263816 -[SentryANRTracker detectANRs] + 1526 (SentryANRTracker.m:103)
5   com.apple.Foundation          	0x00007fff21293487 __NSThread__start__ + 1068
6   libsystem_pthread.dylib       	0x00007fff204e78fc _pthread_start + 224
7   libsystem_pthread.dylib       	0x00007fff204e3443 thread_start + 15

Thread 20:: com.apple.CFNetwork.CustomProtocols
0   libsystem_kernel.dylib        	0x00007fff204b229a mach_msg_trap + 10
1   libsystem_kernel.dylib        	0x00007fff204b260c mach_msg + 60
2   com.apple.CoreFoundation      	0x00007fff205dfc45 __CFRunLoopServiceMachPort + 316
3   com.apple.CoreFoundation      	0x00007fff205de30b __CFRunLoopRun + 1332
4   com.apple.CoreFoundation      	0x00007fff205dd710 CFRunLoopRunSpecific + 567
5   com.apple.CFNetwork           	0x00007fff249b1290 0x7fff24772000 + 2355856
6   com.apple.Foundation          	0x00007fff21293487 __NSThread__start__ + 1068
7   libsystem_pthread.dylib       	0x00007fff204e78fc _pthread_start + 224
8   libsystem_pthread.dylib       	0x00007fff204e3443 thread_start + 15

Thread 21:: io.sentry.app-hang-tracker
0   libsystem_kernel.dylib        	0x00007fff204b4b92 __semwait_signal + 10
1   libsystem_c.dylib             	0x00007fff20434c1a nanosleep + 196
2   com.apple.Foundation          	0x00007fff21326bc8 +[NSThread sleepForTimeInterval:] + 170
3   io.sentry.Sentry              	0x000000010b2ae650 -[SentryThreadWrapper sleepForTimeInterval:] + 64 (SentryThreadWrapper.m:9)
4   io.sentry.Sentry              	0x000000010b263816 -[SentryANRTracker detectANRs] + 1526 (SentryANRTracker.m:103)
5   com.apple.Foundation          	0x00007fff21293487 __NSThread__start__ + 1068
6   libsystem_pthread.dylib       	0x00007fff204e78fc _pthread_start + 224
7   libsystem_pthread.dylib       	0x00007fff204e3443 thread_start + 15

Thread 22:: io.sentry.app-hang-tracker
0   libsystem_kernel.dylib        	0x00007fff204b4b92 __semwait_signal + 10
1   libsystem_c.dylib             	0x00007fff20434c1a nanosleep + 196
2   com.apple.Foundation          	0x00007fff21326bc8 +[NSThread sleepForTimeInterval:] + 170
3   io.sentry.Sentry              	0x000000010b2ae650 -[SentryThreadWrapper sleepForTimeInterval:] + 64 (SentryThreadWrapper.m:9)
4   io.sentry.Sentry              	0x000000010b263816 -[SentryANRTracker detectANRs] + 1526 (SentryANRTracker.m:103)
5   com.apple.Foundation          	0x00007fff21293487 __NSThread__start__ + 1068
6   libsystem_pthread.dylib       	0x00007fff204e78fc _pthread_start + 224
7   libsystem_pthread.dylib       	0x00007fff204e3443 thread_start + 15

Thread 23:: com.apple.NSURLConnectionLoader
0   libsystem_kernel.dylib        	0x00007fff204b229a mach_msg_trap + 10
1   libsystem_kernel.dylib        	0x00007fff204b260c mach_msg + 60
2   com.apple.CoreFoundation      	0x00007fff205dfc45 __CFRunLoopServiceMachPort + 316
3   com.apple.CoreFoundation      	0x00007fff205de30b __CFRunLoopRun + 1332
4   com.apple.CoreFoundation      	0x00007fff205dd710 CFRunLoopRunSpecific + 567
5   com.apple.CFNetwork           	0x00007fff249b1290 0x7fff24772000 + 2355856
6   com.apple.Foundation          	0x00007fff21293487 __NSThread__start__ + 1068
7   libsystem_pthread.dylib       	0x00007fff204e78fc _pthread_start + 224
8   libsystem_pthread.dylib       	0x00007fff204e3443 thread_start + 15

Thread 15 crashed with X86 Thread State (64-bit):
  rax: 0x0000000000000000  rbx: 0x0000000000000005  rcx: 0x000000000000fffe  rdx: 0x00000000c300000c
  rdi: 0x0061007200650070  rsi: 0x00007fff7baaaf79  rbp: 0x000070000483a980  rsp: 0x000070000483a948
   r8: 0x00007fa6abf89978   r9: 0x000000000000d80c  r10: 0x00007fff807de050  r11: 0x00007fff202206b2
  r12: 0x0000000000000007  r13: 0x00007fff2038650c  r14: 0x00007fa6ab9dc100  r15: 0x00007fa6aba6b150
  rip: 0x00007fff20385613  rfl: 0x0000000000010246  cr2: 0x00007fff2488d075
  
Logical CPU:     0
Error Code:      0x0100001f
Trap Number:     133

Thread 15 instruction stream not available.

Thread 15 last branch register state not available.

Some links to crash reports with the same root cause:

💚 How did you test it?

Unit tests and on a real device to ensure the logic of reachability is still working.

📝 Checklist

You have to check all boxes before merging:

  • I reviewed the submitted code.
  • I added tests to verify the changes.
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled.
  • I updated the docs if needed.
  • Review from the native team if needed.
  • No breaking change or entry added to the changelog.
  • No breaking change for hybrid SDKs or communicated to hybrid SDKs.

🔮 Next steps

The tests can launch multiple threads of the ANR tracker, which call
dealloc of SentryReachability simultaneously, leading to a segfault
while iterating over the observers in SentryReachability. This is fixed
now by using some synchronization around the observer's access.
Furthermore, the bookkeeping of the observers is implemented now with
weak references using an NSHashTable. Instead of relying on dealloc to
be called for removing the observers, each observer must now call
removeObserver after subscribing. If an observer doesn't call
removeObserver no memory is leaked cause the SentryReachability uses
weak references. Previously, the list of observers used a dictionary
with the description method of Objective-C as the key, which is
suboptimal cause the description could change between adding and
removing the observer, leading to memory leaks when the observer's
description changed between invocations.
@philipphofmann philipphofmann changed the title fix: Multiple issues in Reachability fix: Multiple issues in SentryReachability Oct 9, 2023
@github-actions
Copy link

github-actions bot commented Oct 9, 2023

Performance metrics 🚀

  Plain With Sentry Diff
Startup time 1231.59 ms 1246.10 ms 14.51 ms
Size 22.85 KiB 410.96 KiB 388.11 KiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
1bbcb9c 1192.51 ms 1231.96 ms 39.45 ms
160a073 1260.72 ms 1270.10 ms 9.38 ms
3b782cc 1261.67 ms 1272.24 ms 10.57 ms
7bc3c0d 1259.74 ms 1268.45 ms 8.71 ms
f587451 1271.63 ms 1275.90 ms 4.27 ms
df2986b 1215.20 ms 1235.02 ms 19.82 ms
32e64d1 1224.45 ms 1238.94 ms 14.49 ms
2d5b1bd 1244.04 ms 1255.18 ms 11.14 ms
c78683b 1255.78 ms 1261.94 ms 6.16 ms
46c6025 1213.87 ms 1253.06 ms 39.19 ms

App size

Revision Plain With Sentry Diff
1bbcb9c 20.76 KiB 426.10 KiB 405.34 KiB
160a073 22.85 KiB 408.88 KiB 386.03 KiB
3b782cc 20.76 KiB 432.21 KiB 411.45 KiB
7bc3c0d 20.76 KiB 427.35 KiB 406.59 KiB
f587451 20.76 KiB 435.25 KiB 414.49 KiB
df2986b 22.85 KiB 406.89 KiB 384.04 KiB
32e64d1 20.76 KiB 433.18 KiB 412.42 KiB
2d5b1bd 22.84 KiB 403.52 KiB 380.67 KiB
c78683b 20.76 KiB 435.24 KiB 414.47 KiB
46c6025 22.85 KiB 408.84 KiB 385.99 KiB

Previous results on branch: fix/reachability-crash

Startup times

Revision Plain With Sentry Diff
8e131db 1232.88 ms 1252.44 ms 19.56 ms
bccf130 1197.15 ms 1223.64 ms 26.49 ms
18ba1ad 1228.34 ms 1251.46 ms 23.12 ms
5b1adbc 1237.02 ms 1243.84 ms 6.82 ms
9eada8d 1221.07 ms 1248.74 ms 27.67 ms

App size

Revision Plain With Sentry Diff
8e131db 22.85 KiB 408.82 KiB 385.97 KiB
bccf130 22.85 KiB 409.45 KiB 386.60 KiB
18ba1ad 22.85 KiB 408.82 KiB 385.97 KiB
5b1adbc 22.85 KiB 410.39 KiB 387.54 KiB
9eada8d 22.85 KiB 410.31 KiB 387.46 KiB

@codecov
Copy link

codecov bot commented Oct 9, 2023

Codecov Report

Merging #3338 (51d8267) into main (2401cbd) will increase coverage by 0.104%.
The diff coverage is 97.156%.

Impacted file tree graph

@@              Coverage Diff              @@
##              main     #3338       +/-   ##
=============================================
+ Coverage   89.205%   89.310%   +0.104%     
=============================================
  Files          501       502        +1     
  Lines        54085     54410      +325     
  Branches     19203     19544      +341     
=============================================
+ Hits         48247     48594      +347     
+ Misses        4860      4845       -15     
+ Partials       978       971        -7     
Files Coverage Δ
Sources/Sentry/SentryBreadcrumbTracker.m 66.145% <100.000%> (+0.720%) ⬆️
...tryTests/Networking/SentryHttpTransportTests.swift 97.688% <100.000%> (+0.133%) ⬆️
...ests/Networking/SentryReachabilitySwiftTests.swift 100.000% <100.000%> (ø)
...s/SentryTests/Networking/SentryReachabilityTests.m 100.000% <100.000%> (ø)
...entryTests/Networking/TestSentryReachability.swift 100.000% <100.000%> (ø)
Sources/Sentry/SentryHttpTransport.m 97.747% <77.777%> (-0.060%) ⬇️
Sources/Sentry/SentryReachability.m 98.347% <97.435%> (+1.013%) ⬆️
...ons/Breadcrumbs/SentryBreadcrumbTrackerTests.swift 96.666% <85.714%> (+0.464%) ⬆️

... and 70 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 2401cbd...51d8267. Read the comment docs.

Copy link
Member

@armcknight armcknight left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the description could change between adding and removing the observer

Ouch. Didn't know this when I made some changes around here in #3232. How does the description change, a new pointer address? Or is it something we're overriding? I looked for such overrides but we don't provide a custom description for SentryBreadcrumbTracker or SentryHttpTransport. Great catch in any case.

I looked at some of the failing tests, and it looks like testIntegration_UrlSessionDelegate_PassedToRequestManager is failing on CI for iOS/tvOS because it's trying to make a real network request, and I'm guessing some kind of configuration issue is preventing it from succeeding? Or maybe an intermittent issue? Not sure why it'd only start failing now. But I think it might make more sense anyways for that to be pointed at the localhost test server vs actual the real sentry.io host over the internet:

2023-10-09 13:25:29.398425+0000 xctest[7127:38848] Task <F2188CA5-B9D5-427E-BF75-CB882EED47F7>.<1> finished with error [-1001] Error Domain=NSURLErrorDomain Code=-1001 "The request timed out." UserInfo={_kCFStreamErrorCodeKey=-2102, NSUnderlyingError=0x600004b11800 {Error Domain=kCFErrorDomainCFNetwork Code=-1001 "(null)" UserInfo={_kCFStreamErrorCodeKey=-2102, _kCFStreamErrorDomainKey=4}}, _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <F2188CA5-B9D5-427E-BF75-CB882EED47F7>.<1>, _NSURLErrorRelatedURLSessionTaskErrorKey=(
    "LocalDataTask <F2188CA5-B9D5-427E-BF75-CB882EED47F7>.<1>"
), NSLocalizedDescription=The request timed out., NSErrorFailingURLStringKey=https://app.getsentry.com/api/12345/envelope/, NSErrorFailingURLKey=https://app.getsentry.com/api/12345/envelope/, _kCFStreamErrorDomainKey=4}
2023-10-09 13:25:29.398693+0000 xctest[7127:38848] [Sentry] [debug] [SentryRequestOperation:34] Request status: 0
2023-10-09 13:25:29.398935+0000 xctest[7127:38848] [Sentry] [error] [SentryRequestOperation:42] Request failed: Error Domain=NSURLErrorDomain Code=-1001 "The request timed out." UserInfo={_kCFStreamErrorCodeKey=-2102, NSUnderlyingError=0x600004b11800 {Error Domain=kCFErrorDomainCFNetwork Code=-1001 "(null)" UserInfo={_kCFStreamErrorCodeKey=-2102, _kCFStreamErrorDomainKey=4}}, _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <F2188CA5-B9D5-427E-BF75-CB882EED47F7>.<1>, _NSURLErrorRelatedURLSessionTaskErrorKey=(
    "LocalDataTask <F2188CA5-B9D5-427E-BF75-CB882EED47F7>.<1>"
), NSLocalizedDescription=The request timed out., NSErrorFailingURLStringKey=https://app.getsentry.com/api/12345/envelope/, NSErrorFailingURLKey=https://app.getsentry.com/api/12345/envelope/, _kCFStreamErrorDomainKey=4}
2023-10-09 13:25:29.399393+0000 xctest[7127:38848] [Sentry] [debug] [SentryQueueableRequestManager:42] Queued requests: 0
2023-10-09 13:25:29.399562+0000 xctest[7127:38848] [Sentry] [debug] [SentryHttpTransport:340] WeakSelf is nil. Not doing anything.

I'm not sure yet why testMultipleReachabilityObservers is failing on macOS, nothing obvious is being logged yet, and it passes for me locally, I added some debug logs in 7cd6146.

category:@"device.connectivity"];
crumb.type = @"connectivity";
crumb.data = [NSDictionary dictionaryWithObject:typeDescription forKey:@"connectivity"];
[self.delegate addBreadcrumb:crumb];
Copy link
Member

@armcknight armcknight Oct 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we'd made this self.delegate a weak self instead, would we have broken a block capture causing issues here? Just trying to understand why this works if you break use the delegate callback pattern instead of ObjC block callback.

ETA: I believe NSHashTable uses -[NSObject hash] as the key for the objects; couldn't we use the NSDictionary with the hashes as keys instead of the problematic descriptions? Moot point I guess since you've done the work, but if that would work we might consider doing that to minimize git history churn. We could swap out NSDictionary for NSMapTable to get the weak object semantics.

Copy link
Member Author

@philipphofmann philipphofmann Oct 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The self.delegate is already weak reference see

@property (nonatomic, weak) id<SentryBreadcrumbDelegate> delegate;
so we don't need to make it a weak self.

I personally prefer the delegate pattern, cause you don't need to worry about using weak references in all the block if you set it up properly. Otherwise, you have to ensure using weak references in the blocks which is easier to get wrong.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

couldn't we use the NSDictionary with the hashes as keys instead of the problematic descriptions

Yes, if we use NSMapTable as pointed out by you, but I would prefer the NSHashTable, cause we don't have to worry about how to come up with the keys and write .allValues two times. I can change it if you prefer. No strong opinion on this one.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, I'm fine with all this!

if (sentry_current_reachability_state != kSCNetworkReachabilityFlagsUninitialized) {
return;
}
if (sentry_reachability_observers.count > 1) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if we remove all observers, and then add one again? It will reenter the logic below, is that desired? I still think this would be better with a dispatch_once to avoid that situation and also it may just be more optimized than calling objc_msgSend for count and comparing the integer value.

ETA: I see how it's cleaned up after reading below. I would say at a minimum leave a comment explaining here, but ideally we'd have some more semantic code wrapping init/teardown. You already have the teardown wrapped, but the init is a bit unclear here. In general though I like your solution best because we release the memory for the unused queue and stop callbacks from SCNetworkReachabilitySetCallback being sent when there are no observers. Nice work 👍🏻

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dispatch once caused some problems in the tests. Yes, I want to properly teardown everything. I will include your feedback. Thanks 🙏

Copy link
Member

@armcknight armcknight Oct 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah yeah that is another good point 👍🏻 guess we need a SentryDispatchOnce wrapper we can subclass for tests 😆

Tests/SentryTests/Networking/SentryReachabilityTests.m Outdated Show resolved Hide resolved
@philipphofmann
Copy link
Member Author

How does the description change, a new pointer address? Or is it something we're overriding? I looked for such overrides but we don't provide a custom description for SentryBreadcrumbTracker or SentryHttpTransport. Great catch in any case.

I think we don't, but such a change could happen any time in the future.

@philipphofmann
Copy link
Member Author

philipphofmann commented Oct 11, 2023

I looked at some of the failing tests, and it looks like testIntegration_UrlSessionDelegate_PassedToRequestManager is failing on CI for iOS/tvOS because it's trying to make a real network request, and I'm guessing some kind of configuration issue is preventing it from succeeding?

Thanks for looking into it @armcknight, but the problem was that my test here

func testAddRemoveFromMultipleThreads() throws {
let sut = SentryReachability()
testConcurrentModifications(writeWork: {_ in
sut.add(TestReachabilityObserver())
}, readWork: {
sut.removeAllObservers()
})
}
}

created hundreds of threads cause when adding the observer I didn't check if it's already in the list and always created a new dispatch queue, which I do know:

@synchronized(sentry_reachability_observers) {
if ([sentry_reachability_observers containsObject:observer]) {
SENTRY_LOG_DEBUG(@"Observer already added. Doing nothing.");
return;
}
[sentry_reachability_observers addObject:observer];

Copy link
Member

@armcknight armcknight left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great, thanks for working it out @philipphofmann 🥔 I added/tweaked a few logs and will merge it once CI runs again.

Sources/Sentry/SentryReachability.m Outdated Show resolved Hide resolved
Sources/Sentry/SentryReachability.m Outdated Show resolved Hide resolved
Sources/Sentry/SentryReachability.m Show resolved Hide resolved
Sources/Sentry/SentryReachability.m Show resolved Hide resolved
@armcknight armcknight merged commit 1437c68 into main Oct 11, 2023
1 check passed
@armcknight armcknight deleted the fix/reachability-crash branch October 11, 2023 20:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants