Skip to content

Commit 1aa54b3

Browse files
committed
Add comments
1 parent 0d35529 commit 1aa54b3

File tree

1 file changed

+59
-1
lines changed

1 file changed

+59
-1
lines changed

firebase-firestore/src/main/java/com/google/firebase/firestore/FirestoreClientProvider.java

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package com.google.firebase.firestore;
1616

1717
import androidx.annotation.GuardedBy;
18+
import androidx.annotation.VisibleForTesting;
1819
import androidx.core.util.Consumer;
1920
import com.google.android.gms.tasks.Task;
2021
import com.google.firebase.firestore.core.FirestoreClient;
@@ -23,7 +24,33 @@
2324
import java.util.concurrent.Executor;
2425

2526
/**
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.
2754
*/
2855
final class FirestoreClientProvider {
2956

@@ -40,10 +67,18 @@ final class FirestoreClientProvider {
4067
this.asyncQueue = new AsyncQueue();
4168
}
4269

70+
/**
71+
* Indicates whether `FirestoreClient` has been instantiated thereby preventing change to
72+
* configuration.
73+
*/
4374
boolean isConfigured() {
4475
return client != null;
4576
}
4677

78+
/**
79+
* Prevents further change to configuration, and instantiates the `FirestoreClient` instance
80+
* to be ready for use.
81+
*/
4782
synchronized void ensureConfigured() {
4883
if (!isConfigured()) {
4984
client = clientFactory.apply(asyncQueue);
@@ -68,6 +103,21 @@ synchronized void procedure(Consumer<FirestoreClient> call) {
68103
call.accept(client);
69104
}
70105

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+
*/
71121
synchronized <T> T executeIfShutdown(
72122
Function<Executor, T> callIf, Function<Executor, T> callElse) {
73123
Executor executor = command -> asyncQueue.enqueueAndForgetEvenAfterShutdown(command);
@@ -94,6 +144,14 @@ synchronized Task<Void> terminate() {
94144
return terminate;
95145
}
96146

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
97155
AsyncQueue getAsyncQueue() {
98156
return asyncQueue;
99157
}

0 commit comments

Comments
 (0)