Skip to content

Handle IndexedDB errors in unsubscribe callback #3162

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 32 commits into from
Jul 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
9686261
Handle IndexedDb errors in unsubscribe callback
schmidt-sebastian Jun 4, 2020
131c6cd
Merge branch 'master' into mrschmidt/unsubscribe
schmidt-sebastian Jun 26, 2020
024c2e7
Review
schmidt-sebastian Jun 26, 2020
0e00088
Create smart-cars-doubt.md
schmidt-sebastian Jun 26, 2020
1f56feb
Update smart-cars-doubt.md
schmidt-sebastian Jun 26, 2020
182d100
WIP
schmidt-sebastian Jun 29, 2020
b45bb4f
Merge branch 'mrschmidt/unsubscribe' of github.com:firebase/firebase-…
schmidt-sebastian Jun 29, 2020
91e915c
update codeowner to allow approving changeset changes (#3308)
Feiyang1 Jun 26, 2020
9a5358f
Add getDocFromCache() & getDocFromServer() (#3285)
schmidt-sebastian Jun 26, 2020
f124462
Remove makeConstructorPrivate (#3309)
schmidt-sebastian Jun 26, 2020
657def9
Reduce size impact of Target/TargetImpl pattern (#3272)
schmidt-sebastian Jun 26, 2020
f334261
Update dependency chalk to v4 (#3298)
renovate[bot] Jun 26, 2020
282cc41
Fix updateToken (#3312)
Feiyang1 Jun 27, 2020
09672eb
Add getQuery(), getQueryFromCache() and getQueryFromServer() (#3294)
schmidt-sebastian Jun 27, 2020
837cc1b
Add setDoc, updateDoc, deleteDoc and addDoc (#3306)
schmidt-sebastian Jun 27, 2020
3947788
Clean up unused FCM secret (#3311)
zwu52 Jun 27, 2020
4b8b439
Add set() overrides to lite sdk (#3291)
Jun 27, 2020
726697f
Add writeBatch(), WriteBatch (#3307)
schmidt-sebastian Jun 29, 2020
7cdfa36
Add terminate() and snapshotEqual() to firestore-exp (#3313)
schmidt-sebastian Jun 29, 2020
d3ca86c
Make exp release script work again (#3301)
Feiyang1 Jun 29, 2020
5ae98a4
Make onSnapshot work with rxjs observers (#3318)
Feiyang1 Jun 29, 2020
7bcb1ff
Update dependency firebase-tools to v8 (#3300)
renovate[bot] Jun 29, 2020
d88058e
Point functions-exp to the right typing file (#3322)
Feiyang1 Jun 29, 2020
6960241
Publish firebase@exp 0.800.2
Feiyang1 Jun 29, 2020
5b26aed
Update dependency eslint to v7 (#3299)
renovate[bot] Jun 30, 2020
0d2b01d
Add Snapshot Listeners to firestore-exp (#3317)
schmidt-sebastian Jun 30, 2020
7042a71
Ignore failures
schmidt-sebastian Jun 30, 2020
01abb6b
Merge branch 'master' into mrschmidt/unsubscribe
schmidt-sebastian Jun 30, 2020
0bdd73e
Merge branch 'master' into mrschmidt/unsubscribe
schmidt-sebastian Jun 30, 2020
af7e24d
Shorter comment
schmidt-sebastian Jun 30, 2020
db788d1
Fix test
schmidt-sebastian Jun 30, 2020
1ba0fe1
Review
schmidt-sebastian Jul 9, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/smart-cars-doubt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"firebase": patch
"@firebase/firestore": patch
---

The SDK no longer crashes if an IndexedDB failure occurs when unsubscribing from a Query.
37 changes: 25 additions & 12 deletions packages/firestore/src/local/local_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -911,7 +911,7 @@ class LocalStoreImpl implements LocalStore {
}
}

releaseTarget(
async releaseTarget(
targetId: number,
keepPersistedTargetData: boolean
): Promise<void> {
Expand All @@ -922,21 +922,34 @@ class LocalStoreImpl implements LocalStore {
);

const mode = keepPersistedTargetData ? 'readwrite' : 'readwrite-primary';
return this.persistence
.runTransaction('Release target', mode, txn => {
if (!keepPersistedTargetData) {

try {
if (!keepPersistedTargetData) {
await this.persistence.runTransaction('Release target', mode, txn => {
return this.persistence.referenceDelegate.removeTarget(
txn,
targetData!
);
} else {
return PersistencePromise.resolve();
}
})
.then(() => {
this.targetDataByTarget = this.targetDataByTarget.remove(targetId);
this.targetIdByTarget.delete(targetData!.target);
});
});
}
} catch (e) {
if (isIndexedDbTransactionError(e)) {
// 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 so this is acceptable.
logDebug(
LOG_TAG,
`Failed to update sequence numbers for target ${targetId}: ${e}`
);
} else {
throw e;
}
}

this.targetDataByTarget = this.targetDataByTarget.remove(targetId);
this.targetIdByTarget.delete(targetData!.target);
}

executeQuery(
Expand Down
69 changes: 55 additions & 14 deletions packages/firestore/test/unit/specs/recovery_spec.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -569,7 +569,10 @@ describeSpec('Persistence Recovery', ['no-ios', 'no-android'], () => {
);
});

specTest('Recovers when watch rejection cannot be persisted', [], () => {
specTest('Handles rejections that cannot be persisted', [], () => {
// This test verifies that the client ignores failures during the
// 'Release target' transaction.

const doc1Query = Query.atPath(path('collection/key1'));
const doc2Query = Query.atPath(path('collection/key2'));
const doc1a = doc('collection/key1', 1000, { foo: 'a' });
Expand All @@ -593,25 +596,22 @@ describeSpec('Persistence Recovery', ['no-ios', 'no-android'], () => {
doc1Query,
new RpcError(Code.PERMISSION_DENIED, 'Simulated target error')
)
// `failDatabaseTransactions()` causes us to go offline.
.expectActiveTargets()
.expectEvents(doc1Query, { fromCache: true })
.expectEvents(doc2Query, { fromCache: true })
.expectEvents(doc1Query, { errorCode: Code.PERMISSION_DENIED })
.recoverDatabase()
.runTimer(TimerId.AsyncQueueRetry)
.expectActiveTargets(
{ query: doc1Query, resumeToken: 'resume-token-1000' },
{ query: doc2Query, resumeToken: 'resume-token-2000' }
)
.watchAcksFull(doc1Query, 3000)
.expectEvents(doc1Query, {})
.watchRemoves(
doc2Query,
new RpcError(Code.PERMISSION_DENIED, 'Simulated target error')
)
.expectEvents(doc2Query, { errorCode: Code.PERMISSION_DENIED })
.watchSends({ affects: [doc1Query] }, doc1b)
.watchSnapshots(4000)
// Verify that `doc1Query` can be listened to again. Note that the
// resume token is slightly outdated since we failed to persist the
// target update during the release.
.userListens(doc1Query, 'resume-token-1000')
.expectEvents(doc1Query, {
added: [doc1a],
fromCache: true
})
.watchAcksFull(doc1Query, 4000, doc1b)
.expectEvents(doc1Query, {
modified: [doc1b]
})
Expand Down Expand Up @@ -797,4 +797,45 @@ describeSpec('Persistence Recovery', ['no-ios', 'no-android'], () => {
);
}
);

specTest('Unlisten succeeds when target release fails', [], () => {
const query = Query.atPath(path('collection'));
const doc1 = doc('collection/key1', 1, { foo: 'a' });
return spec()
.userListens(query)
.watchAcksFull(query, 1000, doc1)
.expectEvents(query, {
added: [doc1]
})
.failDatabaseTransactions('Release target')
.userUnlistens(query)
.expectActiveTargets();
});

specTest('Can re-listen to query when unlisten fails', [], () => {
const query = Query.atPath(path('collection'));
const doc1 = doc('collection/key1', 1, { foo: 'a' });
const doc2 = doc('collection/key2', 2, { foo: 'b' });
return spec()
.withGCEnabled(false)
.userListens(query)
.watchAcksFull(query, 1000, doc1)
.expectEvents(query, {
added: [doc1]
})
.failDatabaseTransactions('Release target')
.userUnlistens(query)
.watchRemoves(query)
.recoverDatabase()
.userListens(query, 'resume-token-1000')
.expectEvents(query, {
added: [doc1],
fromCache: true
})
.watchAcksFull(query, 2000, doc2)
.expectEvents(query, {
added: [doc2]
})
.userUnlistens(query);
});
});