Skip to content

Commit 154f795

Browse files
feat: Experimental support for Swift Async stacktraces (#3051)
Adds support for stitching async Swift Concurrency frames using backtrace_async Co-authored-by: Philipp Hofmann <[email protected]>
1 parent 4bdf3dc commit 154f795

File tree

11 files changed

+161
-15
lines changed

11 files changed

+161
-15
lines changed

.github/workflows/test.yml

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -130,14 +130,6 @@ jobs:
130130
xcode: '14.3'
131131
test-destination-os: 'latest'
132132

133-
# MetricKit doesn't exist for tvOS, so we can still run unit tests with
134-
# Xcode 12 for it.
135-
# tvOS 14
136-
- runs-on: macos-11
137-
platform: 'tvOS'
138-
xcode: '12.5.1'
139-
test-destination-os: 'latest'
140-
141133
# tvOS 15
142134
- runs-on: macos-12
143135
platform: 'tvOS'

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
### Features
6+
7+
- Experimental support for Swift Async stacktraces (#3051)
8+
59
### Fixes
610

711
- Changed `Trace` serialized value of `sampled` from string to boolean (#3067)

Sentry.xcodeproj/project.pbxproj

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@
190190
63FE710120DA4C1000CDBAE8 /* SentryCrashDate.h in Headers */ = {isa = PBXBuildFile; fileRef = 63FE700720DA4C1000CDBAE8 /* SentryCrashDate.h */; };
191191
63FE710320DA4C1000CDBAE8 /* SentryCrashMachineContext_Apple.h in Headers */ = {isa = PBXBuildFile; fileRef = 63FE700820DA4C1000CDBAE8 /* SentryCrashMachineContext_Apple.h */; };
192192
63FE710520DA4C1000CDBAE8 /* SentryCrashLogger.c in Sources */ = {isa = PBXBuildFile; fileRef = 63FE700920DA4C1000CDBAE8 /* SentryCrashLogger.c */; };
193-
63FE710720DA4C1000CDBAE8 /* SentryCrashStackCursor_SelfThread.c in Sources */ = {isa = PBXBuildFile; fileRef = 63FE700A20DA4C1000CDBAE8 /* SentryCrashStackCursor_SelfThread.c */; };
193+
63FE710720DA4C1000CDBAE8 /* SentryCrashStackCursor_SelfThread.m in Sources */ = {isa = PBXBuildFile; fileRef = 63FE700A20DA4C1000CDBAE8 /* SentryCrashStackCursor_SelfThread.m */; };
194194
63FE710920DA4C1000CDBAE8 /* SentryCrashFileUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 63FE700B20DA4C1000CDBAE8 /* SentryCrashFileUtils.h */; };
195195
63FE710B20DA4C1000CDBAE8 /* SentryCrashMach.c in Sources */ = {isa = PBXBuildFile; fileRef = 63FE700C20DA4C1000CDBAE8 /* SentryCrashMach.c */; };
196196
63FE710D20DA4C1000CDBAE8 /* SentryCrashStackCursor_MachineContext.c in Sources */ = {isa = PBXBuildFile; fileRef = 63FE700D20DA4C1000CDBAE8 /* SentryCrashStackCursor_MachineContext.c */; };
@@ -742,6 +742,8 @@
742742
D8370B6C273DF20F00F66E2D /* SentryNSURLSessionTaskSearch.h in Headers */ = {isa = PBXBuildFile; fileRef = D8370B6B273DF20F00F66E2D /* SentryNSURLSessionTaskSearch.h */; };
743743
D84793262788737D00BE8E99 /* SentryByteCountFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = D84793242788737D00BE8E99 /* SentryByteCountFormatter.m */; };
744744
D8479328278873A100BE8E99 /* SentryByteCountFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = D8479327278873A100BE8E99 /* SentryByteCountFormatter.h */; };
745+
D84F833D2A1CC401005828E0 /* SentrySwiftAsyncIntegration.h in Headers */ = {isa = PBXBuildFile; fileRef = D84F833B2A1CC401005828E0 /* SentrySwiftAsyncIntegration.h */; };
746+
D84F833E2A1CC401005828E0 /* SentrySwiftAsyncIntegration.m in Sources */ = {isa = PBXBuildFile; fileRef = D84F833C2A1CC401005828E0 /* SentrySwiftAsyncIntegration.m */; };
745747
D85596F3280580F10041FF8B /* SentryScreenshotIntegration.m in Sources */ = {isa = PBXBuildFile; fileRef = D85596F1280580F10041FF8B /* SentryScreenshotIntegration.m */; };
746748
D855AD62286ED6A4002573E1 /* SentryCrashTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D855AD61286ED6A4002573E1 /* SentryCrashTests.m */; };
747749
D855B3E827D652AF00BCED76 /* SentryCoreDataTrackingIntegrationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D855B3E727D652AF00BCED76 /* SentryCoreDataTrackingIntegrationTest.swift */; };
@@ -1060,7 +1062,7 @@
10601062
63FE700720DA4C1000CDBAE8 /* SentryCrashDate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SentryCrashDate.h; sourceTree = "<group>"; };
10611063
63FE700820DA4C1000CDBAE8 /* SentryCrashMachineContext_Apple.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SentryCrashMachineContext_Apple.h; sourceTree = "<group>"; };
10621064
63FE700920DA4C1000CDBAE8 /* SentryCrashLogger.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SentryCrashLogger.c; sourceTree = "<group>"; };
1063-
63FE700A20DA4C1000CDBAE8 /* SentryCrashStackCursor_SelfThread.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SentryCrashStackCursor_SelfThread.c; sourceTree = "<group>"; };
1065+
63FE700A20DA4C1000CDBAE8 /* SentryCrashStackCursor_SelfThread.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SentryCrashStackCursor_SelfThread.m; sourceTree = "<group>"; };
10641066
63FE700B20DA4C1000CDBAE8 /* SentryCrashFileUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SentryCrashFileUtils.h; sourceTree = "<group>"; };
10651067
63FE700C20DA4C1000CDBAE8 /* SentryCrashMach.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SentryCrashMach.c; sourceTree = "<group>"; };
10661068
63FE700D20DA4C1000CDBAE8 /* SentryCrashStackCursor_MachineContext.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SentryCrashStackCursor_MachineContext.c; sourceTree = "<group>"; };
@@ -1671,6 +1673,8 @@
16711673
D8370B6B273DF20F00F66E2D /* SentryNSURLSessionTaskSearch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryNSURLSessionTaskSearch.h; path = include/SentryNSURLSessionTaskSearch.h; sourceTree = "<group>"; };
16721674
D84793242788737D00BE8E99 /* SentryByteCountFormatter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryByteCountFormatter.m; sourceTree = "<group>"; };
16731675
D8479327278873A100BE8E99 /* SentryByteCountFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryByteCountFormatter.h; path = include/SentryByteCountFormatter.h; sourceTree = "<group>"; };
1676+
D84F833B2A1CC401005828E0 /* SentrySwiftAsyncIntegration.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentrySwiftAsyncIntegration.h; path = include/SentrySwiftAsyncIntegration.h; sourceTree = "<group>"; };
1677+
D84F833C2A1CC401005828E0 /* SentrySwiftAsyncIntegration.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentrySwiftAsyncIntegration.m; sourceTree = "<group>"; };
16741678
D85596F1280580F10041FF8B /* SentryScreenshotIntegration.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryScreenshotIntegration.m; sourceTree = "<group>"; };
16751679
D855AD61286ED6A4002573E1 /* SentryCrashTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryCrashTests.m; sourceTree = "<group>"; };
16761680
D855B3E727D652AF00BCED76 /* SentryCoreDataTrackingIntegrationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryCoreDataTrackingIntegrationTest.swift; sourceTree = "<group>"; };
@@ -2436,7 +2440,7 @@
24362440
63FE702B20DA4C1000CDBAE8 /* SentryCrashStackCursor_Backtrace.h */,
24372441
63FE700D20DA4C1000CDBAE8 /* SentryCrashStackCursor_MachineContext.c */,
24382442
63FE703120DA4C1000CDBAE8 /* SentryCrashStackCursor_MachineContext.h */,
2439-
63FE700A20DA4C1000CDBAE8 /* SentryCrashStackCursor_SelfThread.c */,
2443+
63FE700A20DA4C1000CDBAE8 /* SentryCrashStackCursor_SelfThread.m */,
24402444
63FE702620DA4C1000CDBAE8 /* SentryCrashStackCursor_SelfThread.h */,
24412445
63FE703B20DA4C1000CDBAE8 /* SentryCrashStackCursor.c */,
24422446
63FE701C20DA4C1000CDBAE8 /* SentryCrashStackCursor.h */,
@@ -2799,6 +2803,8 @@
27992803
7BCFBD6E2681D0EE00BC27D8 /* SentryCrashScopeObserver.m */,
28002804
7B96571F26830C9100C66E25 /* SentryScopeSyncC.h */,
28012805
7B96572126830D2400C66E25 /* SentryScopeSyncC.c */,
2806+
D84F833B2A1CC401005828E0 /* SentrySwiftAsyncIntegration.h */,
2807+
D84F833C2A1CC401005828E0 /* SentrySwiftAsyncIntegration.m */,
28022808
);
28032809
name = SentryCrash;
28042810
sourceTree = "<group>";
@@ -3445,6 +3451,7 @@
34453451
7B98D7E425FB7A7200C5A389 /* SentryAppState.h in Headers */,
34463452
7BDEAA022632A4580001EA25 /* SentryOptions+Private.h in Headers */,
34473453
A8AFFCCD29069C3E00967CD7 /* SentryHttpStatusCodeRange.h in Headers */,
3454+
D84F833D2A1CC401005828E0 /* SentrySwiftAsyncIntegration.h in Headers */,
34483455
15E0A8EA240F2C9000F044E3 /* SentrySerialization.h in Headers */,
34493456
63FE70EF20DA4C1000CDBAE8 /* SentryCrashMonitor_AppState.h in Headers */,
34503457
635B3F381EBC6E2500A6176D /* SentryAsynchronousOperation.h in Headers */,
@@ -3898,6 +3905,7 @@
38983905
7B8713B426415BAA006D6004 /* SentryAppStartTracker.m in Sources */,
38993906
7BDB03BB2513652900BAE198 /* SentryDispatchQueueWrapper.m in Sources */,
39003907
7B6C5EDE264E8DF00010D138 /* SentryFramesTracker.m in Sources */,
3908+
D84F833E2A1CC401005828E0 /* SentrySwiftAsyncIntegration.m in Sources */,
39013909
7B6438AB26A70F24000D0F65 /* UIViewController+Sentry.m in Sources */,
39023910
63AA76A31EB9CBAA00D153DE /* SentryDsn.m in Sources */,
39033911
63B818FA1EC34639002FDF4C /* SentryDebugMeta.m in Sources */,
@@ -3974,7 +3982,7 @@
39743982
D8F6A2472885512100320515 /* SentryPredicateDescriptor.m in Sources */,
39753983
A839D89A24864BA8003B7AFD /* SentrySystemEventBreadcrumbs.m in Sources */,
39763984
7D082B8323C628790029866B /* SentryMeta.m in Sources */,
3977-
63FE710720DA4C1000CDBAE8 /* SentryCrashStackCursor_SelfThread.c in Sources */,
3985+
63FE710720DA4C1000CDBAE8 /* SentryCrashStackCursor_SelfThread.m in Sources */,
39783986
63FE711120DA4C1000CDBAE8 /* SentryCrashDebug.c in Sources */,
39793987
7B883F49253D714C00879E62 /* SentryCrashUUIDConversion.c in Sources */,
39803988
63FE716720DA4C1100CDBAE8 /* SentryCrashCPU.c in Sources */,

Sources/Sentry/Public/SentryOptions.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,12 @@ NS_SWIFT_NAME(Options)
482482
*/
483483
@property (nonatomic) BOOL enableTimeToFullDisplay;
484484

485+
/**
486+
* @warning This is an experimental feature and may still have bugs.
487+
* @brief Stitches the call to Swift Async functions in one consecutive stack trace.
488+
* @note Default value is @c NO .
489+
*/
490+
@property (nonatomic, assign) BOOL swiftAsyncStacktraces;
485491
@end
486492

487493
NS_ASSUME_NONNULL_END

Sources/Sentry/SentryOptions.m

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ - (void)setMeasurement:(SentryMeasurementValue *)measurement
4343
@"SentryAutoBreadcrumbTrackingIntegration", @"SentryAutoSessionTrackingIntegration",
4444
@"SentryAppStartTrackingIntegration", @"SentryWatchdogTerminationTrackingIntegration",
4545
@"SentryPerformanceTrackingIntegration", @"SentryNetworkTrackingIntegration",
46-
@"SentryFileIOTrackingIntegration", @"SentryCoreDataTrackingIntegration"
46+
@"SentryFileIOTrackingIntegration", @"SentryCoreDataTrackingIntegration",
47+
@"SentrySwiftAsyncIntegration"
4748
]
4849
.mutableCopy;
4950

@@ -106,6 +107,7 @@ - (instancetype)init
106107
self.enableCoreDataTracing = YES;
107108
_enableSwizzling = YES;
108109
self.sendClientReports = YES;
110+
self.swiftAsyncStacktraces = NO;
109111

110112
#if TARGET_OS_OSX
111113
NSString *dsn = [[[NSProcessInfo processInfo] environment] objectForKey:@"SENTRY_DSN"];
@@ -315,6 +317,9 @@ - (BOOL)validateOptions:(NSDictionary<NSString *, id> *)options
315317
[self setBool:options[@"enableWatchdogTerminationTracking"]
316318
block:^(BOOL value) { self->_enableWatchdogTerminationTracking = value; }];
317319

320+
[self setBool:options[@"swiftAsyncStacktraces"]
321+
block:^(BOOL value) { self->_swiftAsyncStacktraces = value; }];
322+
318323
if ([options[@"sessionTrackingIntervalMillis"] isKindOfClass:[NSNumber class]]) {
319324
self.sessionTrackingIntervalMillis =
320325
[options[@"sessionTrackingIntervalMillis"] unsignedIntValue];
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#import "SentrySwiftAsyncIntegration.h"
2+
#import "SentryCrashStackCursor_SelfThread.h"
3+
4+
@implementation SentrySwiftAsyncIntegration
5+
6+
- (BOOL)installWithOptions:(nonnull SentryOptions *)options
7+
{
8+
sentrycrashsc_setSwiftAsyncStitching(options.swiftAsyncStacktraces);
9+
return options.swiftAsyncStacktraces;
10+
}
11+
12+
- (void)uninstall
13+
{
14+
sentrycrashsc_setSwiftAsyncStitching(NO);
15+
}
16+
17+
@end
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#import "SentryBaseIntegration.h"
2+
#import "SentryIntegrationProtocol.h"
3+
#import <Foundation/Foundation.h>
4+
5+
NS_ASSUME_NONNULL_BEGIN
6+
7+
@interface SentrySwiftAsyncIntegration : SentryBaseIntegration <SentryIntegrationProtocol>
8+
9+
@end
10+
11+
NS_ASSUME_NONNULL_END

Sources/SentryCrash/Recording/Tools/SentryCrashStackCursor_SelfThread.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ extern "C" {
4242
*/
4343
void sentrycrashsc_initSelfThread(SentryCrashStackCursor *cursor, int skipEntries);
4444

45+
void sentrycrashsc_setSwiftAsyncStitching(bool enabled);
46+
4547
#ifdef __cplusplus
4648
}
4749
#endif

Sources/SentryCrash/Recording/Tools/SentryCrashStackCursor_SelfThread.c renamed to Sources/SentryCrash/Recording/Tools/SentryCrashStackCursor_SelfThread.m

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,35 @@ typedef struct {
3939
uintptr_t backtrace[0];
4040
} SelfThreadContext;
4141

42+
static BOOL stitchSwiftAsync = NO;
43+
44+
void
45+
sentrycrashsc_setSwiftAsyncStitching(bool enabled)
46+
{
47+
stitchSwiftAsync = enabled;
48+
}
49+
4250
void
4351
sentrycrashsc_initSelfThread(SentryCrashStackCursor *cursor, int skipEntries)
4452
{
4553
SelfThreadContext *context = (SelfThreadContext *)cursor->context;
54+
55+
// backtrace_async api is only available from xcode 13
56+
#if __clang_major__ >= 13
57+
int backtraceLength;
58+
if (@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *)) {
59+
if (stitchSwiftAsync) {
60+
backtraceLength
61+
= (int)backtrace_async((void **)context->backtrace, MAX_BACKTRACE_LENGTH, NULL);
62+
} else {
63+
backtraceLength = backtrace((void **)context->backtrace, MAX_BACKTRACE_LENGTH);
64+
}
65+
} else {
66+
backtraceLength = backtrace((void **)context->backtrace, MAX_BACKTRACE_LENGTH);
67+
}
68+
#else
4669
int backtraceLength = backtrace((void **)context->backtrace, MAX_BACKTRACE_LENGTH);
70+
#endif
71+
4772
sentrycrashsc_initWithBacktrace(cursor, context->backtrace, backtraceLength, skipEntries + 1);
4873
}

Tests/SentryTests/SentryCrash/SentryStacktraceBuilderTests.swift

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,63 @@ class SentryStacktraceBuilderTests: XCTestCase {
6767

6868
XCTAssertTrue(filteredFrames.count == 1, "The frames must be ordered from caller to callee, or oldest to youngest.")
6969
}
70-
70+
71+
func testConcurrentStacktraces() {
72+
guard #available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *) else { return }
73+
74+
SentrySDK.start { options in
75+
options.dsn = TestConstants.dsnAsString(username: "SentryStacktraceBuilderTests")
76+
options.swiftAsyncStacktraces = true
77+
}
78+
79+
let waitForAsyncToRun = expectation(description: "Wait async functions")
80+
Task {
81+
let filteredFrames = await self.firstFrame()
82+
waitForAsyncToRun.fulfill()
83+
XCTAssertGreaterThanOrEqual(filteredFrames, 3, "The Stacktrace must include the async callers.")
84+
}
85+
wait(for: [waitForAsyncToRun], timeout: 1)
86+
}
87+
88+
func testConcurrentStacktraces_noStitching() {
89+
guard #available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *) else { return }
90+
91+
SentrySDK.start { options in
92+
options.dsn = TestConstants.dsnAsString(username: "SentryStacktraceBuilderTests")
93+
options.swiftAsyncStacktraces = false
94+
}
95+
96+
let waitForAsyncToRun = expectation(description: "Wait async functions")
97+
Task {
98+
let filteredFrames = await self.firstFrame()
99+
waitForAsyncToRun.fulfill()
100+
XCTAssertGreaterThanOrEqual(filteredFrames, 1, "The Stacktrace must have only one function.")
101+
}
102+
wait(for: [waitForAsyncToRun], timeout: 1)
103+
}
104+
105+
@available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)
106+
func firstFrame() async -> Int {
107+
return await innerFrame1()
108+
}
109+
110+
@available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)
111+
func innerFrame1() async -> Int {
112+
await Task { @MainActor in }.value
113+
return await innerFrame2()
114+
}
115+
116+
@available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)
117+
func innerFrame2() async -> Int {
118+
let needed = ["firstFrame", "innerFrame1", "innerFrame2"]
119+
let actual = fixture.sut.buildStacktraceForCurrentThreadAsyncUnsafe()!
120+
let filteredFrames = actual.frames
121+
.compactMap({ $0.function })
122+
.filter { needed.contains(where: $0.contains) }
123+
return filteredFrames.count
124+
125+
}
126+
71127
func asyncFrame1(expect: XCTestExpectation) {
72128
fixture.queue.asyncAfter(deadline: DispatchTime.now()) {
73129
self.asyncFrame2(expect: expect)

0 commit comments

Comments
 (0)