-
-
Notifications
You must be signed in to change notification settings - Fork 401
fix: resolve EXC_BAD_ACCESS in SentryTracer/SentryNetworkTracker span lifecycle #8058
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
base: main
Are you sure you want to change the base?
Changes from 3 commits
cd09335
2712c25
5fad0bc
0e2ddad
d4d62c6
f697441
6435c68
9e6e25a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -123,7 +123,10 @@ - (void)urlSessionTaskResume:(NSURLSessionTask *)sessionTask | |
| return; | ||
| } | ||
|
|
||
| NSURL *url = [[sessionTask currentRequest] URL]; | ||
| // Snapshot currentRequest once — the property is volatile and can return a freed | ||
| // object if the task completes on another thread between repeated accesses. | ||
| NSURLRequest *currentRequest = sessionTask.currentRequest; | ||
| NSURL *url = currentRequest.URL; | ||
|
Comment on lines
+126
to
+129
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We are snapshotting a copy, so the possibility of this is lower.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, exactly. But it could still happen. |
||
|
|
||
| if (url == nil) { | ||
| return; | ||
|
|
@@ -167,14 +170,13 @@ - (void)urlSessionTaskResume:(NSURLSessionTask *)sessionTask | |
| id<SentrySpan> _Nullable currentSpan = [SentrySDKInternal.currentHub.scope span]; | ||
| if (currentSpan != nil) { | ||
| span = currentSpan; | ||
| netSpan = [span startChildWithOperation:SentrySpanOperationNetworkRequestOperation | ||
| description:[NSString stringWithFormat:@"%@ %@", | ||
| sessionTask.currentRequest.HTTPMethod, | ||
| safeUrl.sanitizedUrl]]; | ||
| netSpan = | ||
| [span startChildWithOperation:SentrySpanOperationNetworkRequestOperation | ||
| description:[NSString stringWithFormat:@"%@ %@", | ||
| currentRequest.HTTPMethod, safeUrl.sanitizedUrl]]; | ||
| netSpan.origin = SentryTraceOriginAutoHttpNSURLSession; | ||
|
|
||
| [netSpan setDataValue:sessionTask.currentRequest.HTTPMethod | ||
| forKey:@"http.request.method"]; | ||
| [netSpan setDataValue:currentRequest.HTTPMethod forKey:@"http.request.method"]; | ||
| [netSpan setDataValue:safeUrl.sanitizedUrl forKey:@"url"]; | ||
| [netSpan setDataValue:@"fetch" forKey:@"type"]; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,40 +15,40 @@ + (void)addBaggageHeader:(SentryBaggage *)baggage | |
| tracePropagationTargets:(NSArray *_Nullable)tracePropagationTargets | ||
| toRequest:(NSURLSessionTask *)sessionTask | ||
| { | ||
| if (![SentryTracePropagation sessionTaskRequiresPropagation:sessionTask | ||
| tracePropagationTargets:tracePropagationTargets]) { | ||
| SENTRY_LOG_DEBUG(@"Not adding trace_id and baggage headers for %@", | ||
| sessionTask.currentRequest.URL.absoluteString); | ||
| // Snapshot currentRequest once — the property is volatile and can become a zombie | ||
| // between repeated accesses if the task completes on another thread. | ||
| NSURLRequest *request = sessionTask.currentRequest; | ||
|
Comment on lines
+18
to
+20
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good idea |
||
| if (request == nil) { | ||
| return; | ||
| } | ||
|
|
||
| if (![SentryTracePropagation isTargetMatch:SENTRY_UNWRAP_NULLABLE(NSURL, request.URL) | ||
| withTargets:tracePropagationTargets ?: @[]]) { | ||
| SENTRY_LOG_DEBUG( | ||
| @"Not adding trace_id and baggage headers for %@", request.URL.absoluteString); | ||
| return; | ||
| } | ||
| NSString *baggageHeader = @""; | ||
|
|
||
| if (baggage != nil) { | ||
| NSString *_Nullable rawHeader = SENTRY_UNWRAP_NULLABLE( | ||
| NSString, sessionTask.currentRequest.allHTTPHeaderFields[SENTRY_BAGGAGE_HEADER]); | ||
| NSString *_Nullable rawHeader | ||
| = SENTRY_UNWRAP_NULLABLE(NSString, request.allHTTPHeaderFields[SENTRY_BAGGAGE_HEADER]); | ||
| NSDictionary *originalBaggage = [SentryBaggageSerialization decode:rawHeader ?: @""]; | ||
| if (originalBaggage[@"sentry-trace_id"] == nil) { | ||
| baggageHeader = [baggage toHTTPHeaderWithOriginalBaggage:originalBaggage]; | ||
| } | ||
| } | ||
|
|
||
| // First we check if the current request is mutable, so we could easily add a new | ||
| // header. Otherwise we try to change the current request for a new one with the extra | ||
| // header. | ||
| if ([sessionTask.currentRequest isKindOfClass:[NSMutableURLRequest class]]) { | ||
| NSMutableURLRequest *currentRequest = (NSMutableURLRequest *)sessionTask.currentRequest; | ||
| [SentryTracePropagation addHeaderFieldsToRequest:currentRequest | ||
| if ([request isKindOfClass:[NSMutableURLRequest class]]) { | ||
| NSMutableURLRequest *mutableRequest = (NSMutableURLRequest *)request; | ||
| [SentryTracePropagation addHeaderFieldsToRequest:mutableRequest | ||
| traceHeader:traceHeader | ||
| baggageHeader:baggageHeader | ||
| propagateTraceparent:propagateTraceparent]; | ||
| } else { | ||
| // Even though NSURLSessionTask doesn't have 'setCurrentRequest', some subclasses | ||
| // do. For those subclasses we replace the currentRequest with a mutable one with | ||
| // the additional trace header. Since NSURLSessionTask is a public class and can be | ||
| // overridden, we believe this is not considered a private api. | ||
| SEL setCurrentRequestSelector = NSSelectorFromString(@"setCurrentRequest:"); | ||
| if ([sessionTask respondsToSelector:setCurrentRequestSelector]) { | ||
| NSMutableURLRequest *newRequest = [sessionTask.currentRequest mutableCopy]; | ||
| NSMutableURLRequest *newRequest = [request mutableCopy]; | ||
| [SentryTracePropagation addHeaderFieldsToRequest:newRequest | ||
| traceHeader:traceHeader | ||
| baggageHeader:baggageHeader | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -591,6 +591,61 @@ class SentryTimeToDisplayTrackerTest: XCTestCase { | |
| XCTAssertEqual(tracer.measurements[name]?.value, NSNumber(value: duration), file: file, line: line) | ||
| XCTAssertEqual(tracer.measurements[name]?.unit?.unit, "millisecond", file: file, line: line) | ||
| } | ||
|
|
||
| // MARK: - Weak span safety in finishCallback (issue #8012) | ||
|
|
||
| func testFinishCallback_ConcurrentTracerFinish_DoesNotCrash() throws { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It failed for me when I ran, but seems to be non deterministic (or a later change broke it). And it was more of an integration test. I will add a different test here |
||
| for _ in 0..<10 { | ||
| let sut = fixture.getSut(name: "UIViewController", waitForFullDisplay: true) | ||
| let tracer = try fixture.getTracer() | ||
|
|
||
| fixture.dateProvider.setDate(date: Date(timeIntervalSince1970: 7)) | ||
| XCTAssertTrue(sut.start(for: tracer)) | ||
|
|
||
| fixture.dateProvider.setDate(date: Date(timeIntervalSince1970: 8)) | ||
| sut.reportInitialDisplay() | ||
| fixture.displayLinkWrapper.normalFrame() | ||
|
|
||
| fixture.dateProvider.setDate(date: Date(timeIntervalSince1970: 9)) | ||
| sut.reportFullyDisplayed() | ||
| fixture.displayLinkWrapper.normalFrame() | ||
|
|
||
| let queue = DispatchQueue(label: "ttd-finish-race", attributes: [.concurrent, .initiallyInactive]) | ||
| let expectation = expectation(description: "concurrent finish") | ||
| expectation.expectedFulfillmentCount = 2 | ||
| expectation.assertForOverFulfill = true | ||
|
|
||
| queue.async { | ||
| let child = tracer.startChild(operation: "test.op") | ||
| child.finish() | ||
| expectation.fulfill() | ||
| } | ||
| queue.async { | ||
| tracer.finish() | ||
| expectation.fulfill() | ||
| } | ||
|
|
||
| queue.activate() | ||
| wait(for: [expectation], timeout: 5.0) | ||
|
|
||
| XCTAssertTrue(tracer.isFinished) | ||
| } | ||
| } | ||
|
|
||
| func testFinishCallback_SpansAlreadyNil_DoesNotCrash() throws { | ||
| let sut = fixture.getSut(name: "UIViewController", waitForFullDisplay: false) | ||
| let tracer = try fixture.getTracer() | ||
|
|
||
| fixture.dateProvider.setDate(date: Date(timeIntervalSince1970: 7)) | ||
| XCTAssertTrue(sut.start(for: tracer)) | ||
|
|
||
| fixture.dateProvider.setDate(date: Date(timeIntervalSince1970: 8)) | ||
| sut.reportInitialDisplay() | ||
| fixture.displayLinkWrapper.normalFrame() | ||
|
|
||
| tracer.finish() | ||
| XCTAssertTrue(tracer.isFinished) | ||
| } | ||
| } | ||
|
|
||
| #endif // os(iOS) || os(tvOS) | ||
Uh oh!
There was an error while loading. Please reload this page.