15
15
package com .google .firebase .firestore ;
16
16
17
17
import androidx .annotation .GuardedBy ;
18
+ import androidx .annotation .VisibleForTesting ;
18
19
import androidx .core .util .Consumer ;
19
20
import com .google .android .gms .tasks .Task ;
20
21
import com .google .firebase .firestore .core .FirestoreClient ;
23
24
import java .util .concurrent .Executor ;
24
25
25
26
/**
26
- * The FirestoreClientProvider handles the life cycle of FirestoreClients.
27
+ * The `FirestoreClientProvider` handles the life cycle of `FirestoreClient`s within a `Firestore`
28
+ * instance.
29
+ *
30
+ * The instantiation of `FirestoreClient` is delayed until there is a need for the client. This
31
+ * delay affords changes to configuration through the `Firestore` instance prior to performing a
32
+ * query. After instantiation of the `FirestoreClient`, the `Firestore` instance is considered
33
+ * configured, and any subsequent attempt to modify configuration will throw anexception.
34
+ *
35
+ * Access to `FirestoreClient` is via synchronized indirection to ensure the `FirestoreClient` is
36
+ * configured, instantiated and current. The `FirestoreClient` should be considered ephemeral, such
37
+ * that no reference to `FirestoreClient` should be retained outside of this provider.
38
+ *
39
+ * All calls to the `FirestoreClient` should be done through access methods in the
40
+ * `FirestoreClientProvider`. Access methods take a functional block of code as a parameter. The
41
+ * most current `FirestoreClient` instance will be applied to the functional block of code.
42
+ * Execution of the functional block of code will be synchronous to ensure the `FirestoreClient`
43
+ * instance remains current during execution.
44
+ *
45
+ * Retaining a reference to `FirestoreClient` outside of `FirestoreClientProvider` risks calling a
46
+ * no longer current `FirestoreClient`. Internally, the `FirestoreClient` may self reference, but
47
+ * this is with intent to couple internal logic with a specific `FirestoreClient` instance.
48
+ *
49
+ * The life of a `FirestoreClient` is tightly coupled to the life the internal `AsyncQueue`. The
50
+ * `AsyncQueue` is associated with exactly one `FirestoreClient`, and when that `FirestoreClient` is
51
+ * terminated, the `AsyncQueue` is shutdown. Internal coupling within `FirestoreClient` relies on
52
+ * `AsyncQueue` to stop processing upon shutdown. A terminated `FirestoreClient` will also rely on
53
+ * `AsyncQueue` to safeguard against external access.
27
54
*/
28
55
final class FirestoreClientProvider {
29
56
@@ -40,10 +67,18 @@ final class FirestoreClientProvider {
40
67
this .asyncQueue = new AsyncQueue ();
41
68
}
42
69
70
+ /**
71
+ * Indicates whether `FirestoreClient` has been instantiated thereby preventing change to
72
+ * configuration.
73
+ */
43
74
boolean isConfigured () {
44
75
return client != null ;
45
76
}
46
77
78
+ /**
79
+ * Prevents further change to configuration, and instantiates the `FirestoreClient` instance
80
+ * to be ready for use.
81
+ */
47
82
synchronized void ensureConfigured () {
48
83
if (!isConfigured ()) {
49
84
client = clientFactory .apply (asyncQueue );
@@ -68,6 +103,21 @@ synchronized void procedure(Consumer<FirestoreClient> call) {
68
103
call .accept (client );
69
104
}
70
105
106
+ /**
107
+ * Conditional execution based on whether `FirestoreClient` is up and running.
108
+ *
109
+ * Handling the conditional logic as part of `FirestoreClientProvider` prevents possible race
110
+ * condition between condition check and execution functional block of code.
111
+ *
112
+ * Example, clearing the cache can only be done while `FirestoreClient` is not running. Checking
113
+ * whether `FirestoreClient` is running and then performing clearing of cache outside of a
114
+ * synchronized code block, risks another thread instantiating `FirestoreClient` after check, but
115
+ * before running code to clear cache.
116
+ *
117
+ * @param callIf Executes if client is shutdown or client hasn't been started yet.
118
+ * @param callElse Executes if client is running.
119
+ * @return Result of execution.
120
+ */
71
121
synchronized <T > T executeIfShutdown (
72
122
Function <Executor , T > callIf , Function <Executor , T > callElse ) {
73
123
Executor executor = command -> asyncQueue .enqueueAndForgetEvenAfterShutdown (command );
@@ -94,6 +144,14 @@ synchronized Task<Void> terminate() {
94
144
return terminate ;
95
145
}
96
146
147
+ /**
148
+ * Direct access to internal AsyncQueue.
149
+ *
150
+ * The danger of using this method is retaining non-synchronized direct access to AsyncQueue.
151
+ *
152
+ * @return internal AsyncQueue
153
+ */
154
+ @ VisibleForTesting
97
155
AsyncQueue getAsyncQueue () {
98
156
return asyncQueue ;
99
157
}
0 commit comments