@@ -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,53 @@ 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
+ log . debug (
213
+ LOG_TAG ,
214
+ `Primary lease changed. Current client ${
215
+ this . isPrimary ? 'acquired' : 'released'
216
+ } its primary lease.`
217
+ ) ;
218
+ this . queue . enqueue ( ( ) => this . primaryStateListener ( this . isPrimary ) ) ;
219
+ }
231
220
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
- ) ;
221
+ if ( this . isPrimary ) {
222
+ return this . acquireOrExtendPrimaryLease ( txn ) ;
223
+ }
257
224
} ) ;
258
225
} ) ;
259
226
}
260
227
261
228
/**
262
229
* Schedules a recurring timer to update the client metadata and to either
263
230
* 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
231
*/
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
- } ) ;
232
+ private scheduleClientMetadataAndPrimaryLeaseRefreshes ( ) : void {
233
+ this . queue . enqueueAfterDelay (
234
+ TimerId . ClientMetadataRefresh ,
235
+ CLIENT_METADATA_REFRESH_INTERVAL_MS ,
236
+ ( ) => {
237
+ return this . updateClientMetadataAndTryBecomePrimary ( ) . then ( ( ) =>
238
+ this . scheduleClientMetadataAndPrimaryLeaseRefreshes ( )
239
+ ) ;
240
+ }
241
+ ) ;
277
242
}
278
243
279
244
/** Checks whether `client` is the local client. */
280
245
private isLocalClient ( client : DbOwner | null ) : boolean {
281
246
return client ? client . ownerId === this . clientKey : false ;
282
247
}
283
248
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
249
/**
302
250
* Evaluate the state of all active instances and determine whether the local
303
251
* client is or can act as the holder of the primary lease. Returns whether
@@ -306,45 +254,46 @@ export class IndexedDbPersistence implements Persistence {
306
254
* (foreground) client should become leaseholder instead.
307
255
*/
308
256
private canActAsPrimary (
309
- currentPrimary : DbOwner | null ,
310
257
txn : SimpleDbTransaction
311
258
) : PersistencePromise < boolean > {
312
- if ( currentPrimary !== null ) {
313
- return PersistencePromise . resolve ( this . isLocalClient ( currentPrimary ) ) ;
314
- }
259
+ const store = ownerStore ( txn ) ;
260
+ return store . get ( 'owner' ) . next ( currentPrimary => {
261
+ const currentLeaseIsValid =
262
+ currentPrimary !== null &&
263
+ this . isWithinMaxAge ( currentPrimary . leaseTimestampMs ) &&
264
+ currentPrimary . ownerId !== this . getZombiedClientId ( ) ;
315
265
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 ) => {
327
- if ( this . clientKey !== value . clientKey ) {
328
- if (
329
- this . isWithinMaxAge ( value . updateTimeMs ) &&
330
- value . inForeground
331
- ) {
332
- canActAsPrimary = false ;
333
- control . done ( ) ;
334
- }
266
+ if ( currentLeaseIsValid ) {
267
+ return this . isLocalClient ( currentPrimary ) ;
268
+ }
269
+ // Check if this client is eligible for a primary lease based on its
270
+ // visibility state and the visibility state of all active clients. A
271
+ // client can obtain the primary lease if it is either in the foreground
272
+ // or if this client and all other clients are in the background.
273
+ if ( this . inForeground ) {
274
+ return true ;
275
+ }
276
+
277
+ let canActAsPrimary = true ;
278
+ return clientMetadataStore ( txn )
279
+ . iterate ( ( key , value , control ) => {
280
+ if ( this . clientKey !== value . clientKey ) {
281
+ if ( this . isWithinMaxAge ( value . updateTimeMs ) && value . inForeground ) {
282
+ canActAsPrimary = false ;
283
+ control . done ( ) ;
335
284
}
336
- } ) ;
337
- }
338
- } )
339
- . next ( ( ) => {
340
- log . debug (
341
- LOG_TAG ,
342
- `Client ${
343
- canActAsPrimary ? 'is' : 'is not'
344
- } eligible for a primary lease.`
345
- ) ;
346
- return canActAsPrimary ;
347
- } ) ;
285
+ }
286
+ } )
287
+ . next ( ( ) => {
288
+ console . log (
289
+ LOG_TAG ,
290
+ `Client ${
291
+ canActAsPrimary ? 'is' : 'is not'
292
+ } eligible for a primary lease.`
293
+ ) ;
294
+ return canActAsPrimary ;
295
+ } ) ;
296
+ } ) ;
348
297
}
349
298
350
299
shutdown ( ) : Promise < void > {
@@ -393,10 +342,9 @@ export class IndexedDbPersistence implements Persistence {
393
342
// executing transactionOperation(). This ensures that even if the
394
343
// transactionOperation takes a long time, we'll use a recent
395
344
// 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 ) {
345
+ return this . canActAsPrimary ( txn )
346
+ . next ( canActAsPrimary => {
347
+ if ( ! canActAsPrimary ) {
400
348
// TODO(multitab): Handle this gracefully and transition back to
401
349
// secondary state.
402
350
throw new FirestoreError (
@@ -419,11 +367,9 @@ export class IndexedDbPersistence implements Persistence {
419
367
* Obtains or extends the new primary lease for the current client. This
420
368
* method does not verify that the client is eligible for this lease.
421
369
*/
422
- private acquireOrExtendPrimaryLease ( txn ) : PersistencePromise < DbOwner > {
370
+ private acquireOrExtendPrimaryLease ( txn ) : PersistencePromise < void > {
423
371
const newPrimary = new DbOwner ( this . clientKey , Date . now ( ) ) ;
424
- return ownerStore ( txn )
425
- . put ( 'owner' , newPrimary )
426
- . next ( ( ) => newPrimary ) ;
372
+ return ownerStore ( txn ) . put ( 'owner' , newPrimary ) ;
427
373
}
428
374
429
375
static isAvailable ( ) : boolean {
@@ -490,7 +436,7 @@ export class IndexedDbPersistence implements Persistence {
490
436
this . documentVisibilityHandler = ( ) => {
491
437
this . queue . enqueue < DbOwner | void > ( ( ) => {
492
438
this . inForeground = this . document . visibilityState === 'visible' ;
493
- return Promise . resolve ( ) ;
439
+ return this . updateClientMetadataAndTryBecomePrimary ( ) ;
494
440
} ) ;
495
441
} ;
496
442
0 commit comments