Skip to content

Commit 5ea8b46

Browse files
committed
review-comment: Synchronize access to shared network details data
Synchronize objc_get/setAssociatedObject and SentryNetwokDetailsData locking on the NSURLSessionTask instance. `setState` - iOS internal API, undocumented threading model - calls captureRequestDetails `completionHandler` - callback queue (thread) configured by app. - calls captureResponseDetails captureRequestDetails To avoid introducing blocking on apple's thread, we use the lock to objc_getAssociated(task,..) but drop the lock before processing the request (SentryNetworkRequestDetails:setRequest). captureResponseDetails However we need the lock while processing response data (SentryNetworkRequestDetails:setResponse) b/c (later) it will race with calls to `SentryNetworkRequestDetails:serialize`. The completionHandler is on the app's network response path, so adding some thread contention here (worst-case) is less noticable (an i/o request just finished).
1 parent f1f036a commit 5ea8b46

2 files changed

Lines changed: 42 additions & 34 deletions

File tree

Sources/Sentry/SentryNetworkTracker.m

Lines changed: 37 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -606,32 +606,36 @@ - (void)captureResponseDetails:(NSData *)data
606606
return;
607607
}
608608

609-
SentryReplayNetworkDetails *details = objc_getAssociatedObject(task, &SentryNetworkDetailsKey);
610-
if (!details) {
611-
SENTRY_LOG_WARN(@"[NetworkCapture] No SentryReplayNetworkDetails found for %@ - "
612-
@"skipping response capture",
613-
urlString);
614-
return;
615-
}
609+
@synchronized(task) {
610+
SentryReplayNetworkDetails *details
611+
= objc_getAssociatedObject(task, &SentryNetworkDetailsKey);
612+
if (!details) {
613+
SENTRY_LOG_WARN(@"[NetworkCapture] No SentryReplayNetworkDetails found for %@ - "
614+
@"skipping response capture",
615+
urlString);
616+
return;
617+
}
616618

617-
NSInteger statusCode = 0;
618-
NSDictionary *allHeaders = nil;
619-
NSString *contentType = nil;
620-
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
621-
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
622-
statusCode = httpResponse.statusCode;
623-
allHeaders = httpResponse.allHeaderFields;
624-
contentType = httpResponse.allHeaderFields[@"Content-Type"];
625-
}
619+
NSInteger statusCode = 0;
620+
NSDictionary *allHeaders = nil;
621+
NSString *contentType = nil;
622+
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
623+
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
624+
statusCode = httpResponse.statusCode;
625+
allHeaders = httpResponse.allHeaderFields;
626+
contentType = httpResponse.allHeaderFields[@"Content-Type"];
627+
}
626628

627-
NSData *bodyData = (options.sessionReplay.networkCaptureBodies && data.length > 0) ? data : nil;
629+
NSData *bodyData
630+
= (options.sessionReplay.networkCaptureBodies && data.length > 0) ? data : nil;
628631

629-
[details setResponseWithStatusCode:statusCode
630-
size:@(data ? data.length : 0)
631-
bodyData:bodyData
632-
contentType:contentType
633-
allHeaders:allHeaders
634-
configuredHeaders:options.sessionReplay.networkResponseHeaders];
632+
[details setResponseWithStatusCode:statusCode
633+
size:@(data ? data.length : 0)
634+
bodyData:bodyData
635+
contentType:contentType
636+
allHeaders:allHeaders
637+
configuredHeaders:options.sessionReplay.networkResponseHeaders];
638+
}
635639
}
636640

637641
- (void)captureRequestDetails:(NSURLSessionTask *)sessionTask
@@ -642,16 +646,18 @@ - (void)captureRequestDetails:(NSURLSessionTask *)sessionTask
642646
return;
643647
}
644648

645-
SentryReplayNetworkDetails *existingDetails
646-
= objc_getAssociatedObject(sessionTask, &SentryNetworkDetailsKey);
647-
if (existingDetails) {
648-
return;
649-
}
650-
651649
NSURLRequest *request = sessionTask.currentRequest;
650+
SentryReplayNetworkDetails *details;
652651

653-
SentryReplayNetworkDetails *details =
654-
[[SentryReplayNetworkDetails alloc] initWithMethod:request.HTTPMethod ?: @"GET"];
652+
@synchronized(sessionTask) {
653+
if (objc_getAssociatedObject(sessionTask, &SentryNetworkDetailsKey)) {
654+
return;
655+
}
656+
details =
657+
[[SentryReplayNetworkDetails alloc] initWithMethod:request.HTTPMethod ?: @"GET"];
658+
objc_setAssociatedObject(
659+
sessionTask, &SentryNetworkDetailsKey, details, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
660+
}
655661

656662
// Prefer originalRequest.HTTPBody: currentRequest may reflect redirects, and its HTTPBody may be nil on in-flight tasks.
657663
NSData *bodyData
@@ -664,9 +670,6 @@ - (void)captureRequestDetails:(NSURLSessionTask *)sessionTask
664670
contentType:request.allHTTPHeaderFields[@"Content-Type"]
665671
allHeaders:request.allHTTPHeaderFields
666672
configuredHeaders:networkRequestHeaders];
667-
668-
objc_setAssociatedObject(
669-
sessionTask, &SentryNetworkDetailsKey, details, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
670673
}
671674
#endif // SENTRY_TARGET_REPLAY_SUPPORTED
672675

Sources/Swift/Integrations/SessionReplay/SentryReplayNetworkDetails.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ enum NetworkBodyWarning: String {
1616
/// ObjC callers (SentryNetworkTracker) create this object and populate it
1717
/// via `setRequest`/`setResponse`. Swift callers (SentrySRDefaultBreadcrumbConverter)
1818
/// consume it via `serialize()`.
19+
///
20+
/// - Important: `setRequest` and `setResponse` can be called concurrently from
21+
/// `SentryNetworkTracker` because they write to independent properties.
22+
/// Adding shared mutable state between will require adding synchronization.
23+
1924
@objc
2025
@_spi(Private) public class SentryReplayNetworkDetails: NSObject {
2126

0 commit comments

Comments
 (0)