@@ -148,14 +148,23 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
148
148
q . canonicalId ( )
149
149
) ;
150
150
private queriesByTarget : { [ targetId : number ] : Query [ ] } = { } ;
151
- /** The keys of documents that are in limbo for which we haven't yet started a limbo resolution query. */
152
- private limboListenQueue : DocumentKey [ ] = [ ] ;
153
- /** Keeps track of the target ID for each document that is in limbo with an active target. */
154
- private limboTargetsByKey = new SortedMap < DocumentKey , TargetId > (
151
+ /**
152
+ * The keys of documents that are in limbo for which we haven't yet started a
153
+ * limbo resolution query.
154
+ */
155
+ private enqueuedLimboResolutions : DocumentKey [ ] = [ ] ;
156
+ /**
157
+ * Keeps track of the target ID for each document that is in limbo with an
158
+ * active target.
159
+ */
160
+ private activeLimboTargetsByKey = new SortedMap < DocumentKey , TargetId > (
155
161
DocumentKey . comparator
156
162
) ;
157
- /** Keeps track of the information about an active limbo resolution for each active target ID that was started for the purpose of limbo resolution. */
158
- private limboResolutionsByTarget : {
163
+ /**
164
+ * Keeps track of the information about an active limbo resolution for each
165
+ * active target ID that was started for the purpose of limbo resolution.
166
+ */
167
+ private activeLimboResolutionsByTarget : {
159
168
[ targetId : number ] : LimboResolution ;
160
169
} = { } ;
161
170
private limboDocumentRefs = new ReferenceSet ( ) ;
@@ -405,7 +414,9 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
405
414
const changes = await this . localStore . applyRemoteEvent ( remoteEvent ) ;
406
415
// Update `receivedDocument` as appropriate for any limbo targets.
407
416
objUtils . forEach ( remoteEvent . targetChanges , ( targetId , targetChange ) => {
408
- const limboResolution = this . limboResolutionsByTarget [ Number ( targetId ) ] ;
417
+ const limboResolution = this . activeLimboResolutionsByTarget [
418
+ Number ( targetId )
419
+ ] ;
409
420
if ( limboResolution ) {
410
421
// Since this is a limbo resolution lookup, it's for a single document
411
422
// and it could be added, modified, or removed, but not a combination.
@@ -484,14 +495,16 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
484
495
// PORTING NOTE: Multi-tab only.
485
496
this . sharedClientState . updateQueryState ( targetId , 'rejected' , err ) ;
486
497
487
- const limboResolution = this . limboResolutionsByTarget [ targetId ] ;
498
+ const limboResolution = this . activeLimboResolutionsByTarget [ targetId ] ;
488
499
const limboKey = limboResolution && limboResolution . key ;
489
500
if ( limboKey ) {
490
501
// Since this query failed, we won't want to manually unlisten to it.
491
502
// So go ahead and remove it from bookkeeping.
492
- this . limboTargetsByKey = this . limboTargetsByKey . remove ( limboKey ) ;
493
- delete this . limboResolutionsByTarget [ targetId ] ;
494
- this . pumpLimboResolutionListenQueue ( ) ;
503
+ this . activeLimboTargetsByKey = this . activeLimboTargetsByKey . remove (
504
+ limboKey
505
+ ) ;
506
+ delete this . activeLimboResolutionsByTarget [ targetId ] ;
507
+ this . pumpEnqueuedLimboResolutions ( ) ;
495
508
496
509
// TODO(klimt): We really only should do the following on permission
497
510
// denied errors, but we don't have the cause code here.
@@ -737,16 +750,16 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
737
750
private removeLimboTarget ( key : DocumentKey ) : void {
738
751
// It's possible that the target already got removed because the query failed. In that case,
739
752
// the key won't exist in `limboTargetsByKey`. Only do the cleanup if we still have the target.
740
- const limboTargetId = this . limboTargetsByKey . get ( key ) ;
753
+ const limboTargetId = this . activeLimboTargetsByKey . get ( key ) ;
741
754
if ( limboTargetId === null ) {
742
755
// This target already got removed, because the query failed.
743
756
return ;
744
757
}
745
758
746
759
this . remoteStore . unlisten ( limboTargetId ) ;
747
- this . limboTargetsByKey = this . limboTargetsByKey . remove ( key ) ;
748
- delete this . limboResolutionsByTarget [ limboTargetId ] ;
749
- this . pumpLimboResolutionListenQueue ( ) ;
760
+ this . activeLimboTargetsByKey = this . activeLimboTargetsByKey . remove ( key ) ;
761
+ delete this . activeLimboResolutionsByTarget [ limboTargetId ] ;
762
+ this . pumpEnqueuedLimboResolutions ( ) ;
750
763
}
751
764
752
765
private updateTrackedLimbos (
@@ -775,41 +788,32 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
775
788
776
789
private trackLimboChange ( limboChange : AddedLimboDocument ) : void {
777
790
const key = limboChange . key ;
778
- if ( ! this . limboTargetsByKey . get ( key ) ) {
791
+ if ( ! this . activeLimboTargetsByKey . get ( key ) ) {
779
792
log . debug ( LOG_TAG , 'New document in limbo: ' + key ) ;
780
- this . limboListenQueue . push ( key ) ;
781
- this . pumpLimboResolutionListenQueue ( ) ;
793
+ this . enqueuedLimboResolutions . push ( key ) ;
794
+ this . pumpEnqueuedLimboResolutions ( ) ;
782
795
}
783
796
}
784
797
785
798
/**
786
- * Starts listens for documents in limbo that are enqueued for resolution.
787
- *
788
- * When a document goes into limbo it is enqueued for resolution. This method
789
- * repeatedly removes entries from the limbo resolution queue and starts a
790
- * listen for them until either (1) the queue is empty, meaning that all
791
- * documents that were in limbo either have active listens or have been
792
- * resolved, or (2) the maximum number of concurrent limbo resolution listens
793
- * has been reached.
799
+ * Starts listens for documents in limbo that are enqueued for resolution,
800
+ * subject to a maximum number of concurrent resolutions.
794
801
*
795
- * This method is invoked every time an entry is added to the limbo
796
- * resolution queue and every time that a limbo resolution listen completes
797
- * (either successfully or unsuccessfully). This ensures that all documents in
798
- * limbo are eventually resolved.
799
- *
800
- * A maximum number of concurrent limbo resolution listens was implemented to
801
- * prevent an unbounded number of active limbo resolution listens that can
802
- * exhaust server resources and result in "resource exhausted" errors.
802
+ * Without bounding the number of concurrent resolutions, the server can fail
803
+ * with "resource exhausted" errors which can lead to pathological client
804
+ * behavior as seen in https://github.com/firebase/firebase-js-sdk/issues/2683.
803
805
*/
804
- private pumpLimboResolutionListenQueue ( ) : void {
806
+ private pumpEnqueuedLimboResolutions ( ) : void {
805
807
while (
806
- this . limboListenQueue . length > 0 &&
807
- this . limboTargetsByKey . size < this . maxConcurrentLimboResolutions
808
+ this . enqueuedLimboResolutions . length > 0 &&
809
+ this . activeLimboTargetsByKey . size < this . maxConcurrentLimboResolutions
808
810
) {
809
- const key = this . limboListenQueue . shift ( ) ! ;
811
+ const key = this . enqueuedLimboResolutions . shift ( ) ! ;
810
812
const limboTargetId = this . limboTargetIdGenerator . next ( ) ;
811
- this . limboResolutionsByTarget [ limboTargetId ] = new LimboResolution ( key ) ;
812
- this . limboTargetsByKey = this . limboTargetsByKey . insert (
813
+ this . activeLimboResolutionsByTarget [ limboTargetId ] = new LimboResolution (
814
+ key
815
+ ) ;
816
+ this . activeLimboTargetsByKey = this . activeLimboTargetsByKey . insert (
813
817
key ,
814
818
limboTargetId
815
819
) ;
@@ -826,12 +830,12 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
826
830
827
831
// Visible for testing
828
832
activeLimboDocumentResolutions ( ) : SortedMap < DocumentKey , TargetId > {
829
- return this . limboTargetsByKey ;
833
+ return this . activeLimboTargetsByKey ;
830
834
}
831
835
832
836
// Visible for testing
833
- documentsEnqueuedForLimboResolution ( ) : DocumentKey [ ] {
834
- return this . limboListenQueue ;
837
+ enqueuedLimboDocumentResolutions ( ) : DocumentKey [ ] {
838
+ return this . enqueuedLimboResolutions ;
835
839
}
836
840
837
841
private async emitNewSnapsAndNotifyLocalStore (
@@ -977,12 +981,12 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
977
981
978
982
// PORTING NOTE: Multi-tab only.
979
983
private resetLimboDocuments ( ) : void {
980
- objUtils . forEachNumber ( this . limboResolutionsByTarget , targetId => {
984
+ objUtils . forEachNumber ( this . activeLimboResolutionsByTarget , targetId => {
981
985
this . remoteStore . unlisten ( targetId ) ;
982
986
} ) ;
983
987
this . limboDocumentRefs . removeAllReferences ( ) ;
984
- this . limboResolutionsByTarget = [ ] ;
985
- this . limboTargetsByKey = new SortedMap < DocumentKey , TargetId > (
988
+ this . activeLimboResolutionsByTarget = [ ] ;
989
+ this . activeLimboTargetsByKey = new SortedMap < DocumentKey , TargetId > (
986
990
DocumentKey . comparator
987
991
) ;
988
992
}
@@ -1179,7 +1183,7 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
1179
1183
}
1180
1184
1181
1185
getRemoteKeysForTarget ( targetId : TargetId ) : DocumentKeySet {
1182
- const limboResolution = this . limboResolutionsByTarget [ targetId ] ;
1186
+ const limboResolution = this . activeLimboResolutionsByTarget [ targetId ] ;
1183
1187
if ( limboResolution && limboResolution . receivedDocument ) {
1184
1188
return documentKeySet ( ) . add ( limboResolution . key ) ;
1185
1189
} else {
0 commit comments