Skip to content

Commit a9dc976

Browse files
committed
Revert "use threadlocal instead of synchronization"
This reverts commit 32c3add. It made no performance improvements whatsoever.
1 parent 32c3add commit a9dc976

File tree

4 files changed

+71
-87
lines changed

4 files changed

+71
-87
lines changed

firebase-firestore/src/main/java/com/google/firebase/firestore/local/SQLiteDocumentOverlayCache.java

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -61,26 +61,29 @@ public Overlay getOverlay(DocumentKey key) {
6161
@Override
6262
public Map<DocumentKey, Overlay> getOverlays(SortedSet<DocumentKey> keys) {
6363
hardAssert(keys.comparator() == null, "getOverlays() requires natural order");
64+
Map<DocumentKey, Overlay> result = new HashMap<>();
6465

65-
BackgroundQueue<DocumentKey, Overlay> backgroundQueue = new BackgroundQueue<>();
66+
BackgroundQueue backgroundQueue = new BackgroundQueue();
6667
ResourcePath currentCollection = ResourcePath.EMPTY;
6768
List<Object> accumulatedDocumentIds = new ArrayList<>();
6869
for (DocumentKey key : keys) {
6970
if (!currentCollection.equals(key.getCollectionPath())) {
70-
processSingleCollection(backgroundQueue, currentCollection, accumulatedDocumentIds);
71+
processSingleCollection(result, backgroundQueue, currentCollection, accumulatedDocumentIds);
7172
currentCollection = key.getCollectionPath();
7273
accumulatedDocumentIds.clear();
7374
}
7475
accumulatedDocumentIds.add(key.getDocumentId());
7576
}
7677

77-
processSingleCollection(backgroundQueue, currentCollection, accumulatedDocumentIds);
78-
return backgroundQueue.drain();
78+
processSingleCollection(result, backgroundQueue, currentCollection, accumulatedDocumentIds);
79+
backgroundQueue.drain();
80+
return result;
7981
}
8082

8183
/** Reads the overlays for the documents in a single collection. */
8284
private void processSingleCollection(
83-
BackgroundQueue<DocumentKey, Overlay> backgroundQueue,
85+
Map<DocumentKey, Overlay> result,
86+
BackgroundQueue backgroundQueue,
8487
ResourcePath collectionPath,
8588
List<Object> documentIds) {
8689
if (documentIds.isEmpty()) {
@@ -98,7 +101,7 @@ private void processSingleCollection(
98101
while (longQuery.hasMoreSubqueries()) {
99102
longQuery
100103
.performNextSubquery()
101-
.forEach(row -> processOverlaysInBackground(backgroundQueue, row));
104+
.forEach(row -> processOverlaysInBackground(backgroundQueue, result, row));
102105
}
103106
}
104107

@@ -135,23 +138,26 @@ public void removeOverlaysForBatchId(int batchId) {
135138

136139
@Override
137140
public Map<DocumentKey, Overlay> getOverlays(ResourcePath collection, int sinceBatchId) {
138-
BackgroundQueue<DocumentKey, Overlay> backgroundQueue = new BackgroundQueue<>();
141+
Map<DocumentKey, Overlay> result = new HashMap<>();
142+
BackgroundQueue backgroundQueue = new BackgroundQueue();
139143
db.query(
140144
"SELECT overlay_mutation, largest_batch_id FROM document_overlays "
141145
+ "WHERE uid = ? AND collection_path = ? AND largest_batch_id > ?")
142146
.binding(uid, EncodedPath.encode(collection), sinceBatchId)
143-
.forEach(row -> processOverlaysInBackground(backgroundQueue, row));
144-
return backgroundQueue.drain();
147+
.forEach(row -> processOverlaysInBackground(backgroundQueue, result, row));
148+
backgroundQueue.drain();
149+
return result;
145150
}
146151

147152
@Override
148153
public Map<DocumentKey, Overlay> getOverlays(
149154
String collectionGroup, int sinceBatchId, int count) {
155+
Map<DocumentKey, Overlay> result = new HashMap<>();
150156
String[] lastCollectionPath = new String[1];
151157
String[] lastDocumentPath = new String[1];
152158
int[] lastLargestBatchId = new int[1];
153159

154-
BackgroundQueue<DocumentKey, Overlay> backgroundQueue1 = new BackgroundQueue<>();
160+
BackgroundQueue backgroundQueue = new BackgroundQueue();
155161
db.query(
156162
"SELECT overlay_mutation, largest_batch_id, collection_path, document_id "
157163
+ " FROM document_overlays "
@@ -163,19 +169,17 @@ public Map<DocumentKey, Overlay> getOverlays(
163169
lastLargestBatchId[0] = row.getInt(1);
164170
lastCollectionPath[0] = row.getString(2);
165171
lastDocumentPath[0] = row.getString(3);
166-
processOverlaysInBackground(backgroundQueue1, row);
172+
processOverlaysInBackground(backgroundQueue, result, row);
167173
});
168174

169-
HashMap<DocumentKey, Overlay> results = backgroundQueue1.drain();
170175
if (lastCollectionPath[0] == null) {
171-
return results;
176+
return result;
172177
}
173178

174179
// This function should not return partial batch overlays, even if the number of overlays in the
175180
// result set exceeds the given `count` argument. Since the `LIMIT` in the above query might
176181
// result in a partial batch, the following query appends any remaining overlays for the last
177182
// batch.
178-
BackgroundQueue<DocumentKey, Overlay> backgroundQueue2 = new BackgroundQueue<>();
179183
db.query(
180184
"SELECT overlay_mutation, largest_batch_id FROM document_overlays "
181185
+ "WHERE uid = ? AND collection_group = ? "
@@ -188,21 +192,22 @@ public Map<DocumentKey, Overlay> getOverlays(
188192
lastCollectionPath[0],
189193
lastDocumentPath[0],
190194
lastLargestBatchId[0])
191-
.forEach(row -> processOverlaysInBackground(backgroundQueue2, row));
192-
193-
backgroundQueue2.drainInto(results);
194-
return results;
195+
.forEach(row -> processOverlaysInBackground(backgroundQueue, result, row));
196+
backgroundQueue.drain();
197+
return result;
195198
}
196199

197200
private void processOverlaysInBackground(
198-
BackgroundQueue<DocumentKey, Overlay> backgroundQueue, Cursor row) {
201+
BackgroundQueue backgroundQueue, Map<DocumentKey, Overlay> results, Cursor row) {
199202
byte[] rawMutation = row.getBlob(0);
200203
int largestBatchId = row.getInt(1);
201204

202205
backgroundQueue.submit(
203-
results -> {
206+
() -> {
204207
Overlay overlay = decodeOverlay(rawMutation, largestBatchId);
205-
results.put(overlay.getKey(), overlay);
208+
synchronized (results) {
209+
results.put(overlay.getKey(), overlay);
210+
}
206211
});
207212
}
208213

firebase-firestore/src/main/java/com/google/firebase/firestore/local/SQLiteRemoteDocumentCache.java

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -168,19 +168,21 @@ public Map<DocumentKey, MutableDocument> getAll(Iterable<DocumentKey> documentKe
168168
bindVars,
169169
") ORDER BY path");
170170

171-
BackgroundQueue<DocumentKey, MutableDocument> backgroundQueue = new BackgroundQueue<>();
171+
BackgroundQueue backgroundQueue = new BackgroundQueue();
172172
while (longQuery.hasMoreSubqueries()) {
173173
longQuery
174174
.performNextSubquery()
175-
.forEach(row -> processRowInBackground(backgroundQueue, row, /*filter*/ null));
175+
.forEach(row -> processRowInBackground(backgroundQueue, results, row, /*filter*/ null));
176176
}
177-
178-
backgroundQueue.drainInto(results);
177+
backgroundQueue.drain();
179178

180179
// Backfill any rows with null "document_type" discovered by processRowInBackground().
181180
documentTypeBackfiller.backfill(db);
182181

183-
return results;
182+
// Synchronize on `results` to avoid a data race with the background queue.
183+
synchronized (results) {
184+
return results;
185+
}
184186
}
185187

186188
@Override
@@ -262,23 +264,26 @@ private Map<DocumentKey, MutableDocument> getAll(
262264
}
263265
bindVars[i] = count;
264266

265-
BackgroundQueue<DocumentKey, MutableDocument> backgroundQueue = new BackgroundQueue<>();
267+
BackgroundQueue backgroundQueue = new BackgroundQueue();
268+
Map<DocumentKey, MutableDocument> results = new HashMap<>();
266269
db.query(sql.toString())
267270
.binding(bindVars)
268271
.forEach(
269272
row -> {
270-
processRowInBackground(backgroundQueue, row, filter);
273+
processRowInBackground(backgroundQueue, results, row, filter);
271274
if (context != null) {
272275
context.incrementDocumentReadCount();
273276
}
274277
});
275-
276-
HashMap<DocumentKey, MutableDocument> results = backgroundQueue.drain();
278+
backgroundQueue.drain();
277279

278280
// Backfill any null "document_type" columns discovered by processRowInBackground().
279281
documentTypeBackfiller.backfill(db);
280282

281-
return results;
283+
// Synchronize on `results` to avoid a data race with the background queue.
284+
synchronized (results) {
285+
return results;
286+
}
282287
}
283288

284289
private Map<DocumentKey, MutableDocument> getAll(
@@ -291,7 +296,8 @@ private Map<DocumentKey, MutableDocument> getAll(
291296
}
292297

293298
private void processRowInBackground(
294-
BackgroundQueue<DocumentKey, MutableDocument> backgroundQueue,
299+
BackgroundQueue backgroundQueue,
300+
Map<DocumentKey, MutableDocument> results,
295301
Cursor row,
296302
@Nullable Function<MutableDocument, Boolean> filter) {
297303
byte[] rawDocument = row.getBlob(0);
@@ -301,14 +307,16 @@ private void processRowInBackground(
301307
String path = row.getString(4);
302308

303309
backgroundQueue.submit(
304-
results -> {
310+
() -> {
305311
MutableDocument document =
306312
decodeMaybeDocument(rawDocument, readTimeSeconds, readTimeNanos);
307313
if (documentTypeIsNull) {
308314
documentTypeBackfiller.enqueue(path, readTimeSeconds, readTimeNanos, document);
309315
}
310316
if (filter == null || filter.apply(document)) {
311-
results.put(document.getKey(), document);
317+
synchronized (results) {
318+
results.put(document.getKey(), document);
319+
}
312320
}
313321
});
314322
}

firebase-firestore/src/main/java/com/google/firebase/firestore/util/BackgroundQueue.kt

Lines changed: 13 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -27,29 +27,24 @@ import kotlinx.coroutines.asExecutor
2727
* called from the same thread. The behavior of an instance is undefined if any of the methods are
2828
* called from multiple threads.
2929
*/
30-
internal class BackgroundQueue<K, V> {
30+
internal class BackgroundQueue {
3131

32-
private var state: State<K, V> = State.Submitting()
33-
34-
/** Overload for convenience of being called from Java code. */
35-
fun submit(consumer: Consumer<HashMap<K, V>>) = this.submit { consumer.accept(it) }
32+
private var state: State = State.Submitting()
3633

3734
/**
3835
* Submit a task for asynchronous execution on the executor of the owning [BackgroundQueue].
3936
*
4037
* @throws IllegalStateException if [drain] has been called.
4138
*/
42-
fun submit(runnable: (results: HashMap<K, V>) -> Unit) {
39+
fun submit(runnable: Runnable) {
4340
val submittingState = this.state
44-
check(submittingState is State.Submitting) {
45-
"submit() may not be called after drain() or drainInto()"
46-
}
41+
check(submittingState is State.Submitting) { "submit() may not be called after drain()" }
4742

4843
submittingState.run {
4944
taskCount++
5045
executor.execute {
5146
try {
52-
runnable(threadLocalResults.get()!!)
47+
runnable.run()
5348
} finally {
5449
completedTasks.release()
5550
}
@@ -60,53 +55,22 @@ internal class BackgroundQueue<K, V> {
6055
/**
6156
* Blocks until all tasks submitted via calls to [submit] have completed.
6257
*
63-
* The results produced by each thread are merged into a new [HashMap] and returned.
64-
*
65-
* @throws IllegalStateException if [drain] or [drainInto] has already been called.
66-
*/
67-
fun drain(): HashMap<K, V> = HashMap<K, V>().also { drainInto(it) }
68-
69-
/**
70-
* Blocks until all tasks submitted via calls to [submit] have completed.
71-
*
72-
* The results produced by each thread are merged into the given map.
73-
*
74-
* @throws IllegalStateException if [drain] or [drainInto] has already been called.
58+
* @throws IllegalStateException if called more than once.
7559
*/
76-
fun drainInto(results: MutableMap<K, V>) {
60+
fun drain() {
7761
val submittingState = this.state
78-
check(submittingState is State.Submitting) { "drain() or drainInto() has already been called" }
79-
this.state = State.Draining()
80-
return submittingState.run {
81-
completedTasks.acquire(taskCount)
82-
threadLocalResults.mergeResultsInto(results)
83-
}
84-
}
85-
86-
private class ThreadLocalResults<K, V> : ThreadLocal<HashMap<K, V>>() {
87-
88-
private val results = mutableListOf<HashMap<K, V>>()
62+
check(submittingState is State.Submitting) { "drain() may not be called more than once" }
63+
this.state = State.Draining
8964

90-
override fun initialValue(): HashMap<K, V> {
91-
synchronized(results) {
92-
val result = HashMap<K, V>()
93-
results.add(result)
94-
return result
95-
}
96-
}
97-
98-
fun mergeResultsInto(mergedResults: MutableMap<K, V>) {
99-
synchronized(results) { results.forEach { mergedResults.putAll(it) } }
100-
}
65+
submittingState.run { completedTasks.acquire(taskCount) }
10166
}
10267

103-
private sealed interface State<K, V> {
104-
class Submitting<K, V> : State<K, V> {
68+
private sealed interface State {
69+
class Submitting : State {
10570
val completedTasks = Semaphore(0)
106-
val threadLocalResults = ThreadLocalResults<K, V>()
10771
var taskCount: Int = 0
10872
}
109-
class Draining<K, V> : State<K, V>
73+
object Draining : State
11074
}
11175

11276
companion object {

firebase-firestore/src/test/java/com/google/firebase/firestore/core/QueryTest.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import java.util.HashMap;
4646
import java.util.Iterator;
4747
import java.util.List;
48+
import java.util.Map;
4849
import org.junit.Assert;
4950
import org.junit.Test;
5051
import org.junit.runner.RunWith;
@@ -983,19 +984,25 @@ public void testSynchronousMatchesOrderBy() {
983984
// SQLiteRemoteDocumentCache.getAll(...), where `query.matches(doc)` is performed
984985
// for many different docs concurrently on the BackgroundQueue.
985986
Iterator<MutableDocument> iterator = docs.iterator();
986-
BackgroundQueue<DocumentKey, Boolean> backgroundQueue = new BackgroundQueue<>();
987+
BackgroundQueue backgroundQueue = new BackgroundQueue();
988+
Map<DocumentKey, Boolean> results = new HashMap<>();
987989

988990
while (iterator.hasNext()) {
989991
MutableDocument doc = iterator.next();
990992
backgroundQueue.submit(
991-
results -> {
993+
() -> {
992994
// We call query.matches() to indirectly test query.matchesOrderBy()
993995
boolean result = query.matches(doc);
994-
results.put(doc.getKey(), result);
996+
997+
// We will include a synchronized block in our command to simulate
998+
// the implementation in SQLiteRemoteDocumentCache.getAll(...)
999+
synchronized (results) {
1000+
results.put(doc.getKey(), result);
1001+
}
9951002
});
9961003
}
9971004

998-
HashMap<DocumentKey, Boolean> results = backgroundQueue.drain();
1005+
backgroundQueue.drain();
9991006

10001007
Assert.assertEquals(101, results.keySet().size());
10011008
for (DocumentKey key : results.keySet()) {

0 commit comments

Comments
 (0)