Skip to content

Commit 378ddee

Browse files
committed
WatchChangeAggregatorTestingHooks added
1 parent 8696c17 commit 378ddee

File tree

2 files changed

+170
-0
lines changed

2 files changed

+170
-0
lines changed

firebase-firestore/src/main/java/com/google/firebase/firestore/remote/WatchChangeAggregator.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,10 @@ public void handleExistenceFilter(ExistenceFilterWatchChange watchChange) {
216216
resetTarget(targetId);
217217
pendingTargetResets.add(targetId);
218218
}
219+
220+
WatchChangeAggregatorTestingHooks.notifyOnExistenceFilterMismatch(
221+
WatchChangeAggregatorTestingHooks.ExistenceFilterMismatchInfo.from(
222+
bloomFilterApplied, currentSize, watchChange.getExistenceFilter()));
219223
}
220224
}
221225
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
// Copyright 2023 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.firestore.remote;
16+
17+
import static com.google.firebase.firestore.util.Preconditions.checkNotNull;
18+
19+
import androidx.annotation.AnyThread;
20+
import androidx.annotation.NonNull;
21+
import androidx.annotation.Nullable;
22+
import androidx.annotation.VisibleForTesting;
23+
import com.google.auto.value.AutoValue;
24+
import com.google.firebase.firestore.ListenerRegistration;
25+
import com.google.firebase.firestore.util.Executors;
26+
import com.google.firestore.v1.BloomFilter;
27+
import java.util.HashMap;
28+
import java.util.Map;
29+
30+
final class WatchChangeAggregatorTestingHooks {
31+
32+
private WatchChangeAggregatorTestingHooks() {}
33+
34+
private static final Map<Object, ExistenceFilterMismatchListener>
35+
existenceFilterMismatchListeners = new HashMap<>();
36+
37+
/**
38+
* Notifies all registered {@link ExistenceFilterMismatchListener}` listeners registered via
39+
* {@link #addExistenceFilterMismatchListener}.
40+
*
41+
* @param info Information about the existence filter mismatch to deliver to the listeners.
42+
*/
43+
static void notifyOnExistenceFilterMismatch(ExistenceFilterMismatchInfo info) {
44+
synchronized (existenceFilterMismatchListeners) {
45+
for (ExistenceFilterMismatchListener listener : existenceFilterMismatchListeners.values()) {
46+
Executors.BACKGROUND_EXECUTOR.execute(() -> listener.onExistenceFilterMismatch(info));
47+
}
48+
}
49+
}
50+
51+
/**
52+
* Registers a {@link ExistenceFilterMismatchListener} to be notified when an existence filter
53+
* mismatch occurs in the Watch listen stream.
54+
*
55+
* <p>The relative order in which callbacks are notified is unspecified; do not rely on any
56+
* particular ordering. If a given callback is registered multiple times then it will be notified
57+
* multiple times, once per registration.
58+
*
59+
* <p>The thread on which the callback occurs is unspecified; listeners should perform their work
60+
* as quickly as possible and return to avoid blocking any critical work. In particular, the
61+
* listener callbacks should <em>not</em> block or perform long-running operations. Listener
62+
* callbacks can occur concurrently with other callbacks on the same and other listeners.
63+
*
64+
* @param listener the listener to register.
65+
* @return an object that unregisters the given listener via its {@link
66+
* ListenerRegistration#remove} method; only the first unregistration request does anything;
67+
* all subsequent requests do nothing.
68+
*/
69+
@VisibleForTesting
70+
static ListenerRegistration addExistenceFilterMismatchListener(
71+
@NonNull ExistenceFilterMismatchListener listener) {
72+
checkNotNull(listener, "a null listener is not allowed");
73+
74+
Object listenerId = new Object();
75+
synchronized (existenceFilterMismatchListeners) {
76+
existenceFilterMismatchListeners.put(listenerId, listener);
77+
}
78+
79+
return () -> {
80+
synchronized (existenceFilterMismatchListeners) {
81+
existenceFilterMismatchListeners.remove(listenerId);
82+
}
83+
};
84+
}
85+
86+
interface ExistenceFilterMismatchListener {
87+
@AnyThread
88+
void onExistenceFilterMismatch(ExistenceFilterMismatchInfo info);
89+
}
90+
91+
@AutoValue
92+
abstract static class ExistenceFilterMismatchInfo {
93+
94+
static ExistenceFilterMismatchInfo create(
95+
int localCacheCount,
96+
int existenceFilterCount,
97+
@Nullable ExistenceFilterBloomFilterInfo bloomFilter) {
98+
return new AutoValue_WatchChangeAggregatorTestingHooks_ExistenceFilterMismatchInfo(
99+
localCacheCount, existenceFilterCount, bloomFilter);
100+
}
101+
102+
/** Returns the number of documents that matched the query in the local cache. */
103+
abstract int localCacheCount();
104+
105+
/**
106+
* Returns the number of documents that matched the query on the server, as specified in the
107+
* ExistenceFilter message's `count` field.
108+
*/
109+
abstract int existenceFilterCount();
110+
111+
/**
112+
* Returns information about the bloom filter provided by Watch in the ExistenceFilter message's
113+
* `unchangedNames` field. A `null` return value means that Watch did _not_ provide a bloom
114+
* filter.
115+
*/
116+
@Nullable
117+
abstract ExistenceFilterBloomFilterInfo bloomFilter();
118+
119+
static ExistenceFilterMismatchInfo from(
120+
boolean bloomFilterApplied, int localCacheCount, ExistenceFilter existenceFilter) {
121+
return create(
122+
localCacheCount,
123+
existenceFilter.getCount(),
124+
ExistenceFilterBloomFilterInfo.from(bloomFilterApplied, existenceFilter));
125+
}
126+
}
127+
128+
@AutoValue
129+
abstract static class ExistenceFilterBloomFilterInfo {
130+
131+
static ExistenceFilterBloomFilterInfo create(
132+
boolean applied, int hashCount, int bitmapLength, int padding) {
133+
return new AutoValue_WatchChangeAggregatorTestingHooks_ExistenceFilterBloomFilterInfo(
134+
applied, hashCount, bitmapLength, padding);
135+
}
136+
137+
/**
138+
* Returns whether a full requery was averted by using the bloom filter. If false, then
139+
* something happened, such as a false positive, to prevent using the bloom filter to avoid a
140+
* full requery.
141+
*/
142+
abstract boolean applied();
143+
144+
/** Returns the number of hash functions used in the bloom filter. */
145+
abstract int hashCount();
146+
147+
/** Returns the number of bytes in the bloom filter's bitmask. */
148+
abstract int bitmapLength();
149+
150+
/** Returns the number of bits of padding in the last byte of the bloom filter. */
151+
abstract int padding();
152+
153+
static ExistenceFilterBloomFilterInfo from(
154+
boolean bloomFilterApplied, ExistenceFilter existenceFilter) {
155+
BloomFilter unchangedNames = existenceFilter.getUnchangedNames();
156+
if (unchangedNames == null) {
157+
return null;
158+
}
159+
return create(
160+
bloomFilterApplied,
161+
unchangedNames.getHashCount(),
162+
unchangedNames.getBits().getBitmap().size(),
163+
unchangedNames.getBits().getPadding());
164+
}
165+
}
166+
}

0 commit comments

Comments
 (0)