Skip to content

IndexedDB recovery for waitForPendingWrites() #3038

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

Merged
merged 8 commits into from
May 18, 2020

Conversation

schmidt-sebastian
Copy link
Contributor

This:

  • adda a new executeWithIndexedDbRecovery function that aims to centralize the error checking for IndexedDbTransactionErrors. I wasted a lot of time trying to come up with some clean API and clever API and have concluded that the current approach is the most useable (unless we want to keep current duplication).
  • fails the waitForPendingWrites() Promise if we cannot access IndexedDb. Note that we cannot retry here, since the retry might let other writes "slip through" which would then also erroneously be awaited.

Addresses #2755

@google-oss-bot
Copy link
Contributor

google-oss-bot commented May 8, 2020

Binary Size Report

Affected SDKs

  • @firebase/firestore

    Type Base (ac215cf) Head (7b01b97) Diff
    browser 250 kB 250 kB -34 B (-0.0%)
    esm2017 194 kB 194 kB -103 B (-0.1%)
    main 491 kB 491 kB +289 B (+0.1%)
    module 248 kB 248 kB -34 B (-0.0%)
  • @firebase/firestore/memory

    Type Base (ac215cf) Head (7b01b97) Diff
    browser 190 kB 190 kB +34 B (+0.0%)
    esm2017 148 kB 148 kB -42 B (-0.0%)
    main 367 kB 367 kB +304 B (+0.1%)
    module 188 kB 188 kB +34 B (+0.0%)
  • firebase

    Type Base (ac215cf) Head (7b01b97) Diff
    firebase-firestore.js 288 kB 288 kB -33 B (-0.0%)
    firebase-firestore.memory.js 230 kB 230 kB +32 B (+0.0%)
    firebase.js 822 kB 822 kB -33 B (-0.0%)

Test Logs

const query = listener.query;
let firstListen = false;
listen(listener: QueryListener): Promise<void> {
return executeWithIndexedDbRecovery(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't say this pattern is really different from try/catch, except it's hiding that.

What do you think of the more direct:

try {
  const query = listener.query;
  //
} catch (e) {
  const msg = `Initialization of query '${query}' failed: ${e}`;	
  logError(LOG_TAG, msg);
  throwIfUnrecoverable(e);
  listener.onError(new FirestoreError(Code.UNAVAILABLE, msg));
}

As I see it:

  • It's not really more code than what you have here
  • It uses async/await try/cache directly and looks more natural/synchronous-like
  • On the flip side, there aren't a series of callbacks that you have to remember their meaning.
  • There isn't an extra log message about "Internal operation failed"
  • It still abstracts the handling of the IndexedDbTransactionError type

Building on this, you could actually cut the verbosity of each call down by making a checkRecoverable that takes the message, logs it, and then throws if unrecoverable or returns the FirestoreError if it is?

The catch blocks would look like:

const wrapped = checkRecoverable(e, `Initialization of query '${query}' failed: ${e}`);
listener.onError(new FirestoreError(Code.UNAVAILABLE, msg));

The implementation would look like:

checkRecoverable(e: Error, message: string): FirestoreError {
  logError(LOG_TAG, msg);
  if (e.name == ....) {
    return new FirestoreError(unavailable, message);
  } else {
    throw e;
  }
}

WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried really hard to somehow push the try/catch into my helper, but it might have been a bit too mush. I updated the PR to use a helper that returns a FirestoreError if the error is an IndexedDB error, otherwise it throws.

}
}
await this.scheduleGC(localStore);
return executeWithIndexedDbRecovery(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In particular, this change has fairly significantly harmed this method:

  • Why is the extra .then(() => {}) required?
  • Going back to then/catch from async/await is a bummer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is now reverted, but there are some arguments for using Promise chains here:

  • In ES5 code, these are shorter since the function doesn't have to be wrapped in a generator. We haven't really optimized for this in other cases and the overhead might be outweighed by readability.
  • The Promise version doesn't re-schedule the GC task if we have lost the lease. As pointed out in the previous PR, this is also not really an issue though since we stop the scheduler during the next lease refresh.

@wilhuff wilhuff assigned schmidt-sebastian and unassigned wilhuff May 13, 2020
@@ -1144,7 +1144,7 @@ describe('IndexedDb: allowTabSynchronization', () => {
'clientA',
/* multiClient= */ false,
async db => {
db.injectFailures = true;
db.injectFailures = { updateClientMetadataAndTryBecomePrimary: true };
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these tests are now broken in master because I merged two conflicting PRs that were both passing all tests on their own.

Copy link
Contributor

@wilhuff wilhuff left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@wilhuff wilhuff assigned schmidt-sebastian and unassigned wilhuff May 18, 2020
@schmidt-sebastian schmidt-sebastian merged commit fb90059 into master May 18, 2020
@firebase firebase locked and limited conversation to collaborators Jun 18, 2020
@schmidt-sebastian schmidt-sebastian deleted the mrschmidt/pendingwrites branch July 9, 2020 17:44
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants