-
Notifications
You must be signed in to change notification settings - Fork 615
awaitPendingWrites initial revision #689
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should work as is - I just have a bunch of comments on styling and comment. The concept is sound though! Thank you.
assertTrue(!pendingWrite.isComplete()); | ||
assertTrue(!awaitsPendingWrites2.isComplete()); | ||
|
||
waitFor(firestore.enableNetwork()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically, you don't need to wait for enableNetwork()
or the pending write.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
*/ | ||
public Task<Void> awaitPendingWrites() { | ||
this.verifyNotShutdown(); | ||
if (!remoteStore.canUseNetwork()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should run on the async queue:
- It accesses internal state.
enableNetwork()
anddisableNetwork()
run on the async queue, so for this warning to show up these operations would need to resolve before the user callsawaitPendingWrites
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the catch.
if (!remoteStore.canUseNetwork()) { | ||
Logger.warn( | ||
LOG_TAG, | ||
"Network is disabled, the Task created to wait for all writes getting" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can simplify this:
"The network is disabled. The task returned by awaitPendingWrites() will not complete until the network is enabled."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
public Task<Void> awaitPendingWrites() { | ||
this.verifyNotShutdown(); | ||
if (!remoteStore.canUseNetwork()) { | ||
Logger.warn( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am a little torn on whether this is a "warning" - it is certainly not a problem in the SDK itself, and technically not even for users of the SDK. It prevents unexpected behavior - so I would advocate to lower the log level here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
firebase-firestore/src/main/java/com/google/firebase/firestore/core/SyncEngine.java
Show resolved
Hide resolved
@@ -187,6 +187,11 @@ public MutationBatch getNextMutationBatchAfterBatchId(int batchId) { | |||
return queue.size() > index ? queue.get(index) : null; | |||
} | |||
|
|||
@Override | |||
public int getLargestUnacknowledgedBatchId() { | |||
return queue.size() == 0 ? 0 : nextBatchId - 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Google style guide generally recommends using isEmpty(), since it always runs in O(1).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@@ -249,6 +249,16 @@ public MutationBatch getNextMutationBatchAfterBatchId(int batchId) { | |||
.firstValue(row -> decodeInlineMutationBatch(row.getInt(0), row.getBlob(1))); | |||
} | |||
|
|||
@Override | |||
public int getLargestUnacknowledgedBatchId() { | |||
if (isEmpty()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: In case the mutation queue is not empty, you are running a very similar query twice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
if (isEmpty()) { | ||
return 0; | ||
} | ||
return db.query("SELECT MAX(batch_id) FROM mutations " + "WHERE uid = ?") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: s/" + "//
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@@ -1143,4 +1143,21 @@ public void testHandlesPatchMutationWithTransformThenRemoteEvent() { | |||
assertChanged(doc("foo/bar", 1, map("sum", 1), Document.DocumentState.LOCAL_MUTATIONS)); | |||
assertContains(doc("foo/bar", 1, map("sum", 1), Document.DocumentState.LOCAL_MUTATIONS)); | |||
} | |||
|
|||
@Test | |||
public void testGetHighestUnacknowledgedBatchIdReturnsExpectedResult() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Maybe remove ReturnsExpectedResult
, since it doesn't add too much to the descriptiveness.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
* | ||
* <p>Both acceptance and rejection count as server acknowledgement. | ||
* | ||
* @return A {@link Task} which resolves when all pending writes are acknowledged by the server. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We likely want to update the comment a bit:
- We should use concepts that are more widely known ("server acknowledgement for all pending writes" -> "when all writes have been sent to the backend").
- We should mention how this behaves during a user change.
- We should mention that this works even after client restart.
If you want to, we can tweak this comment in the chatroom.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. I made some change, but not enough. Especially i don't understand your comment about client restart, and how this still works with a restart. Let's chat tmr.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One of the reasons that this is a heavily requested feature is that it offers users the ability to monitor the state of writes that were issued during an earlier app session.If a user issues a write and then closes the app before the write is committed, it is not very straightforward to figure out whether the write made it to the backend once the app is opened again. drain/awaitPendingWrites
solves this problem nicely.
Just for reference: This can be done today by either issuing a dummy write and waiting for its completion (one would have to know that we commit writes sequentially) or by issuing gets for all documents that were written and checking the hasPendingWrites
flags.
Task<Void> pendingWrite = documentReference.set(data); | ||
Task<Void> awaitsPendingWrites2 = firestore.waitForPendingWrites(); | ||
|
||
// `awaitsPendingWrites1` is complete immediately because there is no pending writes at |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/is complete/completes/
s/there is/there are/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
if (!remoteStore.canUseNetwork()) { | ||
Logger.debug( | ||
TAG, | ||
"The network is disabled. The task returned by 'awaitPendingWrites()' will not complete until the network is enabled."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Please manually wrap to 100 characters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
firebase-firestore/src/main/java/com/google/firebase/firestore/core/SyncEngine.java
Show resolved
Hide resolved
} | ||
|
||
if (pendingWritesCallbacks.containsKey(largestPendingBatchId)) { | ||
pendingWritesCallbacks.get(largestPendingBatchId).add(userTask); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's fine to stick with the multi-map.
@@ -282,6 +282,14 @@ public LocalWriteResult writeLocally(List<Mutation> mutations) { | |||
}); | |||
} | |||
|
|||
/** | |||
* Returns the largest (latest) batch id in mutation queue that is pending server response. | |||
* Returns 0 if the queue is empty. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are checking for this constant in registerPendingWritesTask()
.
If you use -1 (or rather the existing concept of MutationBatch.UNKNOWN) it is apparent that this is a special sentinel value. -1 indicates right away that it is not a valid batch ID.
0, on the other hand, could very well be a valid batch ID. It is not obvious that we decided (or rather that Proto3 decided for us) that the first valid batch ID should be 1.
@@ -565,6 +626,7 @@ public void handleCredentialChange(User user) { | |||
// Notify local store and emit any resulting events from swapping out the mutation queue. | |||
ImmutableSortedMap<DocumentKey, MaybeDocument> changes = localStore.handleUserChange(user); | |||
emitNewSnapsAndNotifyLocalStore(changes, /*remoteEvent=*/ null); | |||
failOutstandingPendingWritesAwaitingTasks(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally, we would want to fail these Tasks before we raise the new snapshots for the newly signed in user. That way, we handle the callbacks for the old user before invoking any callbacks that affect the new user.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
* | ||
* <p>Both acceptance and rejection count as server acknowledgement. | ||
* | ||
* @return A {@link Task} which resolves when all pending writes are acknowledged by the server. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One of the reasons that this is a heavily requested feature is that it offers users the ability to monitor the state of writes that were issued during an earlier app session.If a user issues a write and then closes the app before the write is committed, it is not very straightforward to figure out whether the write made it to the backend once the app is opened again. drain/awaitPendingWrites
solves this problem nicely.
Just for reference: This can be done today by either issuing a dummy write and waiting for its completion (one would have to know that we commit writes sequentially) or by issuing gets for all documents that were written and checking the hasPendingWrites
flags.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One comment about the order of callbacks and one final nudge to use MutationBatch.UNKNOWN to indicate that there is nothing to wait for.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't a full review just the public comments.
@@ -362,6 +362,16 @@ public WriteBatch batch() { | |||
return shutdownInternal(); | |||
} | |||
|
|||
/** | |||
* Wait until all pending writes existed at the time of calling are sent to the backend. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A bunch of comments on this:
- Phrase comments with an implicit "this method" at the beginning
- missing "that" between "writes existed"
- This doesn't just wait until writes are sent, but also waits for them to complete
[This method ...] Waits until all pending writes that existed at the time of calling are have been successfully written to the server. The task will will not resolve while the client is offline.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
/** | ||
* Wait until all pending writes existed at the time of calling are sent to the backend. | ||
* | ||
* @return A {@link Task} which resolves when all pending writes are sent to the backend. If there |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't @link
items that are already in the signature of the method. Use @link
for anything related that's not part of the signature.
Use {@code Task}
to highlight that it's a type without the extra visual noise that comes from an actual link.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
* there is a Firebase user change, the returned {@code Task} will resolve to an exception. If | ||
* the client is offline, the returned {@code Task} will not resolve. | ||
*/ | ||
Task<Void> waitForPendingWrites() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still think we should move this comment to a doc (it could be the API proposal doc) and do some collaborative editing:
pending writes that existed at the time of calling
could be clearer.written to the server
is an unusual way of phrasing the right thing.- I think we should move the comment on the user change to a separate paragraph and move it out of the
@return
section - Tasks on Android don't resolve, they complete.
@mikelehen usually has a ton of good feedback on these comments, and his feedback tends to be much more valuable than what I can provide.
Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The API proposal doc would be a good place to collaborate on this.
Note that the "written to the server" language is used in the iOS documentation already which is why I suggested it: https://github.com/firebase/firebase-ios-sdk/blob/master/Firestore/Source/Public/FIRDocumentReference.h#L117. I'm definitely open to finding a better way to succinctly describe this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using Sebastian's version now, looks great.
It will resolve in that case. That sounds right to me. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great! Thank you.
} | ||
|
||
private MockCredentialsProvider() { | ||
super(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: This super() call is not needed, is it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not.
listener.onValue(user); | ||
} | ||
|
||
private Listener<User> listener; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Move to top of class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
* <p>The returned Task completes immediately if there are no outstanding writes. Otherwise, the | ||
* Task waits for all previously issued writes (including those written in a previous app | ||
* session), but it does not wait for writes that were added after the method is called. If you | ||
* wish to wait for additional writes, you have to call `waitForPendingWrites()` again. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use {@code}
for all mentions of waitForPendingWrites.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
if (!remoteStore.canUseNetwork()) { | ||
Logger.debug( | ||
TAG, | ||
"The network is disabled. The task returned by 'awaitPendingWrites()' will not" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Super nit: We usually leave the space at the end of the line. This helps to align comments with more than two lines.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
} | ||
} | ||
|
||
/** Resolves tasks waiting for this batch id to get acknowledged by server, if there is any. */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/is/are/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
assertEquals(2, localStore.getHighestUnacknowledgedBatchId()); | ||
|
||
rejectMutation(); | ||
assertEquals(-1, localStore.getHighestUnacknowledgedBatchId()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/-1/MutationBatch.UNKNOWN/
Added 'waitForPendingWrites' to firestore.