Skip to content

Commit

Permalink
initial fetch: Add a timeout to abort.
Browse files Browse the repository at this point in the history
Using `INITIAL_FETCH_ABORT` and `promiseTimeout`, which we set up in
the last few commits, abort and go to the accounts screen if the
initial fetch is taking too long.

Fixes: zulip#4165
  • Loading branch information
chrisbobbe committed Jun 18, 2020
1 parent b0c7fb7 commit ea9f61e
Showing 1 changed file with 21 additions and 8 deletions.
29 changes: 21 additions & 8 deletions src/message/fetchActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ import {
} from '../actionConstants';
import { FIRST_UNREAD_ANCHOR, LAST_MESSAGE_ANCHOR } from '../anchor';
import { ALL_PRIVATE_NARROW } from '../utils/narrow';
import { BackoffMachine } from '../utils/async';
import { BackoffMachine, promiseTimeout, TimeoutError } from '../utils/async';
import * as logging from '../utils/logging';
import { initNotifications } from '../notification/notificationActions';
import { addToOutbox, sendOutbox } from '../outbox/outboxActions';
import { realmInit } from '../realm/realmActions';
Expand Down Expand Up @@ -128,8 +129,6 @@ const initialFetchComplete = (): Action => ({
type: INITIAL_FETCH_COMPLETE,
});

// This will be used in an upcoming commit.
/* eslint-disable-next-line no-unused-vars */
const initialFetchAbort = (): Action => ({
type: INITIAL_FETCH_ABORT,
});
Expand Down Expand Up @@ -236,13 +235,21 @@ const fetchTopMostNarrow = () => async (dispatch: Dispatch, getState: GetState)
* handled further up in the call stack.
*/
export async function tryFetch<T>(func: () => Promise<T>): Promise<T> {
const MAX_TIME_MS: number = 60000;
const backoffMachine = new BackoffMachine();

const timeAtStart = Date.now();
// eslint-disable-next-line no-constant-condition
while (true) {
const msElapsed = Date.now() - timeAtStart;
// If the first attempt had a 60-second timeout, but it took 3
// seconds, then the second attempt should have a 57-second
// timeout. Etc.
const timeLimitMs = MAX_TIME_MS - msElapsed;
try {
return await func();
return await promiseTimeout(func(), timeLimitMs);
} catch (e) {
if (isClientError(e)) {
if (isClientError(e) || e instanceof TimeoutError) {
throw e;
}
await backoffMachine.wait();
Expand Down Expand Up @@ -294,9 +301,15 @@ export const doInitialFetch = () => async (dispatch: Dispatch, getState: GetStat
tryFetch(() => api.getServerSettings(auth.realm)),
]);
} catch (e) {
// This should only happen on a 4xx HTTP status, which should only
// happen when `auth` is no longer valid. No use retrying; just log out.
dispatch(logout());
if (isClientError(e)) {
dispatch(logout());
} else if (e instanceof TimeoutError) {
dispatch(initialFetchAbort());
} else {
logging.warn(e, {
message: 'Unexpected error during initial fetch and serverSettings fetch.',
});
}
return;
}

Expand Down

0 comments on commit ea9f61e

Please sign in to comment.