Skip to content

Commit f551977

Browse files
committed
Review feedback, connected check
1 parent a10a11e commit f551977

File tree

4 files changed

+208
-50
lines changed

4 files changed

+208
-50
lines changed

firebase-database/src/androidTest/java/com/google/firebase/database/QueryTest.java

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3424,8 +3424,7 @@ public void emptyQueryGet() throws DatabaseException, InterruptedException {
34243424
appForDatabaseUrl(IntegrationTestValues.getNamespace(), UUID.randomUUID().toString());
34253425
FirebaseDatabase db = FirebaseDatabase.getInstance(app);
34263426
try {
3427-
Tasks.await(db.getReference("dummy/").setValue(42L));
3428-
assertNull(Tasks.await(db.getReference("null/").get()).getValue());
3427+
assertNull(Tasks.await(db.getReference(UUID.randomUUID().toString()).get()).getValue());
34293428
} catch (ExecutionException e) {
34303429
fail(e.getMessage());
34313430
}
@@ -3436,12 +3435,8 @@ public void offlineQueryGet() throws DatabaseException, InterruptedException {
34363435
FirebaseApp app =
34373436
appForDatabaseUrl(IntegrationTestValues.getNamespace(), UUID.randomUUID().toString());
34383437
FirebaseDatabase db = FirebaseDatabase.getInstance(app);
3438+
db.setLogLevel(Logger.Level.DEBUG);
34393439
DatabaseReference node = db.getReference();
3440-
try {
3441-
Tasks.await(node.setValue(42L));
3442-
} catch (ExecutionException e) {
3443-
fail();
3444-
}
34453440
db.goOffline();
34463441
try {
34473442
Tasks.await(node.get());
@@ -3453,22 +3448,19 @@ public void offlineQueryGet() throws DatabaseException, InterruptedException {
34533448
}
34543449

34553450
@Test
3456-
public void getQueryBasic() throws DatabaseException, InterruptedException {
3451+
public void getQueryBasic() throws DatabaseException, InterruptedException, ExecutionException {
34573452
FirebaseApp app =
34583453
appForDatabaseUrl(IntegrationTestValues.getNamespace(), UUID.randomUUID().toString());
34593454
FirebaseDatabase db = FirebaseDatabase.getInstance(app);
34603455
DatabaseReference node = db.getReference();
3461-
try {
3462-
Tasks.await(node.setValue(42));
3463-
assertEquals(42L, Tasks.await(node.get()).getValue());
3464-
} catch (ExecutionException e) {
3465-
fail(e.getMessage());
3466-
}
3456+
Tasks.await(node.setValue(42));
3457+
assertEquals(42L, Tasks.await(node.get()).getValue());
34673458
}
34683459

34693460
@Test
34703461
public void getQueryCached()
3471-
throws DatabaseException, InterruptedException, TimeoutException, TestFailure {
3462+
throws DatabaseException, InterruptedException, TimeoutException, TestFailure,
3463+
ExecutionException {
34723464
FirebaseApp app =
34733465
appForDatabaseUrl(IntegrationTestValues.getAltNamespace(), UUID.randomUUID().toString());
34743466
FirebaseDatabase db = FirebaseDatabase.getInstance(app);
@@ -3493,15 +3485,13 @@ public void onCancelled(@NonNull DatabaseError error) {}
34933485
try {
34943486
// Since we still have a listener on `ref`, the 42L should be cached here.
34953487
assertEquals(42L, Tasks.await(ref.get()).getValue());
3496-
} catch (ExecutionException e) {
3497-
fail(e.getMessage());
34983488
} finally {
34993489
ref.removeEventListener(listener);
35003490
}
35013491
}
35023492

35033493
@Test
3504-
public void getQuerySkipsCacheWhenOnline()
3494+
public void getRetrievesLatestServerValue()
35053495
throws DatabaseException, InterruptedException, ExecutionException, TestFailure,
35063496
TimeoutException {
35073497
FirebaseApp readerApp =
@@ -3513,13 +3503,13 @@ public void getQuerySkipsCacheWhenOnline()
35133503
DatabaseReference reader = readerDb.getReference();
35143504
DatabaseReference writer = writerDb.getReference();
35153505

3516-
final Semaphore semaphore = new Semaphore(0);
3506+
final Semaphore readerSemaphore = new Semaphore(0);
35173507
reader.addValueEventListener(
35183508
new ValueEventListener() {
35193509
@Override
35203510
public void onDataChange(@NonNull DataSnapshot snapshot) {
35213511
if (snapshot.getValue() != null && snapshot.getValue().equals(42L)) {
3522-
semaphore.release();
3512+
readerSemaphore.release();
35233513
}
35243514
}
35253515

@@ -3529,7 +3519,7 @@ public void onCancelled(@NonNull DatabaseError error) {}
35293519

35303520
WriteFuture write = new WriteFuture(writer, 42L);
35313521
assertNull(write.timedGet());
3532-
IntegrationTestHelpers.waitFor(semaphore);
3522+
IntegrationTestHelpers.waitFor(readerSemaphore);
35333523

35343524
write = new WriteFuture(writer, 43L);
35353525
assertNull(write.timedGet());
@@ -3541,6 +3531,41 @@ public void onCancelled(@NonNull DatabaseError error) {}
35413531
}
35423532
}
35433533

3534+
@Test
3535+
public void getUpdatesPersistenceCacheWhenEnabled()
3536+
throws DatabaseException, InterruptedException, ExecutionException, TestFailure,
3537+
TimeoutException {
3538+
FirebaseApp readerApp =
3539+
appForDatabaseUrl(IntegrationTestValues.getNamespace(), UUID.randomUUID().toString());
3540+
FirebaseApp writerApp =
3541+
appForDatabaseUrl(IntegrationTestValues.getNamespace(), UUID.randomUUID().toString());
3542+
FirebaseDatabase readerDb = FirebaseDatabase.getInstance(readerApp);
3543+
readerDb.setPersistenceEnabled(true);
3544+
FirebaseDatabase writerDb = FirebaseDatabase.getInstance(writerApp);
3545+
DatabaseReference reader = readerDb.getReference();
3546+
DatabaseReference writer = writerDb.getReference();
3547+
3548+
assertNull(new WriteFuture(writer, 42L).timedGet());
3549+
assertEquals(42L, Tasks.await(reader.get()).getValue());
3550+
3551+
readerDb.goOffline();
3552+
3553+
Semaphore semaphore = new Semaphore(0);
3554+
reader.addListenerForSingleValueEvent(
3555+
new ValueEventListener() {
3556+
@Override
3557+
public void onDataChange(@NonNull DataSnapshot snapshot) {
3558+
if (snapshot.getValue() != null && snapshot.getValue().equals(42L)) {
3559+
semaphore.release();
3560+
}
3561+
}
3562+
3563+
@Override
3564+
public void onCancelled(@NonNull DatabaseError error) {}
3565+
});
3566+
IntegrationTestHelpers.waitFor(semaphore);
3567+
}
3568+
35443569
@Test
35453570
public void querySnapshotChildrenRespectDefaultOrdering()
35463571
throws DatabaseException, ExecutionException, TimeoutException, TestFailure,

firebase-database/src/main/java/com/google/firebase/database/connection/PersistentConnectionImpl.java

Lines changed: 120 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,11 @@
2929
import java.util.Iterator;
3030
import java.util.List;
3131
import java.util.Map;
32+
import java.util.concurrent.ConcurrentHashMap;
3233
import java.util.concurrent.ScheduledExecutorService;
3334
import java.util.concurrent.ScheduledFuture;
3435
import java.util.concurrent.TimeUnit;
36+
import java.util.concurrent.atomic.AtomicBoolean;
3537

3638
public class PersistentConnectionImpl implements Connection.Delegate, PersistentConnection {
3739

@@ -111,6 +113,41 @@ public String toString() {
111113
}
112114
}
113115

116+
private static class OutstandingGet {
117+
private Map<String, Object> request;
118+
private ConnectionRequestCallback onComplete;
119+
private String action;
120+
private AtomicBoolean sent;
121+
122+
private OutstandingGet(
123+
String action, Map<String, Object> request, ConnectionRequestCallback onComplete) {
124+
this.action = action;
125+
this.request = request;
126+
this.onComplete = onComplete;
127+
this.sent = new AtomicBoolean(false);
128+
}
129+
130+
private String getAction() {
131+
return action;
132+
}
133+
134+
private ConnectionRequestCallback getOnComplete() {
135+
return onComplete;
136+
}
137+
138+
private Map<String, Object> getRequest() {
139+
return request;
140+
}
141+
142+
private boolean markSent() {
143+
return sent.compareAndSet(false, true);
144+
}
145+
146+
private boolean wasSent() {
147+
return sent.get();
148+
}
149+
}
150+
114151
private static class OutstandingPut {
115152
private String action;
116153
private Map<String, Object> request;
@@ -234,6 +271,7 @@ private enum ConnectionState {
234271
private static final long SUCCESSFUL_CONNECTION_ESTABLISHED_DELAY = 30 * 1000;
235272

236273
private static final long IDLE_TIMEOUT = 60 * 1000;
274+
private static final long GET_CONNECT_TIMEOUT = 3 * 1000;
237275

238276
/** If auth fails repeatedly, we'll assume something is wrong and log a warning / back off. */
239277
private static final long INVALID_AUTH_TOKEN_THRESHOLD = 3;
@@ -253,11 +291,13 @@ private enum ConnectionState {
253291
private Connection realtime;
254292
private ConnectionState connectionState = ConnectionState.Disconnected;
255293
private long writeCounter = 0;
294+
private long readCounter = 0;
256295
private long requestCounter = 0;
257296
private Map<Long, ConnectionRequestCallback> requestCBHash;
258297

259298
private List<OutstandingDisconnect> onDisconnectRequestQueue;
260299
private Map<Long, OutstandingPut> outstandingPuts;
300+
private Map<Long, OutstandingGet> outstandingGets;
261301

262302
private Map<QuerySpec, OutstandingListen> listens;
263303
private String authToken;
@@ -287,6 +327,7 @@ public PersistentConnectionImpl(
287327
this.listens = new HashMap<QuerySpec, OutstandingListen>();
288328
this.requestCBHash = new HashMap<Long, ConnectionRequestCallback>();
289329
this.outstandingPuts = new HashMap<Long, OutstandingPut>();
330+
this.outstandingGets = new ConcurrentHashMap<Long, OutstandingGet>();
290331
this.onDisconnectRequestQueue = new ArrayList<OutstandingDisconnect>();
291332
this.retryHelper =
292333
new RetryHelper.Builder(this.executorService, context.getLogger(), "ConnectionRetryHelper")
@@ -351,15 +392,58 @@ public void listen(
351392
public Task<Object> get(List<String> path, Map<String, Object> queryParams) {
352393
QuerySpec query = new QuerySpec(path, queryParams);
353394
TaskCompletionSource<Object> source = new TaskCompletionSource<>();
354-
Task<Object> task;
355-
if (connected()) {
356-
task = sendGet(query, source);
357-
} else {
358-
source.setException(new Exception("Client is offline"));
359-
task = source.getTask();
395+
396+
long readId = this.readCounter++;
397+
398+
Map<String, Object> request = new HashMap<String, Object>();
399+
request.put(REQUEST_PATH, ConnectionUtils.pathToString(query.path));
400+
request.put(REQUEST_QUERIES, query.queryParams);
401+
402+
outstandingGets.put(
403+
readId,
404+
new OutstandingGet(
405+
REQUEST_ACTION_GET,
406+
request,
407+
new ConnectionRequestCallback() {
408+
@Override
409+
public void onResponse(Map<String, Object> response) {
410+
String status = (String) response.get(REQUEST_STATUS);
411+
if (status.equals("ok")) {
412+
Object body = response.get(SERVER_DATA_UPDATE_BODY);
413+
delegate.onDataUpdate(query.path, body, /*isMerge=*/ false, /*tagNumber=*/ null);
414+
source.setResult(body);
415+
} else {
416+
source.setException(
417+
new Exception((String) response.get(SERVER_DATA_UPDATE_BODY)));
418+
}
419+
}
420+
}));
421+
422+
if (!connected()) {
423+
executorService.schedule(
424+
new Runnable() {
425+
@Override
426+
public void run() {
427+
OutstandingGet get = outstandingGets.get(readId);
428+
if (get == null || !get.markSent()) {
429+
return;
430+
}
431+
if (logger.logsDebug()) {
432+
logger.debug("get " + readId + " timed out waiting for connection");
433+
}
434+
outstandingGets.remove(readId);
435+
source.setException(new Exception("Client is offline"));
436+
}
437+
},
438+
GET_CONNECT_TIMEOUT,
439+
TimeUnit.MILLISECONDS);
440+
}
441+
442+
if (canSendReads()) {
443+
sendGet(readId);
360444
}
361445
doIdleCheck();
362-
return task;
446+
return source.getTask();
363447
}
364448

365449
@Override
@@ -508,6 +592,10 @@ private boolean canSendWrites() {
508592
return connectionState == ConnectionState.Connected;
509593
}
510594

595+
private boolean canSendReads() {
596+
return connectionState == ConnectionState.Connected;
597+
}
598+
511599
@Override
512600
public void onDisconnectMerge(
513601
List<String> path, Map<String, Object> updates, final RequestResultCallback onComplete) {
@@ -979,6 +1067,13 @@ private void restoreState() {
9791067
sendPut(put);
9801068
}
9811069

1070+
if (logger.logsDebug()) logger.debug("Restoring reads.");
1071+
ArrayList<Long> outstandingGetKeys = new ArrayList<Long>(outstandingGets.keySet());
1072+
Collections.sort(outstandingGetKeys);
1073+
for (Long getId : outstandingGetKeys) {
1074+
sendGet(getId);
1075+
}
1076+
9821077
// Restore disconnect operations
9831078
for (OutstandingDisconnect disconnect : onDisconnectRequestQueue) {
9841079
sendOnDisconnect(
@@ -1067,27 +1162,32 @@ public void onResponse(Map<String, Object> response) {
10671162
});
10681163
}
10691164

1070-
private Task<Object> sendGet(final QuerySpec query, TaskCompletionSource<Object> source) {
1071-
Map<String, Object> request = new HashMap<String, Object>();
1072-
request.put(REQUEST_PATH, ConnectionUtils.pathToString(query.path));
1073-
request.put(REQUEST_QUERIES, query.queryParams);
1165+
private void sendGet(final Long readId) {
1166+
hardAssert(canSendReads(), "sendGet called when we can't send gets");
1167+
OutstandingGet get = outstandingGets.get(readId);
1168+
if (!get.markSent()) {
1169+
if (logger.logsDebug()) {
1170+
logger.debug("get" + readId + " already sent or cancelled, ignoring.");
1171+
return;
1172+
}
1173+
}
10741174
sendAction(
1075-
REQUEST_ACTION_GET,
1076-
request,
1175+
get.getAction(),
1176+
get.getRequest(),
10771177
new ConnectionRequestCallback() {
10781178
@Override
10791179
public void onResponse(Map<String, Object> response) {
1080-
String status = (String) response.get(REQUEST_STATUS);
1081-
if (status.equals("ok")) {
1082-
Object body = response.get(SERVER_DATA_UPDATE_BODY);
1083-
delegate.onDataUpdate(query.path, body, /*isMerge=*/ false, /*tagNumber=*/ null);
1084-
source.setResult(body);
1180+
OutstandingGet currentGet = outstandingGets.get(readId);
1181+
if (currentGet == get) {
1182+
outstandingGets.remove(readId);
1183+
get.getOnComplete().onResponse(response);
10851184
} else {
1086-
source.setException(new Exception((String) response.get(SERVER_DATA_UPDATE_BODY)));
1185+
if (logger.logsDebug())
1186+
logger.debug(
1187+
"Ignoring on complete for get " + readId + " because it was removed already.");
10871188
}
10881189
}
10891190
});
1090-
return source.getTask();
10911191
}
10921192

10931193
private void sendListen(final OutstandingListen listen) {

0 commit comments

Comments
 (0)