Skip to content

Commit db997a3

Browse files
ryanwilsonmaksymmalyhin
authored andcommitted
Gate access to cached instances. (#3766)
* Gate access to cached instances. This is causing an issue in #3728. Hopefully this will fix it, but no repro is available yet to test. There's still an underlying race condition where a `creationBlock` could be called twice for a component that is supposed to cache the instance returned, but that should be addressed in a follow up PR. * Review feedback.
1 parent fbf91f6 commit db997a3

File tree

1 file changed

+31
-6
lines changed

1 file changed

+31
-6
lines changed

Firebase/Core/FIRComponentContainer.m

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@
2323

2424
NS_ASSUME_NONNULL_BEGIN
2525

26-
@interface FIRComponentContainer ()
26+
@interface FIRComponentContainer () {
27+
dispatch_queue_t _containerQueue;
28+
}
2729

2830
/// The dictionary of components that are registered for a particular app. The key is an NSString
2931
/// of the protocol.
@@ -67,6 +69,8 @@ - (instancetype)initWithApp:(FIRApp *)app registrants:(NSMutableSet<Class> *)all
6769
_app = app;
6870
_cachedInstances = [NSMutableDictionary<NSString *, id> dictionary];
6971
_components = [NSMutableDictionary<NSString *, FIRComponentCreationBlock> dictionary];
72+
_containerQueue =
73+
dispatch_queue_create("com.google.FirebaseComponentContainer", DISPATCH_QUEUE_SERIAL);
7074

7175
[self populateComponentsFromRegisteredClasses:allRegistrants forApp:app];
7276
}
@@ -92,7 +96,7 @@ - (void)populateComponentsFromRegisteredClasses:(NSSet<Class> *)classes forApp:(
9296
// Store the creation block for later usage.
9397
self.components[protocolName] = component.creationBlock;
9498

95-
// Instantiate the
99+
// Instantiate the instance if it has requested to be instantiated.
96100
BOOL shouldInstantiateEager =
97101
(component.instantiationTiming == FIRInstantiationTimingAlwaysEager);
98102
BOOL shouldInstantiateDefaultEager =
@@ -136,7 +140,9 @@ - (nullable id)instantiateInstanceForProtocol:(Protocol *)protocol
136140

137141
// The instance is ready to be returned, but check if it should be cached first before returning.
138142
if (shouldCache) {
139-
self.cachedInstances[protocolName] = instance;
143+
dispatch_sync(_containerQueue, ^{
144+
self.cachedInstances[protocolName] = instance;
145+
});
140146
}
141147

142148
return instance;
@@ -147,7 +153,11 @@ - (nullable id)instantiateInstanceForProtocol:(Protocol *)protocol
147153
- (nullable id)instanceForProtocol:(Protocol *)protocol {
148154
// Check if there is a cached instance, and return it if so.
149155
NSString *protocolName = NSStringFromProtocol(protocol);
150-
id cachedInstance = self.cachedInstances[protocolName];
156+
__block id cachedInstance;
157+
dispatch_sync(_containerQueue, ^{
158+
cachedInstance = self.cachedInstances[protocolName];
159+
});
160+
151161
if (cachedInstance) {
152162
return cachedInstance;
153163
}
@@ -161,14 +171,29 @@ - (nullable id)instanceForProtocol:(Protocol *)protocol {
161171

162172
- (void)removeAllCachedInstances {
163173
// Loop through the cache and notify each instance that is a maintainer to clean up after itself.
164-
for (id instance in self.cachedInstances.allValues) {
174+
// Design note: we're getting a copy here, unlocking the cached instances, iterating over the
175+
// copy, then locking and removing all cached instances. A race condition *could* exist where a
176+
// new cached instance is created between the copy and the removal, but the chances are slim and
177+
// side-effects are significantly smaller than including the entire loop in the `dispatch_sync`
178+
// block (access to the cache from inside the block would deadlock and crash).
179+
__block NSDictionary<NSString *, id> *instancesCopy;
180+
dispatch_sync(_containerQueue, ^{
181+
instancesCopy = [self.cachedInstances copy];
182+
});
183+
184+
for (id instance in instancesCopy.allValues) {
165185
if ([instance conformsToProtocol:@protocol(FIRComponentLifecycleMaintainer)] &&
166186
[instance respondsToSelector:@selector(appWillBeDeleted:)]) {
167187
[instance appWillBeDeleted:self.app];
168188
}
169189
}
170190

171-
[self.cachedInstances removeAllObjects];
191+
instancesCopy = nil;
192+
193+
// Empty the cache.
194+
dispatch_sync(_containerQueue, ^{
195+
[self.cachedInstances removeAllObjects];
196+
});
172197
}
173198

174199
@end

0 commit comments

Comments
 (0)