@@ -66,8 +66,6 @@ const CLIENT_METADATA_MAX_AGE_MS = 5000;
66
66
* if they're already performing an IndexedDB operation.
67
67
*/
68
68
const CLIENT_METADATA_REFRESH_INTERVAL_MS = 4000 ;
69
- /** LocalStorage location to indicate a zombied client id (see class comment). */
70
- const ZOMBIED_PRIMARY_LOCALSTORAGE_SUFFIX = 'zombiedClientId' ;
71
69
/** User-facing error when the primary lease is required but not available. */
72
70
const PRIMARY_LEASE_LOST_ERROR_MSG =
73
71
'The current tab is not in the required state to perform this operation. ' +
@@ -81,6 +79,10 @@ const UNSUPPORTED_PLATFORM_ERROR_MSG =
81
79
' IndexedDB or is known to have an incomplete implementation. Offline' +
82
80
' persistence has been disabled.' ;
83
81
82
+ // The format of the LocalStorage key that stores zombied client is:
83
+ // firestore_zombie_<persistence_prefix>_<instance_key>
84
+ const ZOMBIED_CLIENTS_KEY_PREFIX = 'firestore_zombie' ;
85
+
84
86
/**
85
87
* An IndexedDB-backed instance of Persistence. Data is stored persistently
86
88
* across sessions.
@@ -127,7 +129,6 @@ export class IndexedDbPersistence implements Persistence {
127
129
private isPrimary = false ;
128
130
private networkEnabled = true ;
129
131
private dbName : string ;
130
- private localStoragePrefix : string ;
131
132
132
133
/**
133
134
* Set to an Error object if we encounter an unrecoverable error. All further
@@ -153,15 +154,14 @@ export class IndexedDbPersistence implements Persistence {
153
154
private primaryStateListener : PrimaryStateListener = _ => Promise . resolve ( ) ;
154
155
155
156
constructor (
156
- prefix : string ,
157
+ private readonly persistenceKey : string ,
157
158
private readonly clientId : ClientId ,
158
159
platform : Platform ,
159
160
private readonly queue : AsyncQueue ,
160
161
serializer : JsonProtoSerializer
161
162
) {
162
- this . dbName = prefix + IndexedDbPersistence . MAIN_DATABASE ;
163
+ this . dbName = persistenceKey + IndexedDbPersistence . MAIN_DATABASE ;
163
164
this . serializer = new LocalSerializer ( serializer ) ;
164
- this . localStoragePrefix = prefix ;
165
165
this . document = platform . document ;
166
166
this . window = platform . window ;
167
167
}
@@ -308,7 +308,7 @@ export class IndexedDbPersistence implements Persistence {
308
308
const currentLeaseIsValid =
309
309
currentPrimary !== null &&
310
310
this . isWithinMaxAge ( currentPrimary . leaseTimestampMs ) &&
311
- currentPrimary . ownerId !== this . getZombiedClientId ( ) ;
311
+ ! this . isClientZombied ( currentPrimary . ownerId ) ;
312
312
313
313
// A client is eligible for the primary lease if:
314
314
// - its network is enabled and the client's tab is in the foreground.
@@ -356,7 +356,8 @@ export class IndexedDbPersistence implements Persistence {
356
356
. iterate ( ( key , otherClient , control ) => {
357
357
if (
358
358
this . clientId !== otherClient . clientId &&
359
- this . isWithinMaxAge ( otherClient . updateTimeMs )
359
+ this . isWithinMaxAge ( otherClient . updateTimeMs ) &&
360
+ ! this . isClientZombied ( otherClient . clientId )
360
361
) {
361
362
const otherClientHasBetterNetworkState =
362
363
! this . networkEnabled && otherClient . networkEnabled ;
@@ -393,10 +394,9 @@ export class IndexedDbPersistence implements Persistence {
393
394
if ( ! this . started ) {
394
395
return Promise . resolve ( ) ;
395
396
}
396
- // TODO(multitab): Similar to the zombied client ID, we should write an
397
- // entry to Local Storage first to indicate that we are no longer alive.
398
- // This will help us when the shutdown handler doesn't run to completion.
399
397
this . started = false ;
398
+
399
+ this . markClientZombied ( ) ;
400
400
if ( this . clientMetadataRefresher ) {
401
401
this . clientMetadataRefresher . cancel ( ) ;
402
402
}
@@ -412,6 +412,10 @@ export class IndexedDbPersistence implements Persistence {
412
412
}
413
413
) ;
414
414
this . simpleDb . close ( ) ;
415
+
416
+ // Remove the entry marking the client as zombied from LocalStorage since
417
+ // we successfully deleted its metadata from IndexedDb.
418
+ this . removeClientZombiedEntry ( ) ;
415
419
if ( deleteData ) {
416
420
await SimpleDb . delete ( this . dbName ) ;
417
421
}
@@ -506,7 +510,7 @@ export class IndexedDbPersistence implements Persistence {
506
510
const currentLeaseIsValid =
507
511
currentPrimary !== null &&
508
512
this . isWithinMaxAge ( currentPrimary . leaseTimestampMs ) &&
509
- currentPrimary . ownerId !== this . getZombiedClientId ( ) ;
513
+ ! this . isClientZombied ( currentPrimary . ownerId ) ;
510
514
511
515
if ( currentLeaseIsValid && ! this . isLocalClient ( currentPrimary ) ) {
512
516
if ( ! currentPrimary . allowTabSynchronization ) {
@@ -641,9 +645,7 @@ export class IndexedDbPersistence implements Persistence {
641
645
// Note: In theory, this should be scheduled on the AsyncQueue since it
642
646
// accesses internal state. We execute this code directly during shutdown
643
647
// to make sure it gets a chance to run.
644
- if ( this . isPrimary ) {
645
- this . setZombiedClientId ( this . clientId ) ;
646
- }
648
+ this . markClientZombied ( ) ;
647
649
648
650
this . queue . enqueue ( ( ) => {
649
651
// Attempt graceful shutdown (including releasing our owner lease), but
@@ -671,7 +673,7 @@ export class IndexedDbPersistence implements Persistence {
671
673
* zombied due to their tab closing) from LocalStorage, or null if no such
672
674
* record exists.
673
675
*/
674
- private getZombiedClientId ( ) : ClientId | null {
676
+ private isClientZombied ( clientId : ClientId ) : boolean {
675
677
if ( this . window . localStorage === undefined ) {
676
678
assert (
677
679
process . env . USE_MOCK_PERSISTENCE === 'YES' ,
@@ -681,15 +683,17 @@ export class IndexedDbPersistence implements Persistence {
681
683
}
682
684
683
685
try {
684
- const zombiedClientId = this . window . localStorage . getItem (
685
- this . zombiedClientLocalStorageKey ( )
686
- ) ;
686
+ const isZombied =
687
+ this . window . localStorage . getItem (
688
+ this . zombiedClientLocalStorageKey ( clientId )
689
+ ) !== null ;
687
690
log . debug (
688
691
LOG_TAG ,
689
- 'Zombied clientId from LocalStorage:' ,
690
- zombiedClientId
692
+ `Client '${ clientId } ' ${
693
+ isZombied ? 'is' : 'is not'
694
+ } zombied in LocalStorage`
691
695
) ;
692
- return zombiedClientId ;
696
+ return isZombied ;
693
697
} catch ( e ) {
694
698
// Gracefully handle if LocalStorage isn't available / working.
695
699
log . error ( LOG_TAG , 'Failed to get zombied client id.' , e ) ;
@@ -698,29 +702,35 @@ export class IndexedDbPersistence implements Persistence {
698
702
}
699
703
700
704
/**
701
- * Records a zombied primary client (a primary client that had its tab closed)
702
- * in LocalStorage or, if passed null, deletes any recorded zombied owner .
705
+ * Record client as zombied (a client that had its tab closed). Zombied
706
+ * clients are ignored during primary tab selection .
703
707
*/
704
- private setZombiedClientId ( zombiedClientId : ClientId | null ) : void {
708
+ private markClientZombied ( ) : void {
705
709
try {
706
- if ( zombiedClientId === null ) {
707
- this . window . localStorage . removeItem (
708
- this . zombiedClientLocalStorageKey ( )
709
- ) ;
710
- } else {
711
- this . window . localStorage . setItem (
712
- this . zombiedClientLocalStorageKey ( ) ,
713
- zombiedClientId
714
- ) ;
715
- }
710
+ // TODO(multitab): Garbage Collect Local Storage
711
+ this . window . localStorage . setItem (
712
+ this . zombiedClientLocalStorageKey ( this . clientId ) ,
713
+ String ( Date . now ( ) )
714
+ ) ;
716
715
} catch ( e ) {
717
716
// Gracefully handle if LocalStorage isn't available / working.
718
717
log . error ( 'Failed to set zombie owner id.' , e ) ;
719
718
}
720
719
}
721
720
722
- private zombiedClientLocalStorageKey ( ) : string {
723
- return this . localStoragePrefix + ZOMBIED_PRIMARY_LOCALSTORAGE_SUFFIX ;
721
+ /** Removes the zombied client entry if it exists. */
722
+ private removeClientZombiedEntry ( ) : void {
723
+ try {
724
+ this . window . localStorage . removeItem (
725
+ this . zombiedClientLocalStorageKey ( this . clientId )
726
+ ) ;
727
+ } catch ( e ) {
728
+ // Ignore
729
+ }
730
+ }
731
+
732
+ private zombiedClientLocalStorageKey ( clientId : ClientId ) : string {
733
+ return `${ ZOMBIED_CLIENTS_KEY_PREFIX } _${ this . persistenceKey } _${ clientId } ` ;
724
734
}
725
735
}
726
736
0 commit comments