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

Issue:
Crash in FPRGaugeManager addGaugeData
<!-- DO NOT DELETE
validate_template=true
template_path=.github/ISSUE_TEMPLATE/bug_report.md
-->

### [REQUIRED] Step 1: Describe your environment

  * Xcode version: 12.2
  * Firebase SDK version: 7.3.0
  * Installation method: `CocoaPods`
  * Firebase Component: Performance (Auth, Core, Database, Firestore, Messaging, Storage, etc)

### [REQUIRED] Step 2: Describe the problem

#### Steps to reproduce:
Found crash with no known way to reproduce, about 40 crash for 30 days, so far found on iOS 13 and 14
All/most crash seems to happen some time after app returning to foreground

```
Crashed: com.google.perf.gaugeManager.gaugeData
0  libobjc.A.dylib                0x1974e5c90 objc_retain + 16
1  <appname>                      0x100e0e534 __32-[FPRGaugeManager addGaugeData:]_block_invoke + 4369573172
2  libdispatch.dylib              0x182d7a298 _dispatch_call_block_and_release + 24
3  libdispatch.dylib              0x182d7b280 _dispatch_client_callout + 16
4  libdispatch.dylib              0x182d23dcc _dispatch_lane_serial_drain$VARIANT$mp + 612
5  libdispatch.dylib              0x182d248a8 _dispatch_lane_invoke$VARIANT$mp + 424
6  libdispatch.dylib              0x182d2e338 _dispatch_workloop_worker_thread + 712
7  libsystem_pthread.dylib        0x1cb9695a4 _pthread_wqthread + 272
8  libsystem_pthread.dylib        0x1cb96c874 start_wqthread + 8
```



Discussion / hints:
@ivanrein Thank you for reporting it. 

It may be a concurrency issue. Could you please post stack traces for other threads to help us investigate? 
Hi @maksymmalyhin 
here you go 

[traces.txt](https://github.com/firebase/firebase-ios-sdk/files/5993487/traces.txt)

The `-[RBSConnection invalidateAssertion:error:] ` looks suspicious. Any more detail about the error generated there?
Hi @paulb777 
No, unfortunately that's everything I have.
I don't know what this RBSConnection is (not from our app), but let me know if I can put some logs in our app to provide more information about this.
@ivanrein Thanks for the trace logs. The crash log does not specify the thread that got crashed. In the trace logs, we could find the FirebasePerformance stack trace either. It would be helpful to get a couple more information about the crash:

1. The stacktrace that contains the Firebase Performance classes
2. Memory/CPU state of the app at the time of the crash (hopefully you have this information from the crashlytics logs)
hi @visumickey 
I put the stacktrace with Firebase performance class in my first post, so I didn't put it again in the txt file. 
Here it is
```
Crashed: com.google.perf.gaugeManager.gaugeData
0  libobjc.A.dylib                0x1974e5c90 objc_retain + 16
1  <appname>                      0x100e0e534 __32-[FPRGaugeManager addGaugeData:]_block_invoke + 4369573172
2  libdispatch.dylib              0x182d7a298 _dispatch_call_block_and_release + 24
3  libdispatch.dylib              0x182d7b280 _dispatch_client_callout + 16
4  libdispatch.dylib              0x182d23dcc _dispatch_lane_serial_drain$VARIANT$mp + 612
5  libdispatch.dylib              0x182d248a8 _dispatch_lane_invoke$VARIANT$mp + 424
6  libdispatch.dylib              0x182d2e338 _dispatch_workloop_worker_thread + 712
7  libsystem_pthread.dylib        0x1cb9695a4 _pthread_wqthread + 272
8  libsystem_pthread.dylib        0x1cb96c874 start_wqthread + 8
```

Here's two device states  at the time of crash.
<img width="540" alt="image" src="https://user-images.githubusercontent.com/8987461/108450881-d6dbe080-72a0-11eb-9ede-1b868c6e8193.png">
<img width="516" alt="image" src="https://user-images.githubusercontent.com/8987461/108450917-e78c5680-72a0-11eb-9793-296bb97ac3a7.png">


We've gotten these as well
The actual reason of the crash is not clear but a possible reason may the following. It looks like in some conditions [FPRGaugeManager.gaugeData](https://github.com/firebase/firebase-ios-sdk/blob/e30f30beda74bdc72a7f2ad114fadc033356334c/FirebasePerformance/Sources/Gauges/FPRGaugeManager.m#L37) property may be accessed concurrently from different threads here:
https://github.com/firebase/firebase-ios-sdk/blob/e30f30beda74bdc72a7f2ad114fadc033356334c/FirebasePerformance/Sources/Gauges/FPRGaugeManager.m#L176
and here:
https://github.com/firebase/firebase-ios-sdk/blob/e30f30beda74bdc72a7f2ad114fadc033356334c/FirebasePerformance/Sources/Gauges/FPRGaugeManager.m#L196 

As this is the only possible reason related to the posted stack trace found we most likely will post a fix and see if the crash still happens.

cc @visumickey  

Fix patch:
diff --git a/FirebasePerformance/CHANGELOG.md b/FirebasePerformance/CHANGELOG.md
index 3126bb376c8..7dd79ad1144 100644
--- a/FirebasePerformance/CHANGELOG.md
+++ b/FirebasePerformance/CHANGELOG.md
@@ -2,6 +2,7 @@
 * Deprecate Clearcut event transport mechanism.
 * Enable dynamic framework support. (#7569)
 * Remove the warning to include Firebase Analytics as Perf does not depend on Analytics (#7487)
+* Fix the crash on gauge manager due to race condition. (#7535)
 
 # Version 7.7.0
 * Add community supported tvOS.
diff --git a/FirebasePerformance/Sources/Gauges/FPRGaugeManager+Private.h b/FirebasePerformance/Sources/Gauges/FPRGaugeManager+Private.h
index 846edb8a10b..a4cedc70e5d 100644
--- a/FirebasePerformance/Sources/Gauges/FPRGaugeManager+Private.h
+++ b/FirebasePerformance/Sources/Gauges/FPRGaugeManager+Private.h
@@ -42,7 +42,9 @@
 
 /**
  * Prepares for dispatching the current set of gauge data to Google Data Transport.
+ *
+ * @param sessionId SessionId that will be used for dispatching the gauge data
  */
-- (void)prepareAndDispatchGaugeData;
+- (void)prepareAndDispatchCollectedGaugeDataWithSessionId:(nullable NSString *)sessionId;
 
 @end
diff --git a/FirebasePerformance/Sources/Gauges/FPRGaugeManager.m b/FirebasePerformance/Sources/Gauges/FPRGaugeManager.m
index 7bd8a9cabee..fc4f2fadc72 100644
--- a/FirebasePerformance/Sources/Gauges/FPRGaugeManager.m
+++ b/FirebasePerformance/Sources/Gauges/FPRGaugeManager.m
@@ -37,7 +37,7 @@ @interface FPRGaugeManager () <FPRCPUGaugeCollectorDelegate, FPRMemoryGaugeColle
 @property(nonatomic) NSMutableArray *gaugeData;
 
 /** @brief Currently active sessionID. */
-@property(nonatomic, readwrite) NSString *currentSessionId;
+@property(nonatomic, readwrite, copy) NSString *currentSessionId;
 
 @end
 
@@ -117,7 +117,8 @@ - (BOOL)gaugeCollectionEnabled {
 }
 
 - (void)startCollectingGauges:(FPRGauges)gauges forSessionId:(NSString *)sessionId {
-  [self prepareAndDispatchGaugeData];
+  // Dispatch the already available gauge data with old sessionId.
+  [self prepareAndDispatchCollectedGaugeDataWithSessionId:self.currentSessionId];
 
   self.currentSessionId = sessionId;
   if (self.gaugeCollectionEnabled) {
@@ -143,7 +144,8 @@ - (void)stopCollectingGauges:(FPRGauges)gauges {
 
   self.activeGauges = self.activeGauges & ~(gauges);
 
-  [self prepareAndDispatchGaugeData];
+  // Flush out all the already collected gauge metrics
+  [self prepareAndDispatchCollectedGaugeDataWithSessionId:self.currentSessionId];
 }
 
 - (void)collectAllGauges {
@@ -172,31 +174,33 @@ - (void)dispatchMetric:(id)gaugeMetric {
 
 #pragma mark - Utils
 
-- (void)prepareAndDispatchGaugeData {
-  NSArray *currentBatch = self.gaugeData;
-  NSString *currentSessionId = self.currentSessionId;
-  self.gaugeData = [[NSMutableArray alloc] init];
-  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
-    if (currentBatch.count > 0) {
-      [[FPRClient sharedInstance] logGaugeMetric:currentBatch forSessionId:currentSessionId];
-      FPRLogDebug(kFPRGaugeManagerDataCollected, @"Logging %lu gauge metrics.",
-                  (unsigned long)currentBatch.count);
-    }
+- (void)prepareAndDispatchCollectedGaugeDataWithSessionId:(nullable NSString *)sessionId {
+  dispatch_async(self.gaugeDataProtectionQueue, ^{
+    NSArray *dispatchGauges = [self.gaugeData copy];
+    self.gaugeData = [[NSMutableArray alloc] init];
+
+    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+      if (dispatchGauges.count > 0 && sessionId != nil) {
+        [[FPRClient sharedInstance] logGaugeMetric:dispatchGauges forSessionId:sessionId];
+        FPRLogDebug(kFPRGaugeManagerDataCollected, @"Logging %lu gauge metrics.",
+                    (unsigned long)dispatchGauges.count);
+      }
+    });
   });
 }
 
 /**
  * Adds the gauge to the batch and decide on when to dispatch the events to Google Data Transport.
  *
- * @param gaugeData Gauge data received from the collectors.
+ * @param gauge Gauge data received from the collectors.
  */
-- (void)addGaugeData:(id)gaugeData {
+- (void)addGaugeData:(id)gauge {
   dispatch_async(self.gaugeDataProtectionQueue, ^{
-    if (gaugeData) {
-      [self.gaugeData addObject:gaugeData];
+    if (gauge) {
+      [self.gaugeData addObject:gauge];
 
       if (self.gaugeData.count >= kGaugeDataBatchSize) {
-        [self prepareAndDispatchGaugeData];
+        [self prepareAndDispatchCollectedGaugeDataWithSessionId:self.currentSessionId];
       }
     }
   });
diff --git a/FirebasePerformance/Tests/Unit/Gauges/FPRGaugeManagerTests.m b/FirebasePerformance/Tests/Unit/Gauges/FPRGaugeManagerTests.m
index b3e673693b4..1d463648a03 100644
--- a/FirebasePerformance/Tests/Unit/Gauges/FPRGaugeManagerTests.m
+++ b/FirebasePerformance/Tests/Unit/Gauges/FPRGaugeManagerTests.m
@@ -154,7 +154,7 @@ - (void)testCollectAllGauges {
 - (void)testBatchingOfGaugeEvents {
   FPRGaugeManager *manager = [FPRGaugeManager sharedInstance];
   id mock = [OCMockObject partialMockForObject:manager];
-  OCMExpect([mock prepareAndDispatchGaugeData]).andDo(nil);
+  OCMExpect([mock prepareAndDispatchCollectedGaugeDataWithSessionId:@"abc"]).andDo(nil);
   [manager startCollectingGauges:FPRGaugeCPU forSessionId:@"abc"];
   [manager.cpuGaugeCollector stopCollecting];
   for (int i = 0; i < kGaugeDataBatchSize; i++) {
@@ -172,7 +172,7 @@ - (void)testBatchingOfGaugeEventsDoesNotHappenLessThanBatchSize {
   id mock = [OCMockObject partialMockForObject:manager];
   [manager startCollectingGauges:FPRGaugeCPU forSessionId:@"abc"];
   [manager.cpuGaugeCollector stopCollecting];
-  OCMReject([mock prepareAndDispatchGaugeData]);
+  OCMReject([mock prepareAndDispatchCollectedGaugeDataWithSessionId:@"abc"]);
   for (int i = 0; i < kGaugeDataBatchSize - 1; i++) {
     [manager.cpuGaugeCollector collectMetric];
   }


Test patch: