@@ -33,7 +33,7 @@ import { RemoteEvent, TargetChange } from '../remote/remote_event';
33
33
import { RemoteStore } from '../remote/remote_store' ;
34
34
import { RemoteSyncer } from '../remote/remote_syncer' ;
35
35
import { assert , fail } from '../util/assert' ;
36
- import { Code , FirestoreError } from '../util/error' ;
36
+ import { FirestoreError } from '../util/error' ;
37
37
import * as log from '../util/log' ;
38
38
import { AnyJs , primitiveComparator } from '../util/misc' ;
39
39
import { ObjectMap } from '../util/obj_map' ;
@@ -57,7 +57,8 @@ import {
57
57
AddedLimboDocument ,
58
58
LimboDocumentChange ,
59
59
RemovedLimboDocument ,
60
- View , ViewChange ,
60
+ View ,
61
+ ViewChange ,
61
62
ViewDocumentChanges
62
63
} from './view' ;
63
64
import { ViewSnapshot } from './view_snapshot' ;
@@ -68,6 +69,7 @@ import {
68
69
import { ClientId , SharedClientState } from '../local/shared_client_state' ;
69
70
import { SortedSet } from '../util/sorted_set' ;
70
71
import * as objUtils from '../util/obj' ;
72
+ import { isPrimaryLeaseLostError } from '../local/indexeddb_persistence' ;
71
73
72
74
const LOG_TAG = 'SyncEngine' ;
73
75
@@ -208,7 +210,7 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
208
210
queryData . targetId
209
211
) ;
210
212
targetId = queryData . targetId ;
211
- viewSnapshot = await this . initializeViewAndComputeInitialSnapshot (
213
+ viewSnapshot = await this . initializeViewAndComputeSnapshot (
212
214
queryData ,
213
215
status === 'current'
214
216
) ;
@@ -221,7 +223,11 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
221
223
return targetId ;
222
224
}
223
225
224
- private initializeViewAndComputeInitialSnapshot (
226
+ /**
227
+ * Registers a view for a previously unknown query and computes its initial
228
+ * snapshot.
229
+ */
230
+ private initializeViewAndComputeSnapshot (
225
231
queryData : QueryData ,
226
232
current : boolean
227
233
) : Promise < ViewSnapshot > {
@@ -265,15 +271,32 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
265
271
}
266
272
267
273
/**
268
- * Reconcile the list of synced documents in the local views with those from
269
- * persistence.
274
+ * Reconcile the list of synced documents in an existing view with those
275
+ * from persistence.
270
276
*/
271
277
// PORTING NOTE: Multi-tab only.
272
- private async synchronizeLocalView ( query :Query , targetId : TargetId ) : Promise < ViewChange > {
273
- return this . localStore . executeQuery ( query ) . then ( docs => {
274
- const queryView = this . queryViewsByTarget [ targetId ] ;
275
- assert ( ! ! queryView , 'Expected queryView to be defined' ) ;
276
- return queryView . view . synchronizeWithRemoteDocuments ( docs ) ;
278
+ private synchronizeViewAndComputeSnapshot (
279
+ query : Query ,
280
+ targetId : TargetId
281
+ ) : Promise < ViewChange > {
282
+ return this . localStore . executeQuery ( query ) . then ( async docs => {
283
+ return this . localStore
284
+ . remoteDocumentKeys ( targetId )
285
+ . then ( async remoteKeys => {
286
+ const queryView = this . queryViewsByTarget [ targetId ] ;
287
+ assert ( ! ! queryView , 'Cannot reconcile missing view' ) ;
288
+ const viewChange = queryView . view . synchronizeWithRemoteDocuments (
289
+ docs ,
290
+ remoteKeys ,
291
+ /* resetLimboDocuments= */ this . isPrimary ,
292
+ /* resetCurrent= */ this . isPrimary
293
+ ) ;
294
+ await this . updateTrackedLimbos (
295
+ queryView . targetId ,
296
+ viewChange . limboChanges
297
+ ) ;
298
+ return viewChange ;
299
+ } ) ;
277
300
} ) ;
278
301
}
279
302
@@ -763,7 +786,7 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
763
786
private async tryRecoverFromPrimaryLeaseLoss (
764
787
err : FirestoreError
765
788
) : Promise < void > {
766
- if ( err . code === Code . FAILED_PRECONDITION ) {
789
+ if ( isPrimaryLeaseLostError ( err ) ) {
767
790
log . debug (
768
791
LOG_TAG ,
769
792
'Unexpectedly lost primary lease, reverting to secondary'
@@ -802,7 +825,6 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
802
825
async applyPrimaryState ( isPrimary : boolean ) : Promise < void > {
803
826
if ( isPrimary === true && this . isPrimary !== true ) {
804
827
this . isPrimary = true ;
805
-
806
828
await this . remoteStore . applyPrimaryState ( true ) ;
807
829
808
830
// Secondary tabs only maintain Views for their local listeners and the
@@ -811,52 +833,78 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
811
833
// server considers to be in the target). So when a secondary becomes
812
834
// primary, we need to need to make sure that all views for all targets
813
835
// match the state on disk.
814
- let p = Promise . resolve ( ) ;
815
836
const activeTargets = this . sharedClientState . getAllActiveQueryTargets ( ) ;
816
- activeTargets . forEach ( targetId => {
817
- p = p . then ( async ( ) => {
818
- let queryData ;
819
- const query = await this . localStore . getQueryForTarget ( targetId ) ;
820
- if ( this . queryViewsByTarget [ targetId ] === undefined ) {
821
- // For queries that never executed on this client, we need to
822
- // allocate the query in LocalStore and initialize a new View.
823
- queryData = await this . localStore . allocateQuery ( query ) ;
824
- await this . initializeViewAndComputeInitialSnapshot (
825
- queryData ,
826
- false
827
- ) ;
828
- } else {
829
- // For queries that have a local View, we need to update their state
830
- // in LocalStore (as the resume token and the snapshot version
831
- // might have changed) and reconcile their views with the persisted
832
- // state (the list of syncedDocuments may have gotten out of sync).
833
- await this . localStore . releaseQuery ( query , true ) ;
834
- queryData = await this . localStore . allocateQuery ( query ) ;
835
- await this . synchronizeLocalView ( query , targetId ) ;
836
- }
837
- this . remoteStore . listen ( queryData ) ;
838
- } ) ;
839
- } ) ;
840
- await p ;
837
+ const activeQueries = await this . synchronizeLocalViews (
838
+ activeTargets . toArray ( )
839
+ ) ;
840
+ for ( const queryData of activeQueries ) {
841
+ this . remoteStore . listen ( queryData ) ;
842
+ }
841
843
} else if ( isPrimary === false && this . isPrimary !== false ) {
842
844
this . isPrimary = false ;
845
+ const activeQueries = await this . synchronizeLocalViews (
846
+ objUtils . indices ( this . queryViewsByTarget )
847
+ ) ;
848
+ this . resetLimboDocuments ( ) ;
849
+ for ( const queryData of activeQueries ) {
850
+ // TODO(multitab): Remove query views for non-local queries.
851
+ this . remoteStore . unlisten ( queryData . targetId ) ;
852
+ }
853
+ await this . remoteStore . applyPrimaryState ( false ) ;
854
+ }
855
+ }
856
+
857
+ // PORTING NOTE: Multi-tab only.
858
+ private resetLimboDocuments ( ) : void {
859
+ objUtils . forEachNumber ( this . limboKeysByTarget , targetId => {
860
+ this . remoteStore . unlisten ( targetId ) ;
861
+ } ) ;
862
+ this . limboKeysByTarget = [ ] ;
863
+ this . limboTargetsByKey = new SortedMap < DocumentKey , TargetId > (
864
+ DocumentKey . comparator
865
+ ) ;
866
+ }
843
867
844
- let p = Promise . resolve ( ) ;
845
- objUtils . forEachNumber ( this . queryViewsByTarget , targetId => {
846
- p = p . then ( async ( ) => {
847
- let queryView = this . queryViewsByTarget [ targetId ] ;
848
- // TODO(multitab): Remove query views for non-local queries.
849
- const viewChange = await this . synchronizeLocalView ( queryView . query , targetId ) ;
850
- // const viewChange = queryView.view.clearLimboDocuments();
868
+ /**
869
+ * Reconcile the list of synced documents in the local views with those from
870
+ * persistence.
871
+ */
872
+ // PORTING NOTE: Multi-tab only.
873
+ private synchronizeLocalViews ( targets : TargetId [ ] ) : Promise < QueryData [ ] > {
874
+ let p = Promise . resolve ( ) ;
875
+ const activeQueries : QueryData [ ] = [ ] ;
876
+ for ( const targetId of targets ) {
877
+ p = p . then ( async ( ) => {
878
+ let queryData ;
879
+ const query = await this . localStore . getQueryForTarget ( targetId ) ;
880
+ if ( this . queryViewsByTarget [ targetId ] === undefined ) {
881
+ assert (
882
+ this . isPrimary ,
883
+ 'A secondary tab should never have an active query without an active view.'
884
+ ) ;
885
+ // For queries that never executed on this client, we need to
886
+ // allocate the query in LocalStore and initialize a new View.
887
+ queryData = await this . localStore . allocateQuery ( query ) ;
888
+ await this . initializeViewAndComputeSnapshot ( queryData , false ) ;
889
+ } else {
890
+ // For queries that have a local View, we need to update their state
891
+ // in LocalStore (as the resume token and the snapshot version
892
+ // might have changed) and reconcile their views with the persisted
893
+ // state (the list of syncedDocuments may have gotten out of sync).
894
+ await this . localStore . releaseQuery ( query , true ) ;
895
+ queryData = await this . localStore . allocateQuery ( query ) ;
896
+ const viewChange = await this . synchronizeViewAndComputeSnapshot (
897
+ query ,
898
+ queryData . targetId
899
+ ) ;
851
900
if ( viewChange . snapshot ) {
852
901
this . syncEngineListener ! . onWatchChange ( [ viewChange . snapshot ] ) ;
853
902
}
854
- } ) ;
855
- this . remoteStore . unlisten ( targetId ) ;
903
+ }
904
+ activeQueries . push ( queryData ) ;
856
905
} ) ;
857
-
858
- await this . remoteStore . applyPrimaryState ( false ) ;
859
906
}
907
+ return p . then ( ( ) => activeQueries ) ;
860
908
}
861
909
862
910
// PORTING NOTE: Multi-tab only
@@ -871,13 +919,13 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
871
919
error ?: FirestoreError
872
920
) : Promise < void > {
873
921
if ( this . isPrimary ) {
874
- // If we receive a target state notification via Web Storage , we are
922
+ // If we receive a target state notification via WebStorage , we are
875
923
// either already secondary or another tab has taken the primary lease.
876
924
log . debug (
877
925
LOG_TAG ,
878
- 'Unexpectedly received query state notification when already primary. Ignoring .'
926
+ 'Ignoring unexpected query state notification as primary.'
879
927
) ;
880
- return ;
928
+ return ;
881
929
}
882
930
883
931
if ( this . queryViewsByTarget [ targetId ] ) {
@@ -927,7 +975,7 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
927
975
const query = await this . localStore . getQueryForTarget ( targetId ) ;
928
976
assert ( ! ! query , `Query data for active target ${ targetId } not found` ) ;
929
977
const queryData = await this . localStore . allocateQuery ( query ) ;
930
- await this . initializeViewAndComputeInitialSnapshot (
978
+ await this . initializeViewAndComputeSnapshot (
931
979
queryData ,
932
980
/*current=*/ false
933
981
) ;
0 commit comments