diff options
| author | Oskar Nyberg <oskar@mullvad.net> | 2020-04-21 11:24:33 +0200 |
|---|---|---|
| committer | Oskar Nyberg <oskar@mullvad.net> | 2020-04-21 12:06:06 +0200 |
| commit | 513b78e1f665f930397a123764de2b48309da3e3 (patch) | |
| tree | 09fd98542d83e4da1506c6ebfbf8c6fe21618da1 /gui/src | |
| parent | 8f837f09c5658dad894ee2b974af5316fc1cb0a9 (diff) | |
| download | mullvadvpn-513b78e1f665f930397a123764de2b48309da3e3.tar.xz mullvadvpn-513b78e1f665f930397a123764de2b48309da3e3.zip | |
Refactor account data cache retry and delay logic
Diffstat (limited to 'gui/src')
| -rw-r--r-- | gui/src/main/account-data-cache.ts | 74 | ||||
| -rw-r--r-- | gui/src/main/index.ts | 6 | ||||
| -rw-r--r-- | gui/src/shared/scheduler.ts | 12 |
3 files changed, 55 insertions, 37 deletions
diff --git a/gui/src/main/account-data-cache.ts b/gui/src/main/account-data-cache.ts index f4864743a8..ac829ca0f3 100644 --- a/gui/src/main/account-data-cache.ts +++ b/gui/src/main/account-data-cache.ts @@ -2,16 +2,14 @@ import log from 'electron-log'; import moment from 'moment'; import { AccountToken, IAccountData } from '../shared/daemon-rpc-types'; import consumePromise from '../shared/promise'; +import { Scheduler } from '../shared/scheduler'; +import { InvalidAccountError } from './errors'; const EXPIRED_ACCOUNT_REFRESH_PERIOD = 60_000; -export enum AccountFetchRetryAction { - stop, - retry, -} interface IAccountFetchWatcher { onFinish: () => void; - onError: (error: Error) => AccountFetchRetryAction; + onError: (error: Error) => void; } // An account data cache that helps to throttle RPC requests to get_account_data and retain the @@ -20,8 +18,8 @@ export default class AccountDataCache { private currentAccount?: AccountToken; private expiresAt?: Date; private performingFetch = false; - private fetchAttempt = 0; - private fetchRetryTimeout?: NodeJS.Timeout; + private waitStrategy = new WaitStrategy(); + private fetchRetryScheduler = new Scheduler(); private watchers: IAccountFetchWatcher[] = []; constructor( @@ -42,9 +40,9 @@ export default class AccountDataCache { this.watchers.push(watcher); } - this.clearFetchRetryTimeout(); + this.fetchRetryScheduler.cancel(); // If a scheduled retry is cancelled the fetchAttempt shouldn't be increased. - this.fetchAttempt = Math.max(0, this.fetchAttempt - 1); + this.waitStrategy.decrease(); // Only fetch if there's no fetch for this account number in progress. if (!this.performingFetch) { @@ -56,8 +54,8 @@ export default class AccountDataCache { } public invalidate() { - this.clearFetchRetryTimeout(); - this.fetchAttempt = 0; + this.fetchRetryScheduler.cancel(); + this.waitStrategy.reset(); this.performingFetch = false; this.expiresAt = undefined; @@ -67,13 +65,6 @@ export default class AccountDataCache { }); } - private clearFetchRetryTimeout() { - if (this.fetchRetryTimeout) { - clearTimeout(this.fetchRetryTimeout); - this.fetchRetryTimeout = undefined; - } - } - private setValue(value: IAccountData) { this.expiresAt = new Date(Date.now() + 60 * 1000); // 60s expiration this.updateHandler(value); @@ -95,6 +86,7 @@ export default class AccountDataCache { if (this.currentAccount === accountToken) { this.setValue(accountData); this.scheduleRefetchIfExpired(accountToken, accountData); + this.waitStrategy.reset(); this.performingFetch = false; } } catch (error) { @@ -113,24 +105,15 @@ export default class AccountDataCache { } private handleFetchError(accountToken: AccountToken, error: Error) { - let shouldRetry = true; - - this.notifyWatchers((watcher) => { - if (watcher.onError(error) === AccountFetchRetryAction.stop) { - shouldRetry = false; - } - }); - - if (shouldRetry) { + this.notifyWatchers((w) => w.onError(error)); + if (!(error instanceof InvalidAccountError)) { this.scheduleRetry(accountToken); } } private scheduleRetry(accountToken: AccountToken) { - this.fetchAttempt += 1; - - // Max delay: 2^11 = 2048 - const delay = Math.pow(2, Math.min(this.fetchAttempt + 2, 11)) * 1000; + this.waitStrategy.increase(); + const delay = this.waitStrategy.delay(); log.warn(`Failed to fetch account data. Retrying in ${delay} ms`); @@ -138,8 +121,7 @@ export default class AccountDataCache { } private scheduleFetch(accountToken: AccountToken, delay: number) { - this.fetchRetryTimeout = global.setTimeout(() => { - this.fetchRetryTimeout = undefined; + this.fetchRetryScheduler.schedule(() => { consumePromise(this.performFetch(accountToken)); }, delay); } @@ -148,3 +130,29 @@ export default class AccountDataCache { this.watchers.splice(0).forEach(notify); } } + +const MAX_ATTEMPT = 9; + +class WaitStrategy { + private counter = 0; + + public increase() { + if (this.counter < MAX_ATTEMPT) { + this.counter += 1; + } + } + public decrease() { + if (this.counter > 0) { + this.counter -= 1; + } + } + + public reset() { + this.counter = 0; + } + + public delay(): number { + // Max delay: 2^11 = 2048 + return Math.pow(2, this.counter + 2) * 1000; + } +} diff --git a/gui/src/main/index.ts b/gui/src/main/index.ts index 61b689456c..6f9185688a 100644 --- a/gui/src/main/index.ts +++ b/gui/src/main/index.ts @@ -36,7 +36,7 @@ import { setupLogging, } from '../shared/logging'; import consumePromise from '../shared/promise'; -import AccountDataCache, { AccountFetchRetryAction } from './account-data-cache'; +import AccountDataCache from './account-data-cache'; import { getOpenAtLogin, setOpenAtLogin } from './autostart'; import { ConnectionObserver, @@ -1139,13 +1139,11 @@ class ApplicationMain { this.accountDataCache.invalidate(); this.accountDataCache.fetch(accountToken, { onFinish: () => resolve({ status: 'verified' }), - onError: (error): AccountFetchRetryAction => { + onError: (error) => { if (error instanceof InvalidAccountError) { reject(error); - return AccountFetchRetryAction.stop; } else { resolve({ status: 'deferred', error }); - return AccountFetchRetryAction.retry; } }, }); diff --git a/gui/src/shared/scheduler.ts b/gui/src/shared/scheduler.ts new file mode 100644 index 0000000000..af28a7e95b --- /dev/null +++ b/gui/src/shared/scheduler.ts @@ -0,0 +1,12 @@ +export class Scheduler { + private timer?: number; + + public schedule(action: () => void, delay: number) { + this.cancel(); + this.timer = setTimeout(action, delay); + } + + public cancel() { + clearTimeout(this.timer); + } +} |
