Skip to content

Commit

Permalink
Take RemoteStore offline during user change
Browse files Browse the repository at this point in the history
  • Loading branch information
schmidt-sebastian committed Jun 9, 2020
1 parent 1e3721c commit 00c9136
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 52 deletions.
13 changes: 3 additions & 10 deletions packages/firestore/src/core/firestore_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,9 @@ export class FirestoreClient {
persistenceResult
).then(initializationDone.resolve, initializationDone.reject);
} else {
this.asyncQueue.enqueueRetryable(() => {
return this.handleCredentialChange(user);
});
this.asyncQueue.enqueueAndForget(() =>
this.remoteStore.handleCredentialChange(user)
);
}
});

Expand Down Expand Up @@ -339,13 +339,6 @@ export class FirestoreClient {
}
}

private handleCredentialChange(user: User): Promise<void> {
this.asyncQueue.verifyOperationInProgress();

logDebug(LOG_TAG, 'Credential Changed. Current user: ' + user.uid);
return this.syncEngine.handleCredentialChange(user);
}

/** Disables the network connection. Pending operations will not complete. */
disableNetwork(): Promise<void> {
this.verifyNotTerminated();
Expand Down
6 changes: 3 additions & 3 deletions packages/firestore/src/core/sync_engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -860,10 +860,12 @@ export class SyncEngine implements RemoteSyncer {
);
}

async handleCredentialChange(user: User): Promise<void> {
async handleUserChange(user: User): Promise<void> {
const userChanged = !this.currentUser.isEqual(user);

if (userChanged) {
logDebug(LOG_TAG, 'User change. New user:', user.toKey());

const result = await this.localStore.handleUserChange(user);
this.currentUser = user;

Expand All @@ -879,8 +881,6 @@ export class SyncEngine implements RemoteSyncer {
);
await this.emitNewSnapsAndNotifyLocalStore(result.affectedDocuments);
}

await this.remoteStore.handleCredentialChange();
}

enableNetwork(): Promise<void> {
Expand Down
25 changes: 20 additions & 5 deletions packages/firestore/src/remote/remote_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import {
} from './watch_change';
import { ByteString } from '../util/byte_string';
import { isIndexedDbTransactionError } from '../local/simple_db';
import { User } from '../auth/user';

const LOG_TAG = 'RemoteStore';

Expand Down Expand Up @@ -756,13 +757,27 @@ export class RemoteStore implements TargetMetadataProvider {
await this.enableNetwork();
}

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

if (this.canUseNetwork()) {
// Tear down and re-create our network streams. This will ensure we get a fresh auth token
// for the new user and re-fill the write pipeline with new mutations from the LocalStore
// (since mutations are per-user).
// Tear down and re-create our network streams. This will ensure we get a
// fresh auth token for the new user and re-fill the write pipeline with
// new mutations from the LocalStore (since mutations are per-user).
logDebug(LOG_TAG, 'RemoteStore restarting streams for new credential');
await this.restartNetwork();

this.networkEnabled = false;
await this.disableNetworkInternal();
this.onlineStateTracker.set(OnlineState.Unknown);

await this.executeWithRecovery(async () => {
await this.syncEngine.handleUserChange(user);
await this.enableNetwork();
});
} else {
await this.executeWithRecovery(() =>
this.syncEngine.handleUserChange(user)
);
}
}

Expand Down
7 changes: 7 additions & 0 deletions packages/firestore/src/remote/remote_syncer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { DocumentKeySet } from '../model/collections';
import { MutationBatchResult } from '../model/mutation_batch';
import { FirestoreError } from '../util/error';
import { RemoteEvent } from './remote_event';
import { User } from '../auth/user';

/**
* An interface that describes the actions the RemoteStore needs to perform on
Expand Down Expand Up @@ -65,4 +66,10 @@ export interface RemoteSyncer {
* the last snapshot.
*/
getRemoteKeysForTarget(targetId: TargetId): DocumentKeySet;

/**
* Updates all local state to match the pending mutations for the given user.
* May be called repeatedly for the same user.
*/
handleUserChange(user: User): Promise<void>;
}
54 changes: 31 additions & 23 deletions packages/firestore/test/unit/specs/recovery_spec.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -721,29 +721,37 @@ describeSpec('Persistence Recovery', ['no-ios', 'no-android'], () => {
{ foo: 'a' },
{ hasLocalMutations: true }
);
return spec()
.changeUser('user1')
.userSets('collection/key1', { foo: 'a' })
.userListens(query)
.expectEvents(query, {
added: [doc1],
fromCache: true,
hasPendingWrites: true
})
.failDatabaseTransactions('Handle user change')
.changeUser('user2')
.recoverDatabase()
.runTimer(TimerId.AsyncQueueRetry)
.expectEvents(query, { removed: [doc1], fromCache: true })
.failDatabaseTransactions('Handle user change')
.changeUser('user1')
.recoverDatabase()
.runTimer(TimerId.AsyncQueueRetry)
.expectEvents(query, {
added: [doc1],
fromCache: true,
hasPendingWrites: true
});
return (
spec()
.changeUser('user1')
.userSets('collection/key1', { foo: 'a' })
.userListens(query)
.expectEvents(query, {
added: [doc1],
fromCache: true,
hasPendingWrites: true
})
.failDatabaseTransactions('Handle user change')
.changeUser('user2')
// The network is offline due to the failed user change
.expectActiveTargets()
.recoverDatabase()
.runTimer(TimerId.AsyncQueueRetry)
.expectActiveTargets({ query })
.expectEvents(query, { removed: [doc1], fromCache: true })
.failDatabaseTransactions('Handle user change')
.changeUser('user1')
// The network is offline due to the failed user change
.expectActiveTargets()
.recoverDatabase()
.runTimer(TimerId.AsyncQueueRetry)
.expectActiveTargets({ query })
.expectEvents(query, {
added: [doc1],
fromCache: true,
hasPendingWrites: true
})
);
}
);
});
14 changes: 3 additions & 11 deletions packages/firestore/test/unit/specs/spec_test_runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -716,17 +716,9 @@ abstract class TestRunner {

private async doChangeUser(user: string | null): Promise<void> {
this.user = new User(user);
const deferred = new Deferred<void>();
await this.queue.enqueueRetryable(async () => {
try {
await this.syncEngine.handleCredentialChange(this.user);
} finally {
// Resolve the deferred Promise even if the operation failed. This allows
// the spec tests to manually retry the failed user change.
deferred.resolve();
}
});
return deferred.promise;
return this.queue.enqueue(() =>
this.remoteStore.handleCredentialChange(this.user)
);
}

private async doFailDatabase(
Expand Down

0 comments on commit 00c9136

Please sign in to comment.