Repository: firebase/firebase-ios-sdk (language: cpp)

Issue:
A crash of an any application with  Firebase/Performance.
<!-- DO NOT DELETE
validate_template=true
template_path=.github/ISSUE_TEMPLATE/bug_report.md
-->
### [REQUIRED] Step 1: Describe your environment

  * Xcode version: 12.3
  * Firebase SDK version: 8.2.0
  * Installation method: CocoaPods
  * Firebase Component: Performance

### [REQUIRED] Step 2: Describe the problem
An application crashes on launch.

#### Steps to reproduce:

1. Clone a code from https://github.com/firebase/quickstart-ios/tree/master/performance and install pods
2. Turn on diagnostic flags for launch scheme PerformanceExample named "Malloc Scribble", "Zombie Objects": 

<img width="644" alt="Screenshot 2021-06-30 at 10 03 51" src="https://user-images.githubusercontent.com/128474/123895720-7bc2a480-d98a-11eb-943d-7fb07ec03f6d.png">

3. Launch an application named PerformanceExample.

Application will crash with a Zombie access message: 
```
#0	0x00007fff20176876 in object_setClass ()
#1	0x00007fff2042fbd2 in -[NSObject(NSObject) __dealloc_zombie] ()
#2	0x00007fff207f34d6 in -[NSOperation dealloc] ()
#3	0x00007fff2018f834 in objc_object::sidetable_release(bool, bool) ()
#4	0x000000010cc362a1 in __destroy_helper_block_e8_32s40s ()
#5	0x00007fff2007f42f in _Block_release ()
#6	0x000000010ca468d3 in __destroy_helper_block_e8_32s ()
#7	0x00007fff2007f42f in _Block_release ()
#8	0x000000010ca467f1 in __destroy_helper_block_e8_32s40s ()
#9	0x00007fff2007f42f in _Block_release ()
#10	0x000000010ca488ff in __destroy_helper_block_e8_32s40s48s ()
#11	0x00007fff2007f42f in _Block_release ()
#12	0x000000010ce2d9c8 in _dispatch_client_callout ()
#13	0x000000010ce34440 in _dispatch_lane_serial_drain ()
#14	0x000000010ce34f67 in _dispatch_lane_invoke ()
#15	0x000000010ce40de2 in _dispatch_workloop_worker_thread ()
#16	0x00007fff60c8f45d in _pthread_wqthread ()
#17	0x00007fff60c8e42f in start_wqthread ()
Enqueued from com.google.GDTCCTUploader (Thread 5) Queue : com.google.GDTCCTUploader (serial)
#0	0x000000010ce2ee19 in dispatch_group_async ()
#1	0x000000010ca57175 in __44-[FBLPromise observeOnQueue:fulfill:reject:]_block_invoke at /Users/andrewromanov/Documents/work/my/quickstart-ios/performance/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise.m:221
#2	0x000000010ca558c1 in -[FBLPromise fulfill:] at /Users/andrewromanov/Documents/work/my/quickstart-ios/performance/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise.m:90
#3	0x000000010ca5795d in __56-[FBLPromise chainOnQueue:chainedFulfill:chainedReject:]_block_invoke at /Users/andrewromanov/Documents/work/my/quickstart-ios/performance/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise.m:268
#4	0x000000010ca57b3d in __56-[FBLPromise chainOnQueue:chainedFulfill:chainedReject:]_block_invoke.67 at /Users/andrewromanov/Documents/work/my/quickstart-ios/performance/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise.m:274
#5	0x000000010ca5724d in __44-[FBLPromise observeOnQueue:fulfill:reject:]_block_invoke_2 at /Users/andrewromanov/Documents/work/my/quickstart-ios/performance/Pods/PromisesObjC/Sources/FBLPromises/FBLPromise.m:226
#6	0x000000010ce2c7ec in _dispatch_call_block_and_release ()
#7	0x000000010ce2d9c8 in _dispatch_client_callout ()
#8	0x000000010ce34440 in _dispatch_lane_serial_drain ()
#9	0x000000010ce34f67 in _dispatch_lane_invoke ()
#10	0x000000010ce40de2 in _dispatch_workloop_worker_thread ()
#11	0x00007fff60c8f45d in _pthread_wqthread ()
#12	0x00007fff60c8e42f in start_wqthread ()

```

You can reproduce this bug in any application with Firebase/Performance. 



Discussion / hints:
@K-Be Thanks for the report. I can reproduce.
I think it's related but maybe I'm wrong, I have a crash with the same version of firebase on performance w network trace

```
Plantage : com.google.perf.sessionIds.networkTrace
EXC_BAD_ACCESS KERN_INVALID_ADDRESS 0x000000053987d140

Crashed: com.google.perf.sessionIds.networkTrace
0  libobjc.A.dylib                0x1afebdc90 objc_retain + 16
1  FirebasePerformance            0x103e044d8 __48-[FPRNetworkTrace updateTraceWithCurrentSession]_block_invoke + 144 (FPRNetworkTrace.m:144)
2  libdispatch.dylib              0x19b755298 _dispatch_call_block_and_release + 24
3  libdispatch.dylib              0x19b756280 _dispatch_client_callout + 16
4  libdispatch.dylib              0x19b7324f0 _dispatch_lane_serial_drain$VARIANT$armv81 + 568
5  libdispatch.dylib              0x19b732fdc _dispatch_lane_invoke$VARIANT$armv81 + 404
6  libdispatch.dylib              0x19b73c800 _dispatch_workloop_worker_thread + 692
7  libsystem_pthread.dylib        0x1e42ad5a4 _pthread_wqthread + 272
8  libsystem_pthread.dylib        0x1e42b0874 start_wqthread + 8
```

![image](https://user-images.githubusercontent.com/10015557/123984238-c2cd8b80-d9c4-11eb-83fb-e01c1b1d8ed2.png)

Same problem as yours appears in Firebase 8.3.0. (FirebasePerformance 8.3.0)
Hi! The Cian team faced with the same crash. We have updated from 7.10.0 to 8.3.0 and caught issue. For current time we downgraded to the stable version 7.10.0, unfortunately

<img width="1271" alt="Снимок экрана 2021-07-30 в 17 16 10" src="https://user-images.githubusercontent.com/47531029/127666496-a66552ae-11a7-4ab1-a675-5dc783b8830c.png">

We're not sure that the `[FPRNetworkTrace updateTraceWithCurrentSession]` crash is the same as the zombie crash being tracked by this issue. We consider the `[FPRNetworkTrace updateTraceWithCurrentSession]` crash more urgent and are investigating that one at #8495.

Fix patch:
diff --git a/FirebasePerformance/CHANGELOG.md b/FirebasePerformance/CHANGELOG.md
index aee94e3cff2..eb8d8606d8e 100644
--- a/FirebasePerformance/CHANGELOG.md
+++ b/FirebasePerformance/CHANGELOG.md
@@ -1,5 +1,6 @@
 # Version 8.6.0
 * Add Firebase Performance support for Swift Package Manager. (#6528)
+* Fix a crash due to a race condition. (#8485)
 
 # Version 8.2.0
 * Update log messages with proper log levels.
diff --git a/FirebasePerformance/Sources/AppActivity/FPRSessionManager+Private.h b/FirebasePerformance/Sources/AppActivity/FPRSessionManager+Private.h
index 700c6fe5ba9..77b84bbe75f 100644
--- a/FirebasePerformance/Sources/AppActivity/FPRSessionManager+Private.h
+++ b/FirebasePerformance/Sources/AppActivity/FPRSessionManager+Private.h
@@ -22,7 +22,7 @@ NS_ASSUME_NONNULL_BEGIN
 @interface FPRSessionManager ()
 
 /** The current active session managed by the session manager. Modifiable for unit tests */
-@property(nonatomic, nullable, readwrite) FPRSessionDetails *sessionDetails;
+@property(atomic, nullable, readwrite) FPRSessionDetails *sessionDetails;
 
 /**
  * Checks if the currently active session is beyond maximum allowed time. If so renew the session,
diff --git a/FirebasePerformance/Sources/AppActivity/FPRSessionManager.h b/FirebasePerformance/Sources/AppActivity/FPRSessionManager.h
index af0ac549e42..1a5f5fe4862 100644
--- a/FirebasePerformance/Sources/AppActivity/FPRSessionManager.h
+++ b/FirebasePerformance/Sources/AppActivity/FPRSessionManager.h
@@ -19,13 +19,16 @@
 /* Notification name when the session Id gets updated. */
 FOUNDATION_EXTERN NSString *_Nonnull const kFPRSessionIdUpdatedNotification;
 
+/* Notification name when the session Id gets updated. */
+FOUNDATION_EXTERN NSString *_Nonnull const kFPRSessionIdNotificationKey;
+
 /** This class manages the current active sessionId of the application and provides mechanism for
  *  propagating the session Id.
  */
 @interface FPRSessionManager : NSObject
 
 /** The current active session managed by the session manager. */
-@property(nonatomic, readonly, nonnull) FPRSessionDetails *sessionDetails;
+@property(atomic, readonly, nonnull) FPRSessionDetails *sessionDetails;
 
 /**
  * The notification center managed by the session manager. All the notifications by the session
diff --git a/FirebasePerformance/Sources/AppActivity/FPRSessionManager.m b/FirebasePerformance/Sources/AppActivity/FPRSessionManager.m
index 79b19d0c88f..711bf2f8810 100644
--- a/FirebasePerformance/Sources/AppActivity/FPRSessionManager.m
+++ b/FirebasePerformance/Sources/AppActivity/FPRSessionManager.m
@@ -22,6 +22,7 @@
 #import <UIKit/UIKit.h>
 
 NSString *const kFPRSessionIdUpdatedNotification = @"kFPRSessionIdUpdatedNotification";
+NSString *const kFPRSessionIdNotificationKey = @"kFPRSessionIdNotificationKey";
 
 @interface FPRSessionManager ()
 
@@ -104,8 +105,12 @@ - (void)updateSessionId:(NSNotification *)notification {
   FPRSessionDetails *sessionInfo = [[FPRSessionDetails alloc] initWithSessionId:sessionIdString
                                                                         options:sessionOptions];
   self.sessionDetails = sessionInfo;
+  NSMutableDictionary<NSString *, FPRSessionDetails *> *userInfo =
+      [[NSMutableDictionary alloc] init];
+  [userInfo setObject:sessionInfo forKey:kFPRSessionIdNotificationKey];
   [self.sessionNotificationCenter postNotificationName:kFPRSessionIdUpdatedNotification
-                                                object:self];
+                                                object:self
+                                              userInfo:[userInfo copy]];
 }
 
 /**
diff --git a/FirebasePerformance/Sources/Instrumentation/FPRNetworkTrace.m b/FirebasePerformance/Sources/Instrumentation/FPRNetworkTrace.m
index 78bfc4498c9..cd9aa498022 100644
--- a/FirebasePerformance/Sources/Instrumentation/FPRNetworkTrace.m
+++ b/FirebasePerformance/Sources/Instrumentation/FPRNetworkTrace.m
@@ -50,8 +50,11 @@ @interface FPRNetworkTrace ()
 /** @brief Serial queue to manage the updation of session Ids. */
 @property(nonatomic, readwrite) dispatch_queue_t sessionIdSerialQueue;
 
-/** Updates the current trace with the current session details. */
-- (void)updateTraceWithCurrentSession;
+/**
+ * Updates the current trace with the current session details.
+ * @param sessionDetails Updated session details of the currently active session.
+ */
+- (void)updateTraceWithCurrentSession:(FPRSessionDetails *)sessionDetails;
 
 @end
 
@@ -137,14 +140,20 @@ - (NSString *)description {
   return [NSString stringWithFormat:@"Request: %@", _URLRequest];
 }
 
-- (void)updateTraceWithCurrentSession {
+- (void)sessionChanged:(NSNotification *)notification {
   if (self.traceStarted && !self.traceCompleted) {
-    dispatch_async(self.sessionIdSerialQueue, ^{
-      FPRSessionManager *sessionManager = [FPRSessionManager sharedInstance];
-      FPRSessionDetails *sessionDetails = [sessionManager.sessionDetails copy];
-      if (sessionDetails) {
-        [self.activeSessions addObject:sessionDetails];
-      }
+    NSDictionary<NSString *, FPRSessionDetails *> *userInfo = notification.userInfo;
+    FPRSessionDetails *sessionDetails = [userInfo valueForKey:kFPRSessionIdNotificationKey];
+    if (sessionDetails) {
+      [self updateTraceWithCurrentSession:sessionDetails];
+    }
+  }
+}
+
+- (void)updateTraceWithCurrentSession:(FPRSessionDetails *)sessionDetails {
+  if (sessionDetails != nil) {
+    dispatch_sync(self.sessionIdSerialQueue, ^{
+      [self.activeSessions addObject:sessionDetails];
     });
   }
 }
@@ -191,9 +200,9 @@ - (void)start {
     [self checkpointState:FPRNetworkTraceCheckpointStateInitiated];
 
     FPRSessionManager *sessionManager = [FPRSessionManager sharedInstance];
-    [self updateTraceWithCurrentSession];
+    [self updateTraceWithCurrentSession:[sessionManager.sessionDetails copy]];
     [sessionManager.sessionNotificationCenter addObserver:self
-                                                 selector:@selector(updateTraceWithCurrentSession)
+                                                 selector:@selector(sessionChanged:)
                                                      name:kFPRSessionIdUpdatedNotification
                                                    object:sessionManager];
   }
diff --git a/FirebasePerformance/Sources/Timer/FIRTrace.m b/FirebasePerformance/Sources/Timer/FIRTrace.m
index d1d72076bd8..e69005872e0 100644
--- a/FirebasePerformance/Sources/Timer/FIRTrace.m
+++ b/FirebasePerformance/Sources/Timer/FIRTrace.m
@@ -49,8 +49,11 @@ @interface FIRTrace ()
 /** Stops an active stage that is currently active. */
 - (void)stopActiveStage;
 
-/** Updates the current trace with the session id. */
-- (void)updateTraceWithSessionId;
+/**
+ * Updates the current trace with the session id.
+ * @param sessionDetails Updated session details of the currently active session.
+ */
+- (void)updateTraceWithSessionId:(FPRSessionDetails *)sessionDetails;
 
 @end
 
@@ -139,9 +142,9 @@ - (void)start {
     self.backgroundActivityTracker = [[FPRTraceBackgroundActivityTracker alloc] init];
     FPRSessionManager *sessionManager = [FPRSessionManager sharedInstance];
     if (!self.isStage) {
-      [self updateTraceWithSessionId];
+      [self updateTraceWithSessionId:[sessionManager.sessionDetails copy]];
       [sessionManager.sessionNotificationCenter addObserver:self
-                                                   selector:@selector(updateTraceWithSessionId)
+                                                   selector:@selector(sessionChanged:)
                                                        name:kFPRSessionIdUpdatedNotification
                                                      object:sessionManager];
     }
@@ -400,18 +403,22 @@ - (void)removeAttribute:(NSString *)attribute {
 
 #pragma mark - Utility methods
 
-- (void)updateTraceWithSessionId {
+- (void)sessionChanged:(NSNotification *)notification {
   if ([self isTraceActive]) {
-    dispatch_async(self.sessionIdSerialQueue, ^{
-      FPRSessionManager *sessionManager = [FPRSessionManager sharedInstance];
-      FPRSessionDetails *sessionDetails = [sessionManager.sessionDetails copy];
-      if (sessionDetails) {
-        [self.activeSessions addObject:sessionDetails];
-      }
-    });
+    NSDictionary<NSString *, FPRSessionDetails *> *userInfo = notification.userInfo;
+    FPRSessionDetails *sessionDetails = [userInfo valueForKey:kFPRSessionIdNotificationKey];
+    if (sessionDetails) {
+      [self updateTraceWithSessionId:sessionDetails];
+    }
   }
 }
 
+- (void)updateTraceWithSessionId:(FPRSessionDetails *)sessionDetails {
+  dispatch_sync(self.sessionIdSerialQueue, ^{
+    [self.activeSessions addObject:sessionDetails];
+  });
+}
+
 - (BOOL)isTraceStarted {
   return self.startTime != nil;
 }
diff --git a/FirebasePerformance/Tests/Unit/FPRSessionManagerTest.m b/FirebasePerformance/Tests/Unit/FPRSessionManagerTest.m
index 3eb3a897059..d506d918e92 100644
--- a/FirebasePerformance/Tests/Unit/FPRSessionManagerTest.m
+++ b/FirebasePerformance/Tests/Unit/FPRSessionManagerTest.m
@@ -91,4 +91,35 @@ - (void)testSessionIdUpdationThrowsNotification {
   XCTAssertNotEqual(sessionId, instance.sessionDetails.sessionId);
 }
 
+/** Validate that sessionId changes sends notifications with the session details. */
+- (void)testSessionIdUpdationSendsNotificationWithSessionDetails {
+  FPRSessionManager *instance = [FPRSessionManager sharedInstance];
+  [instance startTrackingAppStateChanges];
+  NSString *sessionId = instance.sessionDetails.sessionId;
+
+  __block BOOL containsSessionDetails = NO;
+  __block FPRSessionDetails *updatedSessionDetails = nil;
+  [instance.sessionNotificationCenter
+      addObserverForName:kFPRSessionIdUpdatedNotification
+                  object:instance
+                   queue:[NSOperationQueue mainQueue]
+              usingBlock:^(NSNotification *note) {
+                NSDictionary<NSString *, FPRSessionDetails *> *userInfo = note.userInfo;
+                FPRSessionDetails *sessionDetails =
+                    [userInfo valueForKey:kFPRSessionIdNotificationKey];
+                if (sessionDetails != nil) {
+                  containsSessionDetails = YES;
+                  updatedSessionDetails = sessionDetails;
+                }
+              }];
+
+  NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
+  [notificationCenter postNotificationName:UIApplicationWillEnterForegroundNotification
+                                    object:[UIApplication sharedApplication]];
+
+  XCTAssertTrue(containsSessionDetails);
+  XCTAssertNotEqual(sessionId, instance.sessionDetails.sessionId);
+  XCTAssertEqual(updatedSessionDetails.sessionId, instance.sessionDetails.sessionId);
+}
+
 @end


Test patch: