Skip to content

Commit f8d82bf

Browse files
Revert: IndexedDB recovery for handleUserChange
Reverts #3087 Fixes #3179
1 parent 1e3721c commit f8d82bf

File tree

6 files changed

+66
-83
lines changed

6 files changed

+66
-83
lines changed

packages/firestore/src/core/firestore_client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ export class FirestoreClient {
179179
persistenceResult
180180
).then(initializationDone.resolve, initializationDone.reject);
181181
} else {
182-
this.asyncQueue.enqueueRetryable(() => {
182+
this.asyncQueue.enqueueAndForget(() => {
183183
return this.handleCredentialChange(user);
184184
});
185185
}

packages/firestore/src/core/sync_engine.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -862,15 +862,15 @@ export class SyncEngine implements RemoteSyncer {
862862

863863
async handleCredentialChange(user: User): Promise<void> {
864864
const userChanged = !this.currentUser.isEqual(user);
865+
this.currentUser = user;
865866

866867
if (userChanged) {
867-
const result = await this.localStore.handleUserChange(user);
868-
this.currentUser = user;
869-
870868
// Fails tasks waiting for pending writes requested by previous user.
871869
this.rejectOutstandingPendingWritesCallbacks(
872870
"'waitForPendingWrites' promise is rejected due to a user change."
873871
);
872+
873+
const result = await this.localStore.handleUserChange(user);
874874
// TODO(b/114226417): Consider calling this only in the primary tab.
875875
this.sharedClientState.handleUserChange(
876876
user,

packages/firestore/src/util/async_queue.ts

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -323,32 +323,29 @@ export class AsyncQueue {
323323
* operations were retried successfully.
324324
*/
325325
enqueueRetryable(op: () => Promise<void>): void {
326-
this.verifyNotFailed();
326+
const resultAfterRetries = new Deferred<void>();
327+
328+
const retryAttempt = async (): Promise<void> => {
329+
try {
330+
await op();
331+
resultAfterRetries.resolve();
332+
this.backoff.reset();
333+
} catch (e) {
334+
if (isIndexedDbTransactionError(e)) {
335+
logDebug(LOG_TAG, 'Operation failed with retryable error: ' + e);
336+
this.backoff.backoffAndRun(retryAttempt);
337+
} else {
338+
resultAfterRetries.resolve();
339+
throw e; // Failure will be handled by AsyncQueue
340+
}
341+
}
342+
};
327343

328-
if (this._isShuttingDown) {
329-
return;
330-
}
344+
this.retryableTail = this.retryableTail.then(
345+
() => resultAfterRetries.promise
346+
);
331347

332-
this.retryableTail = this.retryableTail.then(() => {
333-
const deferred = new Deferred<void>();
334-
const retryingOp = async (): Promise<void> => {
335-
try {
336-
await op();
337-
deferred.resolve();
338-
this.backoff.reset();
339-
} catch (e) {
340-
if (isIndexedDbTransactionError(e)) {
341-
logDebug(LOG_TAG, 'Operation failed with retryable error: ' + e);
342-
this.backoff.backoffAndRun(retryingOp);
343-
} else {
344-
deferred.resolve();
345-
throw e; // Failure will be handled by AsyncQueue
346-
}
347-
}
348-
};
349-
this.enqueueAndForget(retryingOp);
350-
return deferred.promise;
351-
});
348+
this.enqueueAndForget(retryAttempt);
352349
}
353350

354351
private enqueueInternal<T extends unknown>(op: () => Promise<T>): Promise<T> {

packages/firestore/test/unit/specs/recovery_spec.test.ts

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -709,41 +709,4 @@ describeSpec('Persistence Recovery', ['no-ios', 'no-android'], () => {
709709
removed: [doc1]
710710
});
711711
});
712-
713-
specTest(
714-
'User change handles transaction failures (with recovery)',
715-
['durable-persistence'],
716-
() => {
717-
const query = Query.atPath(path('collection'));
718-
const doc1 = doc(
719-
'collection/key1',
720-
0,
721-
{ foo: 'a' },
722-
{ hasLocalMutations: true }
723-
);
724-
return spec()
725-
.changeUser('user1')
726-
.userSets('collection/key1', { foo: 'a' })
727-
.userListens(query)
728-
.expectEvents(query, {
729-
added: [doc1],
730-
fromCache: true,
731-
hasPendingWrites: true
732-
})
733-
.failDatabaseTransactions('Handle user change')
734-
.changeUser('user2')
735-
.recoverDatabase()
736-
.runTimer(TimerId.AsyncQueueRetry)
737-
.expectEvents(query, { removed: [doc1], fromCache: true })
738-
.failDatabaseTransactions('Handle user change')
739-
.changeUser('user1')
740-
.recoverDatabase()
741-
.runTimer(TimerId.AsyncQueueRetry)
742-
.expectEvents(query, {
743-
added: [doc1],
744-
fromCache: true,
745-
hasPendingWrites: true
746-
});
747-
}
748-
);
749712
});

packages/firestore/test/unit/specs/spec_test_runner.ts

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -714,19 +714,11 @@ abstract class TestRunner {
714714
return Promise.resolve();
715715
}
716716

717-
private async doChangeUser(user: string | null): Promise<void> {
717+
private doChangeUser(user: string | null): Promise<void> {
718718
this.user = new User(user);
719-
const deferred = new Deferred<void>();
720-
await this.queue.enqueueRetryable(async () => {
721-
try {
722-
await this.syncEngine.handleCredentialChange(this.user);
723-
} finally {
724-
// Resolve the deferred Promise even if the operation failed. This allows
725-
// the spec tests to manually retry the failed user change.
726-
deferred.resolve();
727-
}
728-
});
729-
return deferred.promise;
719+
return this.queue.enqueue(async () =>
720+
this.syncEngine.handleCredentialChange(this.user)
721+
);
730722
}
731723

732724
private async doFailDatabase(

packages/firestore/test/unit/util/async_queue.test.ts

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ describe('AsyncQueue', () => {
286286
expect(completedSteps).to.deep.equal([1, 1]);
287287
});
288288

289-
it('Retries retryable operations in order', async () => {
289+
it.only('Retries retryable operations in order', async () => {
290290
const queue = new AsyncQueue();
291291
const completedSteps: number[] = [];
292292
const doStep = (n: number): void => {
@@ -296,21 +296,52 @@ describe('AsyncQueue', () => {
296296
const blockingPromise = new Deferred<void>();
297297

298298
queue.enqueueRetryable(async () => {
299-
doStep(1);
300-
if (completedSteps.length > 1) {
299+
if (completedSteps.length == 0) {
300+
doStep(1);
301+
throw new IndexedDbTransactionError(
302+
new Error('Simulated retryable error')
303+
);
301304
} else {
305+
doStep(3);
306+
}
307+
});
308+
queue.enqueueRetryable(async () => {
309+
if (completedSteps.length == 0) {
310+
doStep(2);
302311
throw new IndexedDbTransactionError(
303312
new Error('Simulated retryable error')
304313
);
314+
} else {
315+
doStep(4);
316+
blockingPromise.resolve();
305317
}
306318
});
319+
320+
await blockingPromise.promise;
321+
expect(completedSteps).to.deep.equal([1, 2, 3, 4]);
322+
});
323+
324+
it('Retryable and non-retryable operations maintain order', async () => {
325+
const queue = new AsyncQueue();
326+
const completedSteps: number[] = [];
327+
const doStep = (n: number): void => {
328+
completedSteps.push(n);
329+
};
330+
307331
queue.enqueueRetryable(async () => {
332+
doStep(1);
333+
});
334+
queue.enqueueAndForget(async () => {
308335
doStep(2);
309-
blockingPromise.resolve();
336+
});
337+
queue.enqueueRetryable(async () => {
338+
doStep(3);
339+
});
340+
await queue.enqueue(async () => {
341+
doStep(4);
310342
});
311343

312-
await blockingPromise.promise;
313-
expect(completedSteps).to.deep.equal([1, 1, 2]);
344+
expect(completedSteps).to.deep.equal([1, 2, 3, 4]);
314345
});
315346

316347
it('Can drain (non-delayed) operations', async () => {

0 commit comments

Comments
 (0)