@@ -58,21 +58,14 @@ const LOG_TAG = 'IndexedDbPersistence';
58
58
*/
59
59
const CLIENT_METADATA_MAX_AGE_MS = 5000 ;
60
60
/**
61
- * The maximum interval after which clients will update their metadata,
62
- * including refreshing their primary lease if held or potentially trying to
63
- * acquire it if not held.
61
+ * The interval at which clients will update their metadata, including
62
+ * refreshing their primary lease if held or potentially trying to acquire it if
63
+ * not held.
64
64
*
65
65
* Primary clients may opportunistically refresh their metadata earlier
66
- * if they're already performing an IndexedDB operation. Secondary clients
67
- * may also temporarily use a shorter refresh interval to synchronize their
68
- * state refreshes with the primary lease holder.
66
+ * if they're already performing an IndexedDB operation.
69
67
*/
70
68
const CLIENT_METADATA_REFRESH_INTERVAL_MS = 4000 ;
71
- /**
72
- * Minimum interval for all state refreshes. Used when synchronizing the primary
73
- * lease refresh with an existing lease that is about to expire.
74
- */
75
- const MINIMUM_REFRESH_INTERVAL_MS = 1000 ;
76
69
/** LocalStorage location to indicate a zombied client id (see class comment). */
77
70
const ZOMBIED_PRIMARY_LOCALSTORAGE_SUFFIX = 'zombiedClientId' ;
78
71
/** User-facing error when the primary lease is required but not available. */
@@ -186,8 +179,8 @@ export class IndexedDbPersistence implements Persistence {
186
179
. then ( ( ) => {
187
180
this . attachVisibilityHandler ( ) ;
188
181
this . attachWindowUnloadHook ( ) ;
189
- return this . updateClientMetadataAndTryBecomePrimary ( ) . then ( leaseTtl =>
190
- this . scheduleClientMetadataAndPrimaryLeaseRefreshes ( leaseTtl )
182
+ return this . updateClientMetadataAndTryBecomePrimary ( ) . then ( ( ) =>
183
+ this . scheduleClientMetadataAndPrimaryLeaseRefreshes ( )
191
184
) ;
192
185
} ) ;
193
186
}
@@ -206,98 +199,47 @@ export class IndexedDbPersistence implements Persistence {
206
199
* @return {Promise<number> } A Promise that resolves with the interval in ms
207
200
* after which the metadata and primary lease needs to be refreshed.
208
201
*/
209
- private updateClientMetadataAndTryBecomePrimary ( ) : Promise < number > {
202
+ private updateClientMetadataAndTryBecomePrimary ( ) : Promise < void > {
210
203
return this . simpleDb . runTransaction ( 'readwrite' , ALL_STORES , txn => {
211
204
const metadataStore = clientMetadataStore ( txn ) ;
212
205
metadataStore . put (
213
206
new DbClientMetadata ( this . clientKey , Date . now ( ) , this . inForeground )
214
207
) ;
215
208
216
- return this . getCurrentPrimary ( txn ) . next ( currentPrimary => {
217
- return this . canActAsPrimary ( currentPrimary , txn ) . next (
218
- canActAsPrimary => {
219
- if ( canActAsPrimary !== this . isPrimary ) {
220
- this . isPrimary = canActAsPrimary ;
221
- log . debug (
222
- LOG_TAG ,
223
- `Primary lease changed. Current client ${
224
- this . isPrimary ? 'acquired' : 'released'
225
- } its primary lease.`
226
- ) ;
227
- this . queue . enqueue ( ( ) =>
228
- this . primaryStateListener ( this . isPrimary )
229
- ) ;
230
- }
209
+ return this . canActAsPrimary ( txn ) . next ( canActAsPrimary => {
210
+ if ( canActAsPrimary !== this . isPrimary ) {
211
+ this . isPrimary = canActAsPrimary ;
212
+ this . queue . enqueue ( ( ) => this . primaryStateListener ( this . isPrimary ) ) ;
213
+ }
231
214
232
- if ( this . isPrimary ) {
233
- // If we are the primary lease holder, we refresh the client
234
- // metadata and the primary lease after the default interval.
235
- return this . acquireOrExtendPrimaryLease ( txn ) . next (
236
- ( ) => CLIENT_METADATA_REFRESH_INTERVAL_MS
237
- ) ;
238
- } else if ( currentPrimary != null ) {
239
- // If another client currently holds the lease, we use a custom
240
- // refresh interval and schedule a lease refresh immediately after
241
- // the current lease is expected to expire.
242
- const remainingLifetimeMs =
243
- Date . now ( ) -
244
- ( currentPrimary . leaseTimestampMs + CLIENT_METADATA_MAX_AGE_MS ) ;
245
- return Math . min (
246
- MINIMUM_REFRESH_INTERVAL_MS ,
247
- remainingLifetimeMs + 1
248
- ) ;
249
- } else {
250
- // If there is no current leaseholder, but we are not ourselves
251
- // eligible to directly obtain the lease (based on our foreground
252
- // state), we try again after the minimum refresh interval.
253
- return MINIMUM_REFRESH_INTERVAL_MS ;
254
- }
255
- }
256
- ) ;
215
+ if ( this . isPrimary ) {
216
+ return this . acquireOrExtendPrimaryLease ( txn ) ;
217
+ }
257
218
} ) ;
258
219
} ) ;
259
220
}
260
221
261
222
/**
262
223
* Schedules a recurring timer to update the client metadata and to either
263
224
* extend or acquire the primary lease if the client is eligible.
264
- *
265
- * @param delayMs The delay to use for the first refresh. Subsequent refreshes
266
- * may use a different delay based on the primary leaseholder's refresh
267
- * interval.
268
225
*/
269
- private scheduleClientMetadataAndPrimaryLeaseRefreshes (
270
- delayMs : number
271
- ) : void {
272
- this . queue . enqueueAfterDelay ( TimerId . ClientMetadataRefresh , delayMs , ( ) => {
273
- return this . updateClientMetadataAndTryBecomePrimary ( ) . then ( leaseTtl =>
274
- this . scheduleClientMetadataAndPrimaryLeaseRefreshes ( leaseTtl )
275
- ) ;
276
- } ) ;
226
+ private scheduleClientMetadataAndPrimaryLeaseRefreshes ( ) : void {
227
+ this . queue . enqueueAfterDelay (
228
+ TimerId . ClientMetadataRefresh ,
229
+ CLIENT_METADATA_REFRESH_INTERVAL_MS ,
230
+ ( ) => {
231
+ return this . updateClientMetadataAndTryBecomePrimary ( ) . then ( ( ) =>
232
+ this . scheduleClientMetadataAndPrimaryLeaseRefreshes ( )
233
+ ) ;
234
+ }
235
+ ) ;
277
236
}
278
237
279
238
/** Checks whether `client` is the local client. */
280
239
private isLocalClient ( client : DbOwner | null ) : boolean {
281
240
return client ? client . ownerId === this . clientKey : false ;
282
241
}
283
242
284
- private getCurrentPrimary (
285
- txn : SimpleDbTransaction
286
- ) : PersistencePromise < DbOwner | null > {
287
- const store = ownerStore ( txn ) ;
288
- return store . get ( 'owner' ) . next ( currentPrimary => {
289
- if (
290
- currentPrimary !== null &&
291
- this . isWithinMaxAge ( currentPrimary . leaseTimestampMs ) &&
292
- currentPrimary . ownerId !== this . getZombiedClientId ( )
293
- ) {
294
- return currentPrimary ;
295
- } else {
296
- return null ;
297
- }
298
- } ) ;
299
- }
300
-
301
243
/**
302
244
* Evaluate the state of all active instances and determine whether the local
303
245
* client is or can act as the holder of the primary lease. Returns whether
@@ -306,24 +248,32 @@ export class IndexedDbPersistence implements Persistence {
306
248
* (foreground) client should become leaseholder instead.
307
249
*/
308
250
private canActAsPrimary (
309
- currentPrimary : DbOwner | null ,
310
251
txn : SimpleDbTransaction
311
252
) : PersistencePromise < boolean > {
312
- if ( currentPrimary !== null ) {
313
- return PersistencePromise . resolve ( this . isLocalClient ( currentPrimary ) ) ;
314
- }
253
+ const store = ownerStore ( txn ) ;
254
+ return store
255
+ . get ( 'owner' )
256
+ . next ( currentPrimary => {
257
+ const currentLeaseIsValid =
258
+ currentPrimary !== null &&
259
+ this . isWithinMaxAge ( currentPrimary . leaseTimestampMs ) &&
260
+ currentPrimary . ownerId !== this . getZombiedClientId ( ) ;
261
+
262
+ if ( currentLeaseIsValid ) {
263
+ return this . isLocalClient ( currentPrimary ) ;
264
+ }
265
+
266
+ // Check if this client is eligible for a primary lease based on its
267
+ // visibility state and the visibility state of all active clients. A
268
+ // client can obtain the primary lease if it is either in the foreground
269
+ // or if this client and all other clients are in the background.
270
+ if ( this . inForeground ) {
271
+ return true ;
272
+ }
315
273
316
- // Check if this client is eligible for a primary lease based on its
317
- // visibility state and the visibility state of all active clients. A
318
- // client can obtain the primary lease if it is either in the foreground
319
- // or if this client and all other clients are in the background.
320
- let canActAsPrimary = this . inForeground ;
321
-
322
- return PersistencePromise . resolve ( )
323
- . next ( ( ) => {
324
- if ( ! canActAsPrimary ) {
325
- canActAsPrimary = true ;
326
- return clientMetadataStore ( txn ) . iterate ( ( key , value , control ) => {
274
+ let canActAsPrimary = true ;
275
+ return clientMetadataStore ( txn )
276
+ . iterate ( ( key , value , control ) => {
327
277
if ( this . clientKey !== value . clientKey ) {
328
278
if (
329
279
this . isWithinMaxAge ( value . updateTimeMs ) &&
@@ -333,16 +283,18 @@ export class IndexedDbPersistence implements Persistence {
333
283
control . done ( ) ;
334
284
}
335
285
}
336
- } ) ;
337
- }
286
+ } )
287
+ . next ( ( ) => canActAsPrimary ) ;
338
288
} )
339
- . next ( ( ) => {
340
- log . debug (
341
- LOG_TAG ,
342
- `Client ${
343
- canActAsPrimary ? 'is' : 'is not'
344
- } eligible for a primary lease.`
345
- ) ;
289
+ . next ( canActAsPrimary => {
290
+ if ( this . isPrimary !== canActAsPrimary ) {
291
+ log . debug (
292
+ LOG_TAG ,
293
+ `Client ${
294
+ canActAsPrimary ? 'is' : 'is not'
295
+ } eligible for a primary lease.`
296
+ ) ;
297
+ }
346
298
return canActAsPrimary ;
347
299
} ) ;
348
300
}
@@ -393,10 +345,9 @@ export class IndexedDbPersistence implements Persistence {
393
345
// executing transactionOperation(). This ensures that even if the
394
346
// transactionOperation takes a long time, we'll use a recent
395
347
// leaseTimestampMs in the extended (or newly acquired) lease.
396
- return this . getCurrentPrimary ( txn )
397
- . next ( currentPrimary => this . canActAsPrimary ( currentPrimary , txn ) )
398
- . next ( isPrimary => {
399
- if ( ! isPrimary ) {
348
+ return this . canActAsPrimary ( txn )
349
+ . next ( canActAsPrimary => {
350
+ if ( ! canActAsPrimary ) {
400
351
// TODO(multitab): Handle this gracefully and transition back to
401
352
// secondary state.
402
353
throw new FirestoreError (
@@ -419,11 +370,9 @@ export class IndexedDbPersistence implements Persistence {
419
370
* Obtains or extends the new primary lease for the current client. This
420
371
* method does not verify that the client is eligible for this lease.
421
372
*/
422
- private acquireOrExtendPrimaryLease ( txn ) : PersistencePromise < DbOwner > {
373
+ private acquireOrExtendPrimaryLease ( txn ) : PersistencePromise < void > {
423
374
const newPrimary = new DbOwner ( this . clientKey , Date . now ( ) ) ;
424
- return ownerStore ( txn )
425
- . put ( 'owner' , newPrimary )
426
- . next ( ( ) => newPrimary ) ;
375
+ return ownerStore ( txn ) . put ( 'owner' , newPrimary ) ;
427
376
}
428
377
429
378
static isAvailable ( ) : boolean {
@@ -490,7 +439,7 @@ export class IndexedDbPersistence implements Persistence {
490
439
this . documentVisibilityHandler = ( ) => {
491
440
this . queue . enqueue < DbOwner | void > ( ( ) => {
492
441
this . inForeground = this . document . visibilityState === 'visible' ;
493
- return Promise . resolve ( ) ;
442
+ return this . updateClientMetadataAndTryBecomePrimary ( ) ;
494
443
} ) ;
495
444
} ;
496
445
0 commit comments