Description
A getDataWithCompletionBlock: whose request has already been sent when the websocket disconnects never calls its completion block. The call neither returns data nor fails.
From FirebaseDatabase/Sources/Core/FPersistentConnection.m (12.8.0, unchanged on main):
sendGet: sets get.sent = YES and registers the response callback in requestCBHash.
onDisconnect:withReason: clears requestCBHash, which drops that callback. It neither fails outstanding gets nor resets their sent flag.
- On reconnect,
restoreState calls sendGet: for every entry in outstandingGets, but sendGet: begins with if ([get sent]) { return; }. The get is never resent, and its completion never fires.
The connect timeout in getDataAtPath:withParams:withCallback: is armed only when the client is disconnected at call time, so it does not cover this case.
Android resends such gets: PersistentConnectionImpl.restoreState resends every outstanding get. (Its own early return for a get already sent sits inside if (logger.logsDebug()), so with debug logging off the get falls through and is resent. That looks accidental, but it gives the behaviour you'd want.)
Proposed fix
Mark outstanding gets unsent on disconnect so restoreState resends them, matching Android:
diff --git a/FirebaseDatabase/Sources/Core/FPersistentConnection.m b/FirebaseDatabase/Sources/Core/FPersistentConnection.m
index da2820d..40dfaa7 100644
--- a/FirebaseDatabase/Sources/Core/FPersistentConnection.m
+++ b/FirebaseDatabase/Sources/Core/FPersistentConnection.m
@@ -400,6 +400,15 @@ - (void)onDisconnect:(FConnection *)fconnection
self.realtime = nil;
[self cancelSentTransactions];
[self.requestCBHash removeAllObjects];
+ // the line above drops the response callback of every
+ // get already sent, and restoreState's sendGet: skips a get marked sent,
+ // so it never completed. Mark them unsent so they are resent on reconnect.
+ // Android resends such gets too in production, though only because
+ // PersistentConnectionImpl.sendGet's early return sits inside
+ // `if (logger.logsDebug())`.
+ for (FOutstandingGet *get in [self.outstandingGets allValues]) {
+ get.sent = NO;
+ }
self.unackedListensCount = 0;
if ([self shouldReconnect]) {
NSTimeInterval timeSinceLastConnectSucceeded =
Reproducing the issue
- Call
getDataWithCompletionBlock: on a location without an active listener.
- Drop the connection after the request is written but before the response arrives. A proxy that holds the response makes this deterministic.
- Restore the connection. The completion block is never called.
Firebase SDK Version
12.8.0 (same code on main)
Firebase Product(s)
Database
Description
A
getDataWithCompletionBlock:whose request has already been sent when the websocket disconnects never calls its completion block. The call neither returns data nor fails.From
FirebaseDatabase/Sources/Core/FPersistentConnection.m(12.8.0, unchanged onmain):sendGet:setsget.sent = YESand registers the response callback inrequestCBHash.onDisconnect:withReason:clearsrequestCBHash, which drops that callback. It neither fails outstanding gets nor resets theirsentflag.restoreStatecallssendGet:for every entry inoutstandingGets, butsendGet:begins withif ([get sent]) { return; }. The get is never resent, and its completion never fires.The connect timeout in
getDataAtPath:withParams:withCallback:is armed only when the client is disconnected at call time, so it does not cover this case.Android resends such gets:
PersistentConnectionImpl.restoreStateresends every outstanding get. (Its own early return for a get already sent sits insideif (logger.logsDebug()), so with debug logging off the get falls through and is resent. That looks accidental, but it gives the behaviour you'd want.)Proposed fix
Mark outstanding gets unsent on disconnect so
restoreStateresends them, matching Android:Reproducing the issue
getDataWithCompletionBlock:on a location without an active listener.Firebase SDK Version
12.8.0 (same code on
main)Firebase Product(s)
Database