firebase / firebase/firebase-ios-sdk

RemoteConfig: activate() crashes in RCNConfigExperiment due to a data race with the experiment DB load

Open
#16,303 0 comments 0 reactions 0 assignees View on GitHub
api: remoteconfig
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.