Skip to content
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

Handle IndexedDB errors in unsubscribe callback #3162

Merged
merged 32 commits into from
Jul 9, 2020

Conversation

schmidt-sebastian
Copy link
Contributor

Addresses one of the three problems I found during randomized testing.

Addresses #2755

@google-oss-bot
Copy link
Contributor

google-oss-bot commented Jun 4, 2020

Binary Size Report

Affected SDKs

  • @firebase/app

    Type Base (2ab5a17) Head (a80e5ec) Diff
    browser 11.1 kB 11.1 kB +3 B (+0.0%)
    lite 9.10 kB 9.11 kB +3 B (+0.0%)
    main 10.1 kB 10.1 kB +3 B (+0.0%)
    module 11.0 kB 11.0 kB +3 B (+0.0%)
    react-native 9.86 kB 9.87 kB +3 B (+0.0%)
  • @firebase/auth

    Type Base (2ab5a17) Head (a80e5ec) Diff
    main 177 kB 177 kB +137 B (+0.1%)
    module 177 kB 177 kB +137 B (+0.1%)
  • @firebase/database

    Type Base (2ab5a17) Head (a80e5ec) Diff
    browser 268 kB 268 kB +19 B (+0.0%)
    main 269 kB 269 kB +19 B (+0.0%)
    module 267 kB 267 kB +19 B (+0.0%)
  • @firebase/firestore

    Type Base (2ab5a17) Head (a80e5ec) Diff
    browser 248 kB 247 kB -870 B (-0.4%)
    esm2017 194 kB 194 kB +136 B (+0.1%)
    main 493 kB 495 kB +2.09 kB (+0.4%)
    module 246 kB 245 kB -927 B (-0.4%)
    react-native ? 194 kB ? (?)
  • @firebase/firestore/exp

    Type Base (2ab5a17) Head (a80e5ec) Diff
    main 282 kB 400 kB +118 kB (+41.9%)
  • @firebase/firestore/lite

    Type Base (2ab5a17) Head (a80e5ec) Diff
    main 141 kB 124 kB -16.5 kB (-11.7%)
  • @firebase/firestore/memory

    Type Base (2ab5a17) Head (a80e5ec) Diff
    browser 186 kB 185 kB -1.20 kB (-0.6%)
    esm2017 145 kB 145 kB -29 B (-0.0%)
    main 363 kB 364 kB +1.26 kB (+0.3%)
    module 184 kB 183 kB -1.20 kB (-0.7%)
    react-native ? 145 kB ? (?)
  • @firebase/functions

    Type Base (2ab5a17) Head (a80e5ec) Diff
    browser 9.17 kB 9.64 kB +464 B (+5.1%)
    esm2017 7.01 kB 7.30 kB +285 B (+4.1%)
    main 9.21 kB 9.67 kB +464 B (+5.0%)
    module 8.99 kB 9.45 kB +452 B (+5.0%)
  • @firebase/logger

    Type Base (2ab5a17) Head (a80e5ec) Diff
    main 5.14 kB 5.14 kB +3 B (+0.1%)
    module 4.83 kB 4.83 kB +3 B (+0.1%)
  • @firebase/messaging

    Type Base (2ab5a17) Head (a80e5ec) Diff
    esm2017 23.2 kB 23.2 kB +35 B (+0.2%)
    main 31.4 kB 31.4 kB +37 B (+0.1%)
    module 30.9 kB 30.9 kB +37 B (+0.1%)
  • @firebase/performance

    Type Base (2ab5a17) Head (a80e5ec) Diff
    browser 26.7 kB 26.7 kB +2 B (+0.0%)
    main 26.7 kB 26.7 kB +2 B (+0.0%)
    module 26.4 kB 26.4 kB +2 B (+0.0%)
  • @firebase/remote-config

    Type Base (2ab5a17) Head (a80e5ec) Diff
    browser 23.1 kB 23.1 kB +2 B (+0.0%)
    main 23.1 kB 23.1 kB +2 B (+0.0%)
    module 22.7 kB 22.7 kB +2 B (+0.0%)
  • @firebase/storage

    Type Base (2ab5a17) Head (a80e5ec) Diff
    main 62.7 kB 62.7 kB +16 B (+0.0%)
    module 62.5 kB 62.5 kB +16 B (+0.0%)
  • firebase

    Type Base (2ab5a17) Head (a80e5ec) Diff
    firebase-auth.js 173 kB 174 kB +133 B (+0.1%)
    firebase-firestore.js 286 kB 285 kB -1.19 kB (-0.4%)
    firebase-firestore.memory.js 226 kB 224 kB -1.48 kB (-0.7%)
    firebase-functions.js 9.60 kB 9.84 kB +242 B (+2.5%)
    firebase-messaging.js 39.1 kB 39.2 kB +43 B (+0.1%)
    firebase.js 820 kB 819 kB -759 B (-0.1%)

Test Logs

Addresses one of the three problems I found during randomized testing.
try {
await this.eventManager.unlisten(eventEmitter!);
const currentEventEmitter = this.queryListeners.get(query);
// Before removing the listener, verify that the query hasn't been
Copy link
Contributor

Choose a reason for hiding this comment

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

I must be missing some context for this comment. If a user attempts to unlisten, that fails, gets in the retry queue, and then they listen to the same query while we're retrying, doesn't the event manager have two listeners for that query? The first query will stil succeed in unlistening.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

queryListeners is a map of active listeners that the Spec tests care about. This code here was my first attempt at making sure that we don't remove a query from the Spec tests view if another operation has re-registered it. I was able to simplify this though by removing the query directly and outside of the queue, which should have no effect on the spec tests since this step would have previously executed in a very similar order.

@@ -404,7 +404,7 @@ export class FirestoreClient {
if (this.clientTerminated) {
return;
}
this.asyncQueue.enqueueAndForget(() => {
this.asyncQueue.enqueueRetryable(() => {
Copy link
Contributor

@wilhuff wilhuff Jun 5, 2020

Choose a reason for hiding this comment

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

Why is this necessary? I thought that if we failed we tore down the streams? At that point haven't we effectively unlistened anyway? All that's missing is updating our in-memory bookkeeping to indicate that we don't want to restart the target when IndexedDB becomes available again.

One other concern is that if IndexedDB is failing we've likely been put in the background and may not come back for a while--possibly more than the 30 minutes for which a resume token means free query resumption. If we were asleep that long the user would be charged for the query anew--for a query they were trying to stop listening to. If all we're losing is the resume token and the sequence numbers for exact GC ordering, it seems better to let the unlisten succeed despite failing to write.

So in the primary tab, if we fail trying to save metadata data about unlistening that's not really a big deal. Can a secondary tab fail trying to tell the primary that it wants to unlisten? In that case we should probably retry.

If this squares with your understanding, it seems like this line may actually be correct: we do want to retry unlistens but only when it's the secondary tab asking the primary tab to do it. The part that's missing is that primary tabs should handle IndexedDB errors and succeed.

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 code path calls into LocalStore to invoke "Release Target" and then calls RemoteStore. We could change the plumbing here to drive this from RemoteStore, in which case RemoteStore could go offline and do its own retry. That sounds a like a fair bit of code churn that may lead to some unexpected side effects - the mapping between the lastListen state in EventManager and the active list of targets in SyncEngine/LocalStore may go out of sync.

One easy thing we could do here is just swallow IndexedDB failures if "Release Target" fails, which would even remove some of the newly added error handling in RemoteStore. Do you think that's worth exploring?

Copy link
Contributor

Choose a reason for hiding this comment

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

just swallow IndexedDB failures if "Release Target" fails

This is what I was suggesting: releasing a target in the LocalStore is just about making sure the final updates are made for GC metadata (and the in-memory tracking of the target). It may be that even LocalStore itself may need to change so that even if the transaction fails it will remove the target from its own set of live things.

I'm not sure this undoes the error handling in RemoteStore, since it's really only unsubscribe can work this way.

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 updated the PR to ignore errors in "Release target". Unfortunately, since a failed Target can generate a "fake" Remote Event (for Limbo resolutions), the code path that calls into this from RemoteStore still has to use the IndexedDB fallback.

@wilhuff wilhuff assigned schmidt-sebastian and unassigned wilhuff Jun 5, 2020
@changeset-bot
Copy link

changeset-bot bot commented Jun 26, 2020

🦋 Changeset is good to go

Latest commit: 1ba0fe1

We got this.

This PR includes changesets to release 8 packages
Name Type
firebase Patch
@firebase/firestore Patch
@firebase/testing Patch
firebase-browserify-test Patch
firebase-package-typings-test Patch
firebase-messaging-selenium-test Patch
firebase-typescript-test Patch
firebase-webpack-test Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@schmidt-sebastian schmidt-sebastian changed the title Handle IndexedDb errors in unsubscribe callback Handle IndexedDB errors in unsubscribe callback Jun 26, 2020
@wilhuff wilhuff assigned schmidt-sebastian and unassigned wilhuff Jun 29, 2020
zwu52 and others added 12 commits June 30, 2020 09:57
* Clean up unused FCM secret

* Update breezy-queens-give.md

Co-authored-by: Feiyang <feiyangc@google.com>
* change to ts

* use the correct file name

* remove -exp from types packages when releasing

* werid hack to make exp release build

* skip tests in exp release

* do not run test

* revert changes made for testing

* remove extraneous build target

* Create warm-suns-dream.md
* Make onSnapshot work with rxjs observers

* Create thin-ligers-fold.md

* Update thin-ligers-fold.md
* Update dependency firebase-tools to v8

* Create yellow-lamps-greet.md

Co-authored-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: Feiyang <feiyangc@google.com>
* point to the typing file in functions-exp

* use app-exp as the service name in functions

* Create chilled-beers-chew.md
* Update dependency eslint to v7

* Create two-weeks-thank.md

Co-authored-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: Feiyang <feiyangc@google.com>
@googlebot
Copy link

We found a Contributor License Agreement for you (the sender of this pull request), but were unable to determine that you authored the commits in this PR. Maybe you used a different email address in the git commits than was used to sign the CLA? If someone else authored these commits, then please add them to this pull request and have them confirm that they're okay with them being contributed to Google. If there are co-authors, make sure they're formatted properly.

In order to pass this check, please resolve this problem and then comment@googlebot I fixed it... If the bot doesn't comment, it means it doesn't think anything has changed.

ℹ️ Googlers: Go here for more info.

Copy link
Contributor Author

@schmidt-sebastian schmidt-sebastian left a comment

Choose a reason for hiding this comment

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

PR updated. A lot of the previous changes have been removed and it makes more sense to compare against master than against the previous version.

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

} catch (e) {
if (isIndexedDbTransactionError(e)) {
// If `releaseTarget` fails, we did not advance the sequence number of
// the target. While the target might be deleted earlier than it
Copy link
Contributor

@wilhuff wilhuff Jul 8, 2020

Choose a reason for hiding this comment

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

Optional: since the degree of "earlier" is very slight (we already update the sequence number when you start listening and during updates so this is only the final update on unlisten), consider softening the wording here or giving more context. Suggestion:

All releaseTarget does is record the final metadata state for the target, but we've been recording this periodically during target activity. If we lose this write this could cause a very slight difference in the order of target deletion during GC, but we don't define exact LRU semantics anyway so this is acceptable.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. Thanks for the rewrite :)

@schmidt-sebastian schmidt-sebastian merged commit 5a35536 into master Jul 9, 2020
@schmidt-sebastian schmidt-sebastian deleted the mrschmidt/unsubscribe branch July 9, 2020 17:44
This was referenced Jul 15, 2020
@firebase firebase locked and limited conversation to collaborators Aug 9, 2020
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.

None yet

6 participants