Skip to content

Commit b12af44

Browse files
authored
Forced get to wait until db is online (#6340)
Fixes #6036
1 parent 69e2ee0 commit b12af44

File tree

8 files changed

+79
-34
lines changed

8 files changed

+79
-34
lines changed

.changeset/metal-spies-shave.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@firebase/database": patch
3+
"@firebase/util": patch
4+
---
5+
6+
Forced `get()` to wait until db is online to resolve.

common/api-review/util.api.md

+5
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,11 @@ export function ordinal(i: number): string;
346346
// @public (undocumented)
347347
export type PartialObserver<T> = Partial<Observer<T>>;
348348

349+
// Warning: (ae-internal-missing-underscore) The name "promiseWithTimeout" should be prefixed with an underscore because the declaration is marked as @internal
350+
//
351+
// @internal
352+
export function promiseWithTimeout<T>(promise: Promise<T>, timeInMS?: number): Promise<T>;
353+
349354
// Warning: (ae-missing-release-tag) "querystring" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
350355
//
351356
// @public

packages/database-compat/test/query.test.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
* limitations under the License.
1616
*/
1717

18+
import { promiseWithTimeout } from '@firebase/util';
1819
import { expect, use } from 'chai';
1920
import chaiAsPromised from 'chai-as-promised';
2021
import * as _ from 'lodash';
@@ -3226,7 +3227,8 @@ describe('Query Tests', () => {
32263227
const node = getRandomNode() as Reference;
32273228
node.database.goOffline();
32283229
try {
3229-
await expect(node.get()).to.eventually.be.rejected;
3230+
const getPromise = promiseWithTimeout(node.get());
3231+
await expect(getPromise).to.eventually.be.rejected;
32303232
} finally {
32313233
node.database.goOnline();
32323234
}
@@ -3392,8 +3394,8 @@ describe('Query Tests', () => {
33923394
expect(snapshot.val()).to.deep.equal({ data: '1' });
33933395
reader.database.goOffline();
33943396
try {
3395-
await expect(reader.child('foo/notCached').get()).to.eventually.be
3396-
.rejected;
3397+
await expect(promiseWithTimeout(reader.child('foo/notCached').get())).to
3398+
.eventually.be.rejected;
33973399
} finally {
33983400
reader.database.goOnline();
33993401
}

packages/database/src/core/PersistentConnection.ts

-17
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ import { QueryContext } from './view/EventRegistration';
4444

4545
const RECONNECT_MIN_DELAY = 1000;
4646
const RECONNECT_MAX_DELAY_DEFAULT = 60 * 5 * 1000; // 5 minutes in milliseconds (Case: 1858)
47-
const GET_CONNECT_TIMEOUT = 3 * 1000;
4847
const RECONNECT_MAX_DELAY_FOR_ADMINS = 30 * 1000; // 30 seconds for admin clients (likely to be a backend server)
4948
const RECONNECT_DELAY_MULTIPLIER = 1.3;
5049
const RECONNECT_DELAY_RESET_TIMEOUT = 30000; // Reset delay back to MIN_DELAY after being connected for 30sec.
@@ -216,22 +215,6 @@ export class PersistentConnection extends ServerActions {
216215
this.outstandingGetCount_++;
217216
const index = this.outstandingGets_.length - 1;
218217

219-
if (!this.connected_) {
220-
setTimeout(() => {
221-
const get = this.outstandingGets_[index];
222-
if (get === undefined || outstandingGet !== get) {
223-
return;
224-
}
225-
delete this.outstandingGets_[index];
226-
this.outstandingGetCount_--;
227-
if (this.outstandingGetCount_ === 0) {
228-
this.outstandingGets_ = [];
229-
}
230-
this.log_('get ' + index + ' timed out on connection');
231-
deferred.reject(new Error('Client is offline.'));
232-
}, GET_CONNECT_TIMEOUT);
233-
}
234-
235218
if (this.connected_) {
236219
this.sendGet_(index);
237220
}

packages/database/test/exp/integration.test.ts

+29-14
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717

1818
import { initializeApp, deleteApp } from '@firebase/app';
1919
import { Deferred } from '@firebase/util';
20-
import { expect } from 'chai';
20+
import { expect, use } from 'chai';
21+
import chaiAsPromised from 'chai-as-promised';
2122

2223
import {
2324
get,
@@ -44,6 +45,8 @@ import {
4445
waitFor
4546
} from '../helpers/util';
4647

48+
use(chaiAsPromised);
49+
4750
export function createTestApp() {
4851
return initializeApp({ databaseURL: DATABASE_URL });
4952
}
@@ -257,26 +260,38 @@ describe('Database@exp Tests', () => {
257260
expect(events[0]).to.equal('a');
258261
});
259262

260-
it('Can goOffline/goOnline', async () => {
261-
const db = getDatabase(defaultApp);
262-
goOffline(db);
263-
try {
264-
await get(ref(db, 'foo/bar'));
265-
expect.fail('Should have failed since we are offline');
266-
} catch (e) {
267-
expect(e.message).to.equal('Error: Client is offline.');
268-
}
269-
goOnline(db);
270-
await get(ref(db, 'foo/bar'));
271-
});
272-
273263
it('Can delete app', async () => {
274264
const db = getDatabase(defaultApp);
275265
await deleteApp(defaultApp);
276266
expect(() => ref(db)).to.throw('Cannot call ref on a deleted database.');
277267
defaultApp = undefined;
278268
});
279269

270+
it('blocks get requests until the database is online', async () => {
271+
const db = getDatabase(defaultApp);
272+
const r = ref(db, 'foo3');
273+
const initial = {
274+
test: 1
275+
};
276+
await set(r, initial);
277+
goOffline(db);
278+
const pendingGet = get(r);
279+
let resolvedData: any = null;
280+
pendingGet.then(
281+
data => {
282+
resolvedData = data;
283+
},
284+
() => {
285+
resolvedData = new Error('rejected');
286+
}
287+
);
288+
await waitFor(2000);
289+
expect(resolvedData).to.equal(null);
290+
goOnline(db);
291+
await waitFor(2000);
292+
expect(resolvedData.val()).to.deep.equal(initial);
293+
});
294+
280295
it('Can listen to transaction changes', async () => {
281296
// Repro for https://github.com/firebase/firebase-js-sdk/issues/5195
282297
let latestValue = 0;

packages/util/index.node.ts

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export * from './src/errors';
3131
export * from './src/json';
3232
export * from './src/jwt';
3333
export * from './src/obj';
34+
export * from './src/promise';
3435
export * from './src/query';
3536
export * from './src/sha1';
3637
export * from './src/subscribe';

packages/util/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export * from './src/errors';
2626
export * from './src/json';
2727
export * from './src/jwt';
2828
export * from './src/obj';
29+
export * from './src/promise';
2930
export * from './src/query';
3031
export * from './src/sha1';
3132
export * from './src/subscribe';

packages/util/src/promise.ts

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* @license
3+
* Copyright 2022 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { Deferred } from './deferred';
19+
20+
/**
21+
* Rejects if the given promise doesn't resolve in timeInMS milliseconds.
22+
* @internal
23+
*/
24+
export function promiseWithTimeout<T>(
25+
promise: Promise<T>,
26+
timeInMS = 2000
27+
): Promise<T> {
28+
const deferredPromise = new Deferred<T>();
29+
setTimeout(() => deferredPromise.reject('timeout!'), timeInMS);
30+
promise.then(deferredPromise.resolve, deferredPromise.reject);
31+
return deferredPromise.promise;
32+
}

0 commit comments

Comments
 (0)