Skip to content

Commit 972cb4c

Browse files
Addressing comments
1 parent 8481ee5 commit 972cb4c

File tree

1 file changed

+65
-116
lines changed

1 file changed

+65
-116
lines changed

packages/firestore/src/local/indexeddb_persistence.ts

+65-116
Original file line numberDiff line numberDiff line change
@@ -58,21 +58,14 @@ const LOG_TAG = 'IndexedDbPersistence';
5858
*/
5959
const CLIENT_METADATA_MAX_AGE_MS = 5000;
6060
/**
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.
6464
*
6565
* 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.
6967
*/
7068
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;
7669
/** LocalStorage location to indicate a zombied client id (see class comment). */
7770
const ZOMBIED_PRIMARY_LOCALSTORAGE_SUFFIX = 'zombiedClientId';
7871
/** User-facing error when the primary lease is required but not available. */
@@ -186,8 +179,8 @@ export class IndexedDbPersistence implements Persistence {
186179
.then(() => {
187180
this.attachVisibilityHandler();
188181
this.attachWindowUnloadHook();
189-
return this.updateClientMetadataAndTryBecomePrimary().then(leaseTtl =>
190-
this.scheduleClientMetadataAndPrimaryLeaseRefreshes(leaseTtl)
182+
return this.updateClientMetadataAndTryBecomePrimary().then(() =>
183+
this.scheduleClientMetadataAndPrimaryLeaseRefreshes()
191184
);
192185
});
193186
}
@@ -206,98 +199,47 @@ export class IndexedDbPersistence implements Persistence {
206199
* @return {Promise<number>} A Promise that resolves with the interval in ms
207200
* after which the metadata and primary lease needs to be refreshed.
208201
*/
209-
private updateClientMetadataAndTryBecomePrimary(): Promise<number> {
202+
private updateClientMetadataAndTryBecomePrimary(): Promise<void> {
210203
return this.simpleDb.runTransaction('readwrite', ALL_STORES, txn => {
211204
const metadataStore = clientMetadataStore(txn);
212205
metadataStore.put(
213206
new DbClientMetadata(this.clientKey, Date.now(), this.inForeground)
214207
);
215208

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+
}
231214

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+
}
257218
});
258219
});
259220
}
260221

261222
/**
262223
* Schedules a recurring timer to update the client metadata and to either
263224
* 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.
268225
*/
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+
);
277236
}
278237

279238
/** Checks whether `client` is the local client. */
280239
private isLocalClient(client: DbOwner | null): boolean {
281240
return client ? client.ownerId === this.clientKey : false;
282241
}
283242

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-
301243
/**
302244
* Evaluate the state of all active instances and determine whether the local
303245
* client is or can act as the holder of the primary lease. Returns whether
@@ -306,24 +248,32 @@ export class IndexedDbPersistence implements Persistence {
306248
* (foreground) client should become leaseholder instead.
307249
*/
308250
private canActAsPrimary(
309-
currentPrimary: DbOwner | null,
310251
txn: SimpleDbTransaction
311252
): 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+
}
315273

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) => {
327277
if (this.clientKey !== value.clientKey) {
328278
if (
329279
this.isWithinMaxAge(value.updateTimeMs) &&
@@ -333,16 +283,18 @@ export class IndexedDbPersistence implements Persistence {
333283
control.done();
334284
}
335285
}
336-
});
337-
}
286+
})
287+
.next(() => canActAsPrimary);
338288
})
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+
}
346298
return canActAsPrimary;
347299
});
348300
}
@@ -393,10 +345,9 @@ export class IndexedDbPersistence implements Persistence {
393345
// executing transactionOperation(). This ensures that even if the
394346
// transactionOperation takes a long time, we'll use a recent
395347
// 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) {
400351
// TODO(multitab): Handle this gracefully and transition back to
401352
// secondary state.
402353
throw new FirestoreError(
@@ -419,11 +370,9 @@ export class IndexedDbPersistence implements Persistence {
419370
* Obtains or extends the new primary lease for the current client. This
420371
* method does not verify that the client is eligible for this lease.
421372
*/
422-
private acquireOrExtendPrimaryLease(txn): PersistencePromise<DbOwner> {
373+
private acquireOrExtendPrimaryLease(txn): PersistencePromise<void> {
423374
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);
427376
}
428377

429378
static isAvailable(): boolean {
@@ -490,7 +439,7 @@ export class IndexedDbPersistence implements Persistence {
490439
this.documentVisibilityHandler = () => {
491440
this.queue.enqueue<DbOwner | void>(() => {
492441
this.inForeground = this.document.visibilityState === 'visible';
493-
return Promise.resolve();
442+
return this.updateClientMetadataAndTryBecomePrimary();
494443
});
495444
};
496445

0 commit comments

Comments
 (0)