34
34
import java .util .Map ;
35
35
import java .util .Set ;
36
36
import java .util .concurrent .Executor ;
37
+ import java .util .concurrent .atomic .AtomicReference ;
37
38
38
39
/**
39
40
* Main entry point to the component system.
@@ -48,7 +49,7 @@ public class ComponentRuntime extends AbstractComponentContainer implements Comp
48
49
private final Map <Class <?>, LazySet <?>> lazySetMap = new HashMap <>();
49
50
private final List <Provider <ComponentRegistrar >> unprocessedRegistrarProviders ;
50
51
private final EventBus eventBus ;
51
- private Boolean eagerComponentsInitializedWith = null ;
52
+ private final AtomicReference < Boolean > eagerComponentsInitializedWith = new AtomicReference <>() ;
52
53
53
54
/**
54
55
* Creates an instance of {@link ComponentRuntime} for the provided {@link ComponentRegistrar}s
@@ -90,49 +91,63 @@ private ComponentRuntime(
90
91
discoverComponents (componentsToAdd );
91
92
}
92
93
93
- private synchronized void discoverComponents (List <Component <?>> componentsToAdd ) {
94
-
95
- Iterator <Provider <ComponentRegistrar >> iterator = unprocessedRegistrarProviders .iterator ();
96
- while (iterator .hasNext ()) {
97
- Provider <ComponentRegistrar > provider = iterator .next ();
98
- try {
99
- ComponentRegistrar registrar = provider .get ();
100
- if (registrar != null ) {
101
- componentsToAdd .addAll (registrar .getComponents ());
94
+ private void discoverComponents (List <Component <?>> componentsToAdd ) {
95
+ // During discovery many things need to happen, of which "Deferred resolution" and "Set binding
96
+ // updates" are of most interest. During these phases we execute "client" code(i.e. the code of
97
+ // SDKs participating in Components DI), this code can indirectly end up calling back into the
98
+ // ComponentRuntime which, without proper care, can result in a deadlock. For this reason,
99
+ // instead of executing such code in the synchronized block below, we store it in a list and
100
+ // execute right after the synchronized section.
101
+ List <Runnable > runAfterDiscovery = new ArrayList <>();
102
+ synchronized (this ) {
103
+ Iterator <Provider <ComponentRegistrar >> iterator = unprocessedRegistrarProviders .iterator ();
104
+ while (iterator .hasNext ()) {
105
+ Provider <ComponentRegistrar > provider = iterator .next ();
106
+ try {
107
+ ComponentRegistrar registrar = provider .get ();
108
+ if (registrar != null ) {
109
+ componentsToAdd .addAll (registrar .getComponents ());
110
+ iterator .remove ();
111
+ }
112
+ } catch (InvalidRegistrarException ex ) {
102
113
iterator .remove ();
114
+ Log .w (ComponentDiscovery .TAG , "Invalid component registrar." , ex );
103
115
}
104
- } catch (InvalidRegistrarException ex ) {
105
- iterator .remove ();
106
- Log .w (ComponentDiscovery .TAG , "Invalid component registrar." , ex );
107
116
}
108
- }
109
117
110
- if (components .isEmpty ()) {
111
- CycleDetector .detect (componentsToAdd );
112
- } else {
113
- ArrayList <Component <?>> allComponents = new ArrayList <>(this .components .keySet ());
114
- allComponents .addAll (componentsToAdd );
115
- CycleDetector .detect (allComponents );
116
- }
118
+ if (components .isEmpty ()) {
119
+ CycleDetector .detect (componentsToAdd );
120
+ } else {
121
+ ArrayList <Component <?>> allComponents = new ArrayList <>(this .components .keySet ());
122
+ allComponents .addAll (componentsToAdd );
123
+ CycleDetector .detect (allComponents );
124
+ }
117
125
118
- for (Component <?> component : componentsToAdd ) {
119
- Lazy <?> lazy =
120
- new Lazy <>(
121
- () ->
122
- component .getFactory ().create (new RestrictedComponentContainer (component , this )));
126
+ for (Component <?> component : componentsToAdd ) {
127
+ Lazy <?> lazy =
128
+ new Lazy <>(
129
+ () ->
130
+ component
131
+ .getFactory ()
132
+ .create (new RestrictedComponentContainer (component , this )));
123
133
124
- components .put (component , lazy );
125
- }
134
+ components .put (component , lazy );
135
+ }
126
136
127
- processInstanceComponents (componentsToAdd );
128
- processSetComponents ();
129
- processDependencies ();
137
+ runAfterDiscovery .addAll (processInstanceComponents (componentsToAdd ));
138
+ runAfterDiscovery .addAll (processSetComponents ());
139
+ processDependencies ();
140
+ }
141
+ for (Runnable runnable : runAfterDiscovery ) {
142
+ runnable .run ();
143
+ }
130
144
maybeInitializeEagerComponents ();
131
145
}
132
146
133
147
private void maybeInitializeEagerComponents () {
134
- if (eagerComponentsInitializedWith != null ) {
135
- doInitializeEagerComponents (eagerComponentsInitializedWith );
148
+ Boolean isDefaultApp = eagerComponentsInitializedWith .get ();
149
+ if (isDefaultApp != null ) {
150
+ doInitializeEagerComponents (components , isDefaultApp );
136
151
}
137
152
}
138
153
@@ -153,7 +168,8 @@ private static <T> List<T> iterableToList(Iterable<T> iterable) {
153
168
return result ;
154
169
}
155
170
156
- private void processInstanceComponents (List <Component <?>> componentsToProcess ) {
171
+ private List <Runnable > processInstanceComponents (List <Component <?>> componentsToProcess ) {
172
+ ArrayList <Runnable > runnables = new ArrayList <>();
157
173
for (Component <?> component : componentsToProcess ) {
158
174
if (!component .isValue ()) {
159
175
continue ;
@@ -169,14 +185,16 @@ private void processInstanceComponents(List<Component<?>> componentsToProcess) {
169
185
OptionalProvider <Object > deferred = (OptionalProvider <Object >) existingProvider ;
170
186
@ SuppressWarnings ("unchecked" )
171
187
Provider <Object > castedProvider = (Provider <Object >) provider ;
172
- deferred .set (castedProvider );
188
+ runnables . add (() -> deferred .set (castedProvider ) );
173
189
}
174
190
}
175
191
}
192
+ return runnables ;
176
193
}
177
194
178
195
/** Populates lazySetMap to make set components available for consumption via set dependencies. */
179
- private void processSetComponents () {
196
+ private List <Runnable > processSetComponents () {
197
+ ArrayList <Runnable > runnables = new ArrayList <>();
180
198
Map <Class <?>, Set <Provider <?>>> setIndex = new HashMap <>();
181
199
for (Map .Entry <Component <?>, Provider <?>> entry : components .entrySet ()) {
182
200
Component <?> component = entry .getKey ();
@@ -202,10 +220,11 @@ private void processSetComponents() {
202
220
} else {
203
221
LazySet <Object > existingSet = (LazySet <Object >) lazySetMap .get (entry .getKey ());
204
222
for (Provider <?> provider : entry .getValue ()) {
205
- existingSet .add ((Provider <Object >) provider );
223
+ runnables . add (() -> existingSet .add ((Provider <Object >) provider ) );
206
224
}
207
225
}
208
226
}
227
+ return runnables ;
209
228
}
210
229
211
230
@ Override
@@ -243,17 +262,28 @@ public synchronized <T> Provider<Set<T>> setOfProvider(Class<T> anInterface) {
243
262
* <p>Should be called at an appropriate time in the owner's lifecycle.
244
263
*
245
264
* <p>Note: the method is idempotent.
265
+ *
266
+ * <p>Warning: it's important that this method is not synchronized as it could cause a deadlock if
267
+ * another SDK is initializing in another thread.
246
268
*/
247
- public synchronized void initializeEagerComponents (boolean isDefaultApp ) {
248
- if (eagerComponentsInitializedWith != null ) {
269
+ public void initializeEagerComponents (boolean isDefaultApp ) {
270
+ if (! eagerComponentsInitializedWith . compareAndSet ( null , isDefaultApp ) ) {
249
271
return ;
250
272
}
251
- eagerComponentsInitializedWith = isDefaultApp ;
252
- doInitializeEagerComponents (isDefaultApp );
273
+
274
+ // we copy the map under a lock to avoid a race condition.
275
+ // Note that we cannot use a ConcurrentHashMap as it is broken on older Android versions, see:
276
+ // https://issuetracker.google.com/issues/37042460
277
+ HashMap <Component <?>, Provider <?>> componentsCopy ;
278
+ synchronized (this ) {
279
+ componentsCopy = new HashMap <>(components );
280
+ }
281
+ doInitializeEagerComponents (componentsCopy , isDefaultApp );
253
282
}
254
283
255
- private void doInitializeEagerComponents (boolean isDefaultApp ) {
256
- for (Map .Entry <Component <?>, Provider <?>> entry : components .entrySet ()) {
284
+ private void doInitializeEagerComponents (
285
+ Map <Component <?>, Provider <?>> componentsToInitialize , boolean isDefaultApp ) {
286
+ for (Map .Entry <Component <?>, Provider <?>> entry : componentsToInitialize .entrySet ()) {
257
287
Component <?> component = entry .getKey ();
258
288
Provider <?> provider = entry .getValue ();
259
289
@@ -266,9 +296,11 @@ private void doInitializeEagerComponents(boolean isDefaultApp) {
266
296
}
267
297
268
298
@ Override
269
- public synchronized void discoverComponents () {
270
- if (unprocessedRegistrarProviders .isEmpty ()) {
271
- return ;
299
+ public void discoverComponents () {
300
+ synchronized (this ) {
301
+ if (unprocessedRegistrarProviders .isEmpty ()) {
302
+ return ;
303
+ }
272
304
}
273
305
discoverComponents (new ArrayList <>());
274
306
}
0 commit comments