Skip to content

Make newConnection() synchronous #3569

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Aug 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 10 additions & 11 deletions packages/firestore/exp/src/api/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,14 @@ export function runTransaction<T>(
updateFunction: (transaction: firestore.Transaction) => Promise<T>
): Promise<T> {
const firestoreClient = cast(firestore, Firestore);
return getDatastore(firestoreClient).then(async datastore => {
const deferred = new Deferred<T>();
new TransactionRunner<T>(
new AsyncQueue(),
datastore,
internalTransaction =>
updateFunction(new Transaction(firestoreClient, internalTransaction)),
deferred
).run();
return deferred.promise;
});
const datastore = getDatastore(firestoreClient);
const deferred = new Deferred<T>();
new TransactionRunner<T>(
new AsyncQueue(),
datastore,
internalTransaction =>
updateFunction(new Transaction(firestoreClient, internalTransaction)),
deferred
).run();
return deferred.promise;
}
29 changes: 16 additions & 13 deletions packages/firestore/lite/src/api/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,14 @@ export const DEFAULT_SSL = true;
* An instance map that ensures only one Datastore exists per Firestore
* instance.
*/
const datastoreInstances = new Map<Firestore, Promise<Datastore>>();
const datastoreInstances = new Map<Firestore, Datastore>();

/**
* Returns an initialized and started Datastore for the given Firestore
* instance. Callers must invoke removeDatastore() when the Firestore
* instance is terminated.
*/
export function getDatastore(firestore: Firestore): Promise<Datastore> {
export function getDatastore(firestore: Firestore): Datastore {
if (firestore._terminated) {
throw new FirestoreError(
Code.FAILED_PRECONDITION,
Expand All @@ -61,13 +61,16 @@ export function getDatastore(firestore: Firestore): Promise<Datastore> {
settings.ssl ?? DEFAULT_SSL,
/* forceLongPolling= */ false
);
const datastorePromise = newConnection(databaseInfo).then(connection => {
const serializer = newSerializer(databaseInfo.databaseId);
const datastore = newDatastore(firestore._credentials, serializer);
datastore.start(connection);
return datastore;
});
datastoreInstances.set(firestore, datastorePromise);

const connection = newConnection(databaseInfo);
const serializer = newSerializer(databaseInfo.databaseId);
const datastore = newDatastore(
firestore._credentials,
connection,
serializer
);

datastoreInstances.set(firestore, datastore);
}
return datastoreInstances.get(firestore)!;
}
Expand All @@ -76,11 +79,11 @@ export function getDatastore(firestore: Firestore): Promise<Datastore> {
* Removes all components associated with the provided instance. Must be called
* when the Firestore instance is terminated.
*/
export async function removeComponents(firestore: Firestore): Promise<void> {
const datastorePromise = await datastoreInstances.get(firestore);
if (datastorePromise) {
export function removeComponents(firestore: Firestore): void {
const datastore = datastoreInstances.get(firestore);
if (datastore) {
logDebug(LOG_TAG, 'Removing Datastore');
datastoreInstances.delete(firestore);
return (await datastorePromise).termiate();
datastore.terminate();
}
}
3 changes: 2 additions & 1 deletion packages/firestore/lite/src/api/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ export class Firestore
* Only ever called once.
*/
protected _terminate(): Promise<void> {
return removeComponents(this);
removeComponents(this);
return Promise.resolve();
}

// TODO(firestoreexp): `deleteApp()` should call the delete method above,
Expand Down
60 changes: 28 additions & 32 deletions packages/firestore/lite/src/api/reference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -547,8 +547,9 @@ export function getDoc<T>(
reference: firestore.DocumentReference<T>
): Promise<firestore.DocumentSnapshot<T>> {
const ref = cast<DocumentReference<T>>(reference, DocumentReference);
return getDatastore(ref.firestore).then(async datastore => {
const result = await invokeBatchGetDocumentsRpc(datastore, [ref._key]);

const datastore = getDatastore(ref.firestore);
return invokeBatchGetDocumentsRpc(datastore, [ref._key]).then(result => {
hardAssert(result.length === 1, 'Expected a single document result');
const maybeDocument = result[0];
return new DocumentSnapshot<T>(
Expand All @@ -563,21 +564,22 @@ export function getDoc<T>(
export function getDocs<T>(
query: firestore.Query<T>
): Promise<firestore.QuerySnapshot<T>> {
const internalQuery = cast<Query<T>>(query, Query);
validateHasExplicitOrderByForLimitToLast(internalQuery._query);
return getDatastore(internalQuery.firestore).then(async datastore => {
const result = await invokeRunQueryRpc(datastore, internalQuery._query);
const queryImpl = cast<Query<T>>(query, Query);
validateHasExplicitOrderByForLimitToLast(queryImpl._query);

const datastore = getDatastore(queryImpl.firestore);
return invokeRunQueryRpc(datastore, queryImpl._query).then(result => {
const docs = result.map(
doc =>
new QueryDocumentSnapshot<T>(
internalQuery.firestore,
queryImpl.firestore,
doc.key,
doc,
internalQuery.converter
queryImpl.converter
)
);

if (internalQuery._query.hasLimitToLast()) {
if (queryImpl._query.hasLimitToLast()) {
// Limit to last queries reverse the orderBy constraint that was
// specified by the user. As such, we need to reverse the order of the
// results to return the documents in the expected order.
Expand Down Expand Up @@ -619,11 +621,10 @@ export function setDoc<T>(
options
);

return getDatastore(ref.firestore).then(datastore =>
invokeCommitRpc(
datastore,
parsed.toMutations(ref._key, Precondition.none())
)
const datastore = getDatastore(ref.firestore);
return invokeCommitRpc(
datastore,
parsed.toMutations(ref._key, Precondition.none())
);
}

Expand Down Expand Up @@ -668,23 +669,21 @@ export function updateDoc(
);
}

return getDatastore(ref.firestore).then(datastore =>
invokeCommitRpc(
datastore,
parsed.toMutations(ref._key, Precondition.exists(true))
)
const datastore = getDatastore(ref.firestore);
return invokeCommitRpc(
datastore,
parsed.toMutations(ref._key, Precondition.exists(true))
);
}

export function deleteDoc(
reference: firestore.DocumentReference
): Promise<void> {
const ref = cast<DocumentReference<unknown>>(reference, DocumentReference);
return getDatastore(ref.firestore).then(datastore =>
invokeCommitRpc(datastore, [
new DeleteMutation(ref._key, Precondition.none())
])
);
const datastore = getDatastore(ref.firestore);
return invokeCommitRpc(datastore, [
new DeleteMutation(ref._key, Precondition.none())
]);
}

export function addDoc<T>(
Expand All @@ -706,14 +705,11 @@ export function addDoc<T>(
{}
);

return getDatastore(collRef.firestore)
.then(datastore =>
invokeCommitRpc(
datastore,
parsed.toMutations(docRef._key, Precondition.exists(false))
)
)
.then(() => docRef);
const datastore = getDatastore(collRef.firestore);
return invokeCommitRpc(
datastore,
parsed.toMutations(docRef._key, Precondition.exists(false))
).then(() => docRef);
}

export function refEqual<T>(
Expand Down
21 changes: 10 additions & 11 deletions packages/firestore/lite/src/api/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,15 +178,14 @@ export function runTransaction<T>(
updateFunction: (transaction: firestore.Transaction) => Promise<T>
): Promise<T> {
const firestoreClient = cast(firestore, Firestore);
return getDatastore(firestoreClient).then(async datastore => {
const deferred = new Deferred<T>();
new TransactionRunner<T>(
new AsyncQueue(),
datastore,
internalTransaction =>
updateFunction(new Transaction(firestoreClient, internalTransaction)),
deferred
).run();
return deferred.promise;
});
const datastore = getDatastore(firestoreClient);
const deferred = new Deferred<T>();
new TransactionRunner<T>(
new AsyncQueue(),
datastore,
internalTransaction =>
updateFunction(new Transaction(firestoreClient, internalTransaction)),
deferred
).run();
return deferred.promise;
}
5 changes: 2 additions & 3 deletions packages/firestore/lite/src/api/write_batch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,8 @@ export function writeBatch(
firestore: firestore.FirebaseFirestore
): firestore.WriteBatch {
const firestoreImpl = cast(firestore, Firestore);
const datastore = getDatastore(firestoreImpl);
return new WriteBatch(firestoreImpl, writes =>
getDatastore(firestoreImpl).then(datastore =>
invokeCommitRpc(datastore, writes)
)
invokeCommitRpc(datastore, writes)
);
}
11 changes: 2 additions & 9 deletions packages/firestore/src/core/component_provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ import { newConnection, newConnectivityMonitor } from '../platform/connection';
import { newSerializer } from '../platform/serializer';
import { getDocument, getWindow } from '../platform/dom';
import { CredentialsProvider } from '../api/credentials';
import { Connection } from '../remote/connection';

const MEMORY_ONLY_PERSISTENCE_ERROR_MESSAGE =
'You are using the memory-only build of Firestore. Persistence support is ' +
Expand Down Expand Up @@ -329,9 +328,6 @@ export class OnlineComponentProvider {
this.localStore = offlineComponentProvider.localStore;
this.sharedClientState = offlineComponentProvider.sharedClientState;
this.datastore = this.createDatastore(cfg);
const connection = await this.loadConnection(cfg);
this.datastore.start(connection);

this.remoteStore = this.createRemoteStore(cfg);
this.syncEngine = this.createSyncEngine(cfg);
this.eventManager = this.createEventManager(cfg);
Expand All @@ -348,17 +344,14 @@ export class OnlineComponentProvider {
await this.remoteStore.applyPrimaryState(this.syncEngine.isPrimaryClient);
}

protected loadConnection(cfg: ComponentConfiguration): Promise<Connection> {
return newConnection(cfg.databaseInfo);
}

createEventManager(cfg: ComponentConfiguration): EventManager {
return new EventManager(this.syncEngine);
}

createDatastore(cfg: ComponentConfiguration): Datastore {
const serializer = newSerializer(cfg.databaseInfo.databaseId);
return newDatastore(cfg.credentials, serializer);
const connection = newConnection(cfg.databaseInfo);
return newDatastore(cfg.credentials, connection, serializer);
}

createRemoteStore(cfg: ComponentConfiguration): RemoteStore {
Expand Down
4 changes: 2 additions & 2 deletions packages/firestore/src/platform/browser/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ import { BrowserConnectivityMonitor } from './connectivity_monitor';
import { NoopConnectivityMonitor } from '../../remote/connectivity_monitor_noop';

/** Initializes the WebChannelConnection for the browser. */
export function newConnection(databaseInfo: DatabaseInfo): Promise<Connection> {
return Promise.resolve(new WebChannelConnection(databaseInfo));
export function newConnection(databaseInfo: DatabaseInfo): Connection {
return new WebChannelConnection(databaseInfo);
}

/** Return the Platform-specific connectivity monitor. */
Expand Down
4 changes: 2 additions & 2 deletions packages/firestore/src/platform/browser_lite/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ import { FetchConnection } from './fetch_connection';
export { newConnectivityMonitor } from '../browser/connection';

/** Initializes the HTTP connection for the REST API. */
export function newConnection(databaseInfo: DatabaseInfo): Promise<Connection> {
return Promise.resolve(new FetchConnection(databaseInfo, fetch.bind(null)));
export function newConnection(databaseInfo: DatabaseInfo): Connection {
return new FetchConnection(databaseInfo, fetch.bind(null));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain why we need to bind fetch to null here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first argument to any JavaScript function is the "this context", which is this case is not used. It still has to be explicitly set as we are using "bind". If we just pass "bind" as this then calling this function pointer later would use the first argument as the "this" context... and essentially drop it.
Note that we could also do fetch.bind(this), which would be equivalent to using an array function here.

As an example, if we have a function like this:

class Foo {
bar = 'bar';

function foo() {
  console.log(this.bar);
}

function test() {
   this.foo(); // Prints 'bar';
   (() => this.foo())() // Prints 'bar', since arrow functions retain `this` context.
   this.foo().bind({bar:'foo'})(); // Different `this`. Prints 'foo'.
}

}
3 changes: 1 addition & 2 deletions packages/firestore/src/platform/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ export function newConnectivityMonitor(): ConnectivityMonitor {
return platform.newConnectivityMonitor();
}

// TODO(firestorexp): This doesn't need to return a Promise
export function newConnection(databaseInfo: DatabaseInfo): Promise<Connection> {
export function newConnection(databaseInfo: DatabaseInfo): Connection {
return platform.newConnection(databaseInfo);
}
4 changes: 2 additions & 2 deletions packages/firestore/src/platform/node/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ import { Connection } from '../../remote/connection';
import { NoopConnectivityMonitor } from '../../remote/connectivity_monitor_noop';

/** Loads the GRPC stack */
export function newConnection(databaseInfo: DatabaseInfo): Promise<Connection> {
export function newConnection(databaseInfo: DatabaseInfo): Connection {
const protos = loadProtos();
return Promise.resolve(new GrpcConnection(protos, databaseInfo));
return new GrpcConnection(protos, databaseInfo);
}

/** Return the Platform-specific connectivity monitor. */
Expand Down
6 changes: 3 additions & 3 deletions packages/firestore/src/platform/node_lite/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ import { Connection } from '../../remote/connection';
export { newConnectivityMonitor } from '../browser/connection';

/** Initializes the HTTP connection for the REST API. */
export function newConnection(databaseInfo: DatabaseInfo): Promise<Connection> {
// node-fetch is meant to be API compatible with `fetch`, but its type don't
export function newConnection(databaseInfo: DatabaseInfo): Connection {
// node-fetch is meant to be API compatible with `fetch`, but its type doesn't
// match 100%.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return Promise.resolve(new FetchConnection(databaseInfo, nodeFetch as any));
return new FetchConnection(databaseInfo, nodeFetch as any);
}
15 changes: 5 additions & 10 deletions packages/firestore/src/remote/datastore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,20 +47,19 @@ import { Query, queryToTarget } from '../core/query';
* for the rest of the client SDK architecture to consume.
*/
export abstract class Datastore {
abstract start(connection: Connection): void;
abstract termiate(): Promise<void>;
abstract terminate(): void;
}

/**
* An implementation of Datastore that exposes additional state for internal
* consumption.
*/
class DatastoreImpl extends Datastore {
connection!: Connection;
terminated = false;

constructor(
readonly credentials: CredentialsProvider,
readonly connection: Connection,
readonly serializer: JsonProtoSerializer
) {
super();
Expand All @@ -76,11 +75,6 @@ class DatastoreImpl extends Datastore {
}
}

start(connection: Connection): void {
debugAssert(!this.connection, 'Datastore.start() already called');
this.connection = connection;
}

/** Gets an auth token and invokes the provided RPC. */
invokeRPC<Req, Resp>(
rpcName: string,
Expand Down Expand Up @@ -131,7 +125,7 @@ class DatastoreImpl extends Datastore {
});
}

async termiate(): Promise<void> {
terminate(): void {
this.terminated = false;
}
}
Expand All @@ -140,9 +134,10 @@ class DatastoreImpl extends Datastore {
// firestore-exp client.
export function newDatastore(
credentials: CredentialsProvider,
connection: Connection,
serializer: JsonProtoSerializer
): Datastore {
return new DatastoreImpl(credentials, serializer);
return new DatastoreImpl(credentials, connection, serializer);
}

export async function invokeCommitRpc(
Expand Down
Loading