Skip to content

Commit 97ec8e9

Browse files
WIP
1 parent 00c9136 commit 97ec8e9

File tree

4 files changed

+78
-47
lines changed

4 files changed

+78
-47
lines changed

packages/firestore/src/core/firestore_client.ts

+1-1
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.enqueueAndForget(() =>
182+
this.asyncQueue.enqueueRetryable(() =>
183183
this.remoteStore.handleCredentialChange(user)
184184
);
185185
}

packages/firestore/src/remote/remote_store.ts

+34-44
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,15 @@ const LOG_TAG = 'RemoteStore';
6262
// TODO(b/35853402): Negotiate this with the stream.
6363
const MAX_PENDING_WRITES = 10;
6464

65+
const enum OfflineCause {
66+
UserDisabled,
67+
IndexedDbFailed,
68+
IsSecondary,
69+
CredentialChange,
70+
Shutdown,
71+
ConnectivityChange
72+
}
73+
6574
/**
6675
* RemoteStore - An interface to remotely stored data, basically providing a
6776
* wrapper around the Datastore that is more reliable for the rest of the
@@ -117,20 +126,7 @@ export class RemoteStore implements TargetMetadataProvider {
117126
private writeStream: PersistentWriteStream;
118127
private watchChangeAggregator: WatchChangeAggregator | null = null;
119128

120-
/**
121-
* Set to true by enableNetwork() and false by disableNetwork() and indicates
122-
* the user-preferred network state.
123-
*/
124-
private networkEnabled = false;
125-
126-
private isPrimary = false;
127-
128-
/**
129-
* When set to `true`, the network was taken offline due to an IndexedDB
130-
* failure. The state is flipped to `false` when access becomes available
131-
* again.
132-
*/
133-
private indexedDbFailed = false;
129+
private offlineCauses = new Set<OfflineCause>();
134130

135131
private onlineStateTracker: OnlineStateTracker;
136132

@@ -194,7 +190,7 @@ export class RemoteStore implements TargetMetadataProvider {
194190

195191
/** Re-enables the network. Idempotent. */
196192
enableNetwork(): Promise<void> {
197-
this.networkEnabled = true;
193+
this.offlineCauses.delete(OfflineCause.UserDisabled);
198194
return this.enableNetworkInternal();
199195
}
200196

@@ -216,7 +212,7 @@ export class RemoteStore implements TargetMetadataProvider {
216212
* enableNetwork().
217213
*/
218214
async disableNetwork(): Promise<void> {
219-
this.networkEnabled = false;
215+
this.offlineCauses.add(OfflineCause.UserDisabled);
220216
await this.disableNetworkInternal();
221217

222218
// Set the OnlineState to Offline so get()s return from cache, etc.
@@ -240,7 +236,7 @@ export class RemoteStore implements TargetMetadataProvider {
240236

241237
async shutdown(): Promise<void> {
242238
logDebug(LOG_TAG, 'RemoteStore shutting down.');
243-
this.networkEnabled = false;
239+
this.offlineCauses.add(OfflineCause.Shutdown);
244240
await this.disableNetworkInternal();
245241
this.connectivityMonitor.shutdown();
246242

@@ -349,7 +345,7 @@ export class RemoteStore implements TargetMetadataProvider {
349345
}
350346

351347
canUseNetwork(): boolean {
352-
return !this.indexedDbFailed && this.isPrimary && this.networkEnabled;
348+
return this.offlineCauses.size === 0;
353349
}
354350

355351
private cleanUpWatchStreamState(): void {
@@ -457,10 +453,10 @@ export class RemoteStore implements TargetMetadataProvider {
457453
): Promise<void> {
458454
if (isIndexedDbTransactionError(e)) {
459455
debugAssert(
460-
!this.indexedDbFailed,
456+
!this.offlineCauses.has(OfflineCause.IndexedDbFailed),
461457
'Unexpected network event when IndexedDB was marked failed.'
462458
);
463-
this.indexedDbFailed = true;
459+
this.offlineCauses.add(OfflineCause.IndexedDbFailed);
464460

465461
// Disable network and raise offline snapshots
466462
await this.disableNetworkInternal();
@@ -477,7 +473,7 @@ export class RemoteStore implements TargetMetadataProvider {
477473
this.asyncQueue.enqueueRetryable(async () => {
478474
logDebug(LOG_TAG, 'Retrying IndexedDB access');
479475
await op!();
480-
this.indexedDbFailed = false;
476+
this.offlineCauses.delete(OfflineCause.IndexedDbFailed);
481477
await this.enableNetworkInternal();
482478
});
483479
} else {
@@ -751,45 +747,39 @@ export class RemoteStore implements TargetMetadataProvider {
751747
}
752748

753749
private async restartNetwork(): Promise<void> {
754-
this.networkEnabled = false;
750+
this.offlineCauses.add(OfflineCause.ConnectivityChange);
755751
await this.disableNetworkInternal();
756752
this.onlineStateTracker.set(OnlineState.Unknown);
757-
await this.enableNetwork();
753+
this.offlineCauses.delete(OfflineCause.ConnectivityChange);
754+
await this.enableNetworkInternal();
758755
}
759756

760757
async handleCredentialChange(user: User): Promise<void> {
761758
this.asyncQueue.verifyOperationInProgress();
762759

763-
if (this.canUseNetwork()) {
764-
// Tear down and re-create our network streams. This will ensure we get a
765-
// fresh auth token for the new user and re-fill the write pipeline with
766-
// new mutations from the LocalStore (since mutations are per-user).
767-
logDebug(LOG_TAG, 'RemoteStore restarting streams for new credential');
760+
// Tear down and re-create our network streams. This will ensure we get a
761+
// fresh auth token for the new user and re-fill the write pipeline with
762+
// new mutations from the LocalStore (since mutations are per-user).
763+
logDebug(LOG_TAG, 'RemoteStore received new credential');
764+
this.offlineCauses.add(OfflineCause.CredentialChange);
768765

769-
this.networkEnabled = false;
770-
await this.disableNetworkInternal();
771-
this.onlineStateTracker.set(OnlineState.Unknown);
766+
await this.disableNetworkInternal();
767+
this.onlineStateTracker.set(OnlineState.Unknown);
768+
await this.syncEngine.handleUserChange(user);
772769

773-
await this.executeWithRecovery(async () => {
774-
await this.syncEngine.handleUserChange(user);
775-
await this.enableNetwork();
776-
});
777-
} else {
778-
await this.executeWithRecovery(() =>
779-
this.syncEngine.handleUserChange(user)
780-
);
781-
}
770+
this.offlineCauses.delete(OfflineCause.CredentialChange);
771+
await this.enableNetworkInternal();
782772
}
783773

784774
/**
785775
* Toggles the network state when the client gains or loses its primary lease.
786776
*/
787777
async applyPrimaryState(isPrimary: boolean): Promise<void> {
788-
this.isPrimary = isPrimary;
789-
790-
if (isPrimary && this.networkEnabled) {
791-
await this.enableNetwork();
778+
if (isPrimary) {
779+
this.offlineCauses.delete(OfflineCause.IsSecondary);
780+
await this.enableNetworkInternal();
792781
} else if (!isPrimary) {
782+
this.offlineCauses.add(OfflineCause.IsSecondary);
793783
await this.disableNetworkInternal();
794784
this.onlineStateTracker.set(OnlineState.Unknown);
795785
}

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

+41
Original file line numberDiff line numberDiff line change
@@ -754,4 +754,45 @@ describeSpec('Persistence Recovery', ['no-ios', 'no-android'], () => {
754754
);
755755
}
756756
);
757+
758+
specTest(
759+
'Multiple user changes during transaction failure (with recovery)',
760+
['durable-persistence'],
761+
() => {
762+
const query = Query.atPath(path('collection'));
763+
const doc1 = doc(
764+
'collection/key1',
765+
0,
766+
{ foo: 'a' },
767+
{ hasLocalMutations: true }
768+
);
769+
return (
770+
spec()
771+
.changeUser('user1')
772+
.userSets('collection/key1', { foo: 'a' })
773+
.userListens(query)
774+
.expectEvents(query, {
775+
added: [doc1],
776+
fromCache: true,
777+
hasPendingWrites: true
778+
})
779+
.failDatabaseTransactions('Handle user change')
780+
.changeUser('user2')
781+
// The network is offline due to the failed user change
782+
.expectActiveTargets()
783+
.changeUser('user1')
784+
.recoverDatabase()
785+
.runTimer(TimerId.AsyncQueueRetry)
786+
.expectActiveTargets({ query })
787+
// We are now user 2
788+
.expectEvents(query, { removed: [doc1], fromCache: true })
789+
// We are now user 1
790+
.expectEvents(query, {
791+
added: [doc1],
792+
fromCache: true,
793+
hasPendingWrites: true
794+
})
795+
);
796+
}
797+
);
757798
});

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -716,8 +716,8 @@ abstract class TestRunner {
716716

717717
private async doChangeUser(user: string | null): Promise<void> {
718718
this.user = new User(user);
719-
return this.queue.enqueue(() =>
720-
this.remoteStore.handleCredentialChange(this.user)
719+
this.queue.enqueueRetryable(() =>
720+
this.remoteStore.handleCredentialChange(new User(user))
721721
);
722722
}
723723

0 commit comments

Comments
 (0)