@@ -289,11 +289,11 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
289
289
290
290
if ( ! targetRemainsActive ) {
291
291
this . remoteStore . unlisten ( queryView . targetId ) ;
292
- await this . removeAndCleanupQuery ( queryView ) ;
293
- return this . localStore
292
+ await this . localStore
294
293
. releaseQuery ( query , /*keepPersistedQueryData=*/ false )
294
+ . then ( ( ) => this . removeAndCleanupQuery ( queryView ) )
295
295
. then ( ( ) => this . localStore . collectGarbage ( ) )
296
- . catch ( err => this . tryRecoverClient ( err ) ) ;
296
+ . catch ( err => this . tryRecoverFromPrimaryLeaseLoss ( err ) ) ;
297
297
}
298
298
} else {
299
299
await this . removeAndCleanupQuery ( queryView ) ;
@@ -399,7 +399,7 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
399
399
. then ( changes => {
400
400
return this . emitNewSnapsAndNotifyLocalStore ( changes , remoteEvent ) ;
401
401
} )
402
- . catch ( err => this . tryRecoverClient ( err ) ) ;
402
+ . catch ( err => this . tryRecoverFromPrimaryLeaseLoss ( err ) ) ;
403
403
}
404
404
405
405
/**
@@ -469,11 +469,10 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
469
469
} else {
470
470
const queryView = this . queryViewsByTarget [ targetId ] ;
471
471
assert ( ! ! queryView , 'Unknown targetId: ' + targetId ) ;
472
- await this . removeAndCleanupQuery ( queryView ) ;
473
- await this . localStore . releaseQuery (
474
- queryView . query ,
475
- /* keepPersistedQueryData */ false
476
- ) ;
472
+ await this . localStore
473
+ . releaseQuery ( queryView . query , /* keepPersistedQueryData */ false )
474
+ . then ( ( ) => this . removeAndCleanupQuery ( queryView ) )
475
+ . catch ( err => this . tryRecoverFromPrimaryLeaseLoss ( err ) ) ;
477
476
this . errorHandler ! ( queryView . query , err ) ;
478
477
}
479
478
}
@@ -505,10 +504,16 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
505
504
// connection is disabled.
506
505
await this . remoteStore . fillWritePipeline ( ) ;
507
506
} else if ( batchState === 'acknowledged' || batchState === 'rejected' ) {
508
- // If we receive a notification of an `acknowledged` or `rejected` batch
509
- // via Web Storage, we are either already secondary or another tab has
510
- // taken the primary lease.
511
- await this . applyPrimaryState ( false ) ;
507
+ if ( this . isPrimary ) {
508
+ // If we receive a notification of an `acknowledged` or `rejected` batch
509
+ // via Web Storage, we are either already secondary or another tab has
510
+ // taken the primary lease.
511
+ log . debug (
512
+ LOG_TAG ,
513
+ 'Unexpectedly received mutation batch notification when already primary. Releasing primary lease.'
514
+ ) ;
515
+ await this . applyPrimaryState ( false ) ;
516
+ }
512
517
513
518
// NOTE: Both these methods are no-ops for batches that originated from
514
519
// other clients.
@@ -542,7 +547,7 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
542
547
this . sharedClientState . removeLocalPendingMutation ( batchId ) ;
543
548
return this . emitNewSnapsAndNotifyLocalStore ( changes ) ;
544
549
} )
545
- . catch ( err => this . tryRecoverClient ( err ) ) ;
550
+ . catch ( err => this . tryRecoverFromPrimaryLeaseLoss ( err ) ) ;
546
551
}
547
552
548
553
rejectFailedWrite ( batchId : BatchId , error : FirestoreError ) : Promise < void > {
@@ -561,7 +566,7 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
561
566
this . sharedClientState . removeLocalPendingMutation ( batchId ) ;
562
567
return this . emitNewSnapsAndNotifyLocalStore ( changes ) ;
563
568
} )
564
- . catch ( err => this . tryRecoverClient ( err ) ) ;
569
+ . catch ( err => this . tryRecoverFromPrimaryLeaseLoss ( err ) ) ;
565
570
}
566
571
567
572
private addMutationCallback (
@@ -738,22 +743,28 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
738
743
if ( this . isPrimary ) {
739
744
await this . localStore
740
745
. collectGarbage ( )
741
- . catch ( err => this . tryRecoverClient ( err ) ) ;
746
+ . catch ( err => this . tryRecoverFromPrimaryLeaseLoss ( err ) ) ;
742
747
}
743
748
}
744
749
745
750
/**
746
751
* Marks the client as secondary if an IndexedDb operation fails because the
747
752
* primary lease has been taken by another client. This can happen when the
748
753
* client is temporarily CPU throttled and fails to renew its lease in time,
749
- * in which we treat the current client as secondary. We can always revert
750
- * back to primary status via the lease refresh in our persistence layer.
754
+ * in which case we treat the current client as secondary. We can always
755
+ * regain our primary lease via the lease refresh in our persistence layer.
751
756
*
752
- * @param err An error returned by an IndexedDb operation.
757
+ * @param err An error returned by a LocalStore operation.
753
758
* @return A Promise that resolves after we recovered, or the original error.
754
759
*/
755
- private async tryRecoverClient ( err : FirestoreError ) : Promise < void > {
760
+ private async tryRecoverFromPrimaryLeaseLoss (
761
+ err : FirestoreError
762
+ ) : Promise < void > {
756
763
if ( err . code === Code . FAILED_PRECONDITION ) {
764
+ log . debug (
765
+ LOG_TAG ,
766
+ 'Unexpectedly lost primary lease, reverting to secondary'
767
+ ) ;
757
768
return this . applyPrimaryState ( false ) ;
758
769
} else {
759
770
throw err ;
@@ -830,6 +841,7 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
830
841
this . isPrimary = false ;
831
842
await this . remoteStore . disableNetwork ( ) ;
832
843
objUtils . forEachNumber ( this . queryViewsByTarget , targetId => {
844
+ // TODO(multitab): Remove query views for non-local queries.
833
845
this . remoteStore . unlisten ( targetId ) ;
834
846
} ) ;
835
847
}
@@ -849,6 +861,10 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
849
861
if ( this . isPrimary ) {
850
862
// If we receive a target state notification via Web Storage, we are
851
863
// either already secondary or another tab has taken the primary lease.
864
+ log . debug (
865
+ LOG_TAG ,
866
+ 'Unexpectedly received query state notification when already primary. Releasing primary lease.'
867
+ ) ;
852
868
await this . applyPrimaryState ( false ) ;
853
869
}
854
870
@@ -912,11 +928,10 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
912
928
// removed if it has been rejected by the backend.
913
929
if ( queryView ) {
914
930
this . remoteStore . unlisten ( targetId ) ;
915
- await this . removeAndCleanupQuery ( queryView ) ;
916
- await this . localStore . releaseQuery (
917
- queryView . query ,
918
- /*keepPersistedQueryData=*/ false
919
- ) ;
931
+ await this . localStore
932
+ . releaseQuery ( queryView . query , /*keepPersistedQueryData=*/ false )
933
+ . then ( ( ) => this . removeAndCleanupQuery ( queryView ) )
934
+ . catch ( err => this . tryRecoverFromPrimaryLeaseLoss ( err ) ) ;
920
935
}
921
936
}
922
937
}
0 commit comments