firebase / firebase/firebase-ios-sdk
RemoteConfig: activate() crashes in RCNConfigExperiment due to a data race with the experiment DB load
- Dominant language
- C++
- Stars
- 6.7k
- Forks
- 1.8k
- Avg merge
- 2d 14h
- Merged PRs (30d)
- 72
Description
### Description
We're hitting an intermittent crash in -[RCNConfigExperiment updateExperimentStartTime] (SIGTRAP in objc_opt_isKindOfClass, raised from NSJSONSerialization) when activate() runs soon after the RemoteConfig instance is created at app launch.
Looking through the 12.15.0 source, it looks like a data race on RCNConfigExperiment's experiment ivars (_experimentPayloads, _experimentMetadata, _activeExperimentPayloads), which are touched from two queues with no synchronization:
- Write side (main queue): RCNConfigExperiment loads experiments in its init (loadExperimentFromTable). -[RCNConfigDBManager loadExperimentWithCompletionHandler:] delivers that completion on dispatch_get_main_queue(), where it assigns those ivars directly.
- Read side (FIRRemoteConfig's serial _queue): -[FIRRemoteConfig activateWithCompletion:] dispatches to _queue, then updateExperimentStartTime serializes _experimentMetadata with NSJSONSerialization.
RCNConfigExperiment has no queue/lock around these ivars, so the dictionary can change on the main queue while NSJSONSerialization walks it on _queue, which produces the trap. A possible fix would be to run the experiment-load write path on the same serial queue as the activate read path, or add a lock inside RCNConfigExperiment (the read path is already on _queue; the DB-load write path isn't).
ensureInitialized doesn't help here, since it only waits on RCNConfigContent's load, not the experiment load.
### Reproducing the issue
Call activate() / fetchAndActivate() early in launch, right after RemoteConfig.remoteConfig() is created, so it runs before the async (main-queue) experiment-load completion has run. It's intermittent and shows up more on slower devices. At the time of our crash, another thread was concurrently in -[RCNConfigDBManager insertMainTableWithValues:fromSource:].
### Firebase SDK Version
12.15.0
### Xcode Version
26.x
### Installation Method
Swift Package Manager
### Firebase Product(s)
Remote Config
### Targeted Platforms
iOS
### Relevant Log Output
```shell
Exception Type: SIGTRAP
Thread (crashed):
0 libobjc.A.dylib objc_opt_isKindOfClass
1 Foundation _writeJSONValue
2 Foundation +[NSJSONSerialization dataWithJSONObject:options:error:]
3 RCNConfigExperiment -[RCNConfigExperiment updateExperimentStartTime]
4 RCNConfigExperiment -[RCNConfigExperiment updateExperimentsWithHandler:]
5 FIRRemoteConfig __-[FIRRemoteConfig activateWithCompletion:]_block_invoke
6 libdispatch.dylib _dispatch_call_block_and_release
...
```
### If using Swift Package Manager, the project's Package.resolved
Expand Package.resolved snippet
```json
Replace this line with the contents of your Package.resolved.
```
### If using CocoaPods, the project's Podfile.lock
Expand Podfile.lock snippet
```yml
Replace this line with the contents of your Podfile.lock!
```
Contributor guide
Research direction
Start by tracing RCNConfigExperiment's loadExperimentFromTable and RCNConfigDBManager's loadExperimentWithCompletionHandler alongside FIRRemoteConfig's activateWithCompletion and updateExperimentStartTime. Reproduce early activate() or fetchAndActivate() after RemoteConfig.remoteConfig() creation, then add coverage showing experiment loading and activation complete without the reported crash or data race.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- ios, objective-c
- Domain
- databases, mobile-dev
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100