Skip to content

Commit

Permalink
New way to config Firestore SDK Cache.
Browse files Browse the repository at this point in the history
  • Loading branch information
wu-hui committed Feb 8, 2023
1 parent 27b5e7d commit 2555b58
Show file tree
Hide file tree
Showing 6 changed files with 227 additions and 15 deletions.
152 changes: 152 additions & 0 deletions packages/firestore/src/api/cache_config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/**
* @license
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import {
IndexedDbOfflineComponentProvider,
MemoryOfflineComponentProvider,
MultiTabOfflineComponentProvider,
OfflineComponentProvider,
OnlineComponentProvider
} from '../core/component_provider';

export interface MemoryLocalCache {
kind: 'memory';
_onlineComponentProvider: OnlineComponentProvider;
_offlineComponentProvider: MemoryOfflineComponentProvider;
}

class MemoryLocalCacheImpl implements MemoryLocalCache {
kind: 'memory' = 'memory';
_onlineComponentProvider: OnlineComponentProvider;
_offlineComponentProvider: MemoryOfflineComponentProvider;

constructor() {
this._onlineComponentProvider = new OnlineComponentProvider();
this._offlineComponentProvider = new MemoryOfflineComponentProvider();
}
}

export interface IndexedDbLocalCache {
kind: 'indexeddb';
_onlineComponentProvider: OnlineComponentProvider;
_offlineComponentProvider: OfflineComponentProvider;
}

class IndexedDbLocalCacheImpl implements IndexedDbLocalCache {
kind: 'indexeddb' = 'indexeddb';
_onlineComponentProvider: OnlineComponentProvider;
_offlineComponentProvider: OfflineComponentProvider;

constructor(settings: IndexedDbSettings | undefined) {
let tabManager: IndexedDbTabManager;
if (settings?.tabManager) {
settings.tabManager.initialize(settings);
tabManager = settings.tabManager;
} else {
tabManager = indexedDbSingleTabManager(undefined);
tabManager.initialize(settings);
}
this._onlineComponentProvider = tabManager._onlineComponentProvider!;
this._offlineComponentProvider = tabManager._offlineComponentProvider!;
}
}

export type FirestoreLocalCache = MemoryLocalCache | IndexedDbLocalCache;

// Factory function
export function memoryLocalCache(): MemoryLocalCache {
return new MemoryLocalCacheImpl();
}

export interface IndexedDbSettings {
cacheSizeBytes?: number;
// default to singleTabManager({forceOwnership: false})
tabManager?: IndexedDbTabManager;
}

// Factory function
export function indexedDbLocalCache(
settings?: IndexedDbSettings
): IndexedDbLocalCache {
return new IndexedDbLocalCacheImpl(settings);
}

export interface IndexedDbSingleTabManager {
kind: 'indexedDbSingleTab';
initialize: (
settings: Omit<IndexedDbSettings, 'tabManager'> | undefined
) => void;
_onlineComponentProvider?: OnlineComponentProvider;
_offlineComponentProvider?: OfflineComponentProvider;
}

class SingleTabManagerImpl implements IndexedDbSingleTabManager {
kind: 'indexedDbSingleTab' = 'indexedDbSingleTab';

_onlineComponentProvider?: OnlineComponentProvider;
_offlineComponentProvider?: OfflineComponentProvider;

constructor(private forceOwnership?: boolean) {}

initialize(
settings: Omit<IndexedDbSettings, 'tabManager'> | undefined
): void {
this._onlineComponentProvider = new OnlineComponentProvider();
this._offlineComponentProvider = new IndexedDbOfflineComponentProvider(
this._onlineComponentProvider,
settings?.cacheSizeBytes,
this.forceOwnership
);
}
}

export interface IndexedDbMultipleTabManager {
kind: 'IndexedDbMultipleTab';
initialize: (settings: Omit<IndexedDbSettings, 'tabManager'>) => void;
_onlineComponentProvider?: OnlineComponentProvider;
_offlineComponentProvider?: OfflineComponentProvider;
}

class MultiTabManagerImpl implements IndexedDbMultipleTabManager {
kind: 'IndexedDbMultipleTab' = 'IndexedDbMultipleTab';

_onlineComponentProvider?: OnlineComponentProvider;
_offlineComponentProvider?: OfflineComponentProvider;

initialize(
settings: Omit<IndexedDbSettings, 'tabManager'> | undefined
): void {
this._onlineComponentProvider = new OnlineComponentProvider();
this._offlineComponentProvider = new MultiTabOfflineComponentProvider(
this._onlineComponentProvider,
settings?.cacheSizeBytes
);
}
}

export type IndexedDbTabManager =
| IndexedDbSingleTabManager
| IndexedDbMultipleTabManager;

export function indexedDbSingleTabManager(
settings: { forceOwnership?: boolean } | undefined
): IndexedDbSingleTabManager {
return new SingleTabManagerImpl(settings?.forceOwnership);
}
export function indexedDbMultipleTabManager(): IndexedDbMultipleTabManager {
return new MultiTabManagerImpl();
}
19 changes: 19 additions & 0 deletions packages/firestore/src/api/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,15 @@ export function configureFirestore(firestore: Firestore): void {
firestore._queue,
databaseInfo
);
if (
settings.cache?._offlineComponentProvider &&
settings.cache?._onlineComponentProvider
) {
firestore._firestoreClient.uninitializedComponentsProvider = {
offline: settings.cache._offlineComponentProvider,
online: settings.cache._onlineComponentProvider
};
}
}

/**
Expand All @@ -308,6 +317,7 @@ export function configureFirestore(firestore: Firestore): void {
* persistence.
* @returns A `Promise` that represents successfully enabling persistent storage.
*/
// TODO(wuandy): mark obselete
export function enableIndexedDbPersistence(
firestore: Firestore,
persistenceSettings?: PersistenceSettings
Expand All @@ -316,6 +326,10 @@ export function enableIndexedDbPersistence(
verifyNotInitialized(firestore);

const client = ensureFirestoreConfigured(firestore);
if (client.uninitializedComponentsProvider) {
throw new FirestoreError(Code.INVALID_ARGUMENT, 'Already specified.');
}

const settings = firestore._freezeSettings();

const onlineComponentProvider = new OnlineComponentProvider();
Expand Down Expand Up @@ -353,13 +367,18 @@ export function enableIndexedDbPersistence(
* @returns A `Promise` that represents successfully enabling persistent
* storage.
*/
// TODO(wuandy): mark obselete
export function enableMultiTabIndexedDbPersistence(
firestore: Firestore
): Promise<void> {
firestore = cast(firestore, Firestore);
verifyNotInitialized(firestore);

const client = ensureFirestoreConfigured(firestore);
if (client.uninitializedComponentsProvider) {
throw new FirestoreError(Code.INVALID_ARGUMENT, 'Already specified.');
}

const settings = firestore._freezeSettings();

const onlineComponentProvider = new OnlineComponentProvider();
Expand Down
6 changes: 6 additions & 0 deletions packages/firestore/src/api/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

import { FirestoreSettings as LiteSettings } from '../lite-api/settings';

import { FirestoreLocalCache } from './cache_config';

export { DEFAULT_HOST } from '../lite-api/settings';

/**
Expand All @@ -30,6 +32,7 @@ export interface PersistenceSettings {
* Workers. Setting this to `true` will enable persistence, but cause other
* tabs using persistence to fail.
*/
// TODO(wuandy): Deprecate this
forceOwnership?: boolean;
}

Expand All @@ -48,8 +51,11 @@ export interface FirestoreSettings extends LiteSettings {
* The default value is 40 MB. The threshold must be set to at least 1 MB, and
* can be set to `CACHE_SIZE_UNLIMITED` to disable garbage collection.
*/
// TODO(wuandy): Deprecate this
cacheSizeBytes?: number;

cache?: FirestoreLocalCache;

/**
* Forces the SDK’s underlying network transport (WebChannel) to use
* long-polling. Each response from the backend will be closed immediately
Expand Down
46 changes: 33 additions & 13 deletions packages/firestore/src/core/firestore_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ export class FirestoreClient {
appCheckToken: string,
user: User
) => Promise<void> = () => Promise.resolve();
uninitializedComponentsProvider?: {
offline: OfflineComponentProvider;
online: OnlineComponentProvider;
};

offlineComponents?: OfflineComponentProvider;
onlineComponents?: OnlineComponentProvider;
Expand All @@ -120,12 +124,12 @@ export class FirestoreClient {
private authCredentials: CredentialsProvider<User>,
private appCheckCredentials: CredentialsProvider<string>,
/**
* Asynchronous queue responsible for all of our internal processing. When
* we get incoming work from the user (via public API) or the network
* (incoming GRPC messages), we should always schedule onto this queue.
* This ensures all of our work is properly serialized (e.g. we don't
* start processing a new operation while the previous one is waiting for
* an async I/O to complete).
* Asynchronous queue responsible for all of our internal processing. When //
* we get incoming work from the user (via public API) or the network //
* (incoming GRPC messages), we should always schedule onto this queue. //
* This ensures all of our work is properly serialized (e.g. we don't //
* start processing a new operation while the previous one is waiting for //
* an async I/O to complete). //
*/
public asyncQueue: AsyncQueue,
private databaseInfo: DatabaseInfo
Expand Down Expand Up @@ -265,11 +269,19 @@ async function ensureOfflineComponents(
client: FirestoreClient
): Promise<OfflineComponentProvider> {
if (!client.offlineComponents) {
logDebug(LOG_TAG, 'Using default OfflineComponentProvider');
await setOfflineComponentProvider(
client,
new MemoryOfflineComponentProvider()
);
if (client.uninitializedComponentsProvider) {
logDebug(LOG_TAG, 'Using user provided OfflineComponentProvider');
await setOfflineComponentProvider(
client,
client.uninitializedComponentsProvider.offline
);
} else {
logDebug(LOG_TAG, 'Using default OfflineComponentProvider');
await setOfflineComponentProvider(
client,
new MemoryOfflineComponentProvider()
);
}
}

return client.offlineComponents!;
Expand All @@ -279,8 +291,16 @@ async function ensureOnlineComponents(
client: FirestoreClient
): Promise<OnlineComponentProvider> {
if (!client.onlineComponents) {
logDebug(LOG_TAG, 'Using default OnlineComponentProvider');
await setOnlineComponentProvider(client, new OnlineComponentProvider());
if (client.uninitializedComponentsProvider) {
logDebug(LOG_TAG, 'Using user provided OnlineComponentProvider');
await setOnlineComponentProvider(
client,
client.uninitializedComponentsProvider.online
);
} else {
logDebug(LOG_TAG, 'Using default OnlineComponentProvider');
await setOnlineComponentProvider(client, new OnlineComponentProvider());
}
}

return client.onlineComponents!;
Expand Down
11 changes: 11 additions & 0 deletions packages/firestore/src/lite-api/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
* limitations under the License.
*/

import { FirestoreLocalCache } from '../api/cache_config';
import { CredentialsSettings } from '../api/credentials';
import {
LRU_COLLECTION_DISABLED,
Expand All @@ -23,6 +24,7 @@ import {
import { LRU_MINIMUM_CACHE_SIZE_BYTES } from '../local/lru_garbage_collector_impl';
import { Code, FirestoreError } from '../util/error';
import { validateIsNotUsedTogether } from '../util/input_validation';
import { logWarn } from '../util/log';

// settings() defaults:
export const DEFAULT_HOST = 'firestore.googleapis.com';
Expand Down Expand Up @@ -60,6 +62,8 @@ export interface PrivateSettings extends FirestoreSettings {
experimentalAutoDetectLongPolling?: boolean;
// Used in firestore@exp
useFetchStreams?: boolean;

cache?: FirestoreLocalCache;
}

/**
Expand All @@ -83,6 +87,7 @@ export class FirestoreSettingsImpl {
readonly ignoreUndefinedProperties: boolean;

readonly useFetchStreams: boolean;
readonly cache?: FirestoreLocalCache;

// Can be a google-auth-library or gapi client.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand All @@ -105,6 +110,12 @@ export class FirestoreSettingsImpl {

this.credentials = settings.credentials;
this.ignoreUndefinedProperties = !!settings.ignoreUndefinedProperties;
logWarn(
`Setting offline cache to ${JSON.stringify(
settings
)} from PrivateSettings`
);
this.cache = settings.cache;

if (settings.cacheSizeBytes === undefined) {
this.cacheSizeBytes = LRU_DEFAULT_CACHE_SIZE_BYTES;
Expand Down
8 changes: 6 additions & 2 deletions packages/firestore/test/integration/util/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@

import { isIndexedDBAvailable } from '@firebase/util';

import { indexedDbLocalCache } from '../../../src/api/cache_config';
import { logWarn } from '../../../src/util/log';

import {
collection,
doc,
Expand Down Expand Up @@ -184,10 +187,11 @@ export async function withTestDbsSettings(
const dbs: Firestore[] = [];

for (let i = 0; i < numDbs; i++) {
const db = newTestFirestore(newTestApp(projectId), settings);
logWarn(`set persistence from helper: ${persistence}`);
if (persistence) {
await enableIndexedDbPersistence(db);
settings.cache = indexedDbLocalCache();
}
const db = newTestFirestore(newTestApp(projectId), settings);
dbs.push(db);
}

Expand Down

0 comments on commit 2555b58

Please sign in to comment.