diff options
Diffstat (limited to 'gui/src')
| -rw-r--r-- | gui/src/main/account-data-cache.ts | 82 | ||||
| -rw-r--r-- | gui/src/main/index.ts | 6 | ||||
| -rw-r--r-- | gui/src/shared/scheduler.ts | 12 |
3 files changed, 67 insertions, 33 deletions
diff --git a/gui/src/main/account-data-cache.ts b/gui/src/main/account-data-cache.ts index 7db2c05ca8..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 @@ -19,8 +17,9 @@ interface IAccountFetchWatcher { export default class AccountDataCache { private currentAccount?: AccountToken; private expiresAt?: Date; - private fetchAttempt = 0; - private fetchRetryTimeout?: NodeJS.Timeout; + private performingFetch = false; + private waitStrategy = new WaitStrategy(); + private fetchRetryScheduler = new Scheduler(); private watchers: IAccountFetchWatcher[] = []; constructor( @@ -35,25 +34,30 @@ export default class AccountDataCache { this.currentAccount = accountToken; } - // Only fetch is value has expired + // Only fetch if value has expired if (this.isExpired()) { if (watcher) { this.watchers.push(watcher); } - consumePromise(this.performFetch(accountToken)); + this.fetchRetryScheduler.cancel(); + // If a scheduled retry is cancelled the fetchAttempt shouldn't be increased. + this.waitStrategy.decrease(); + + // Only fetch if there's no fetch for this account number in progress. + if (!this.performingFetch) { + consumePromise(this.performFetch(accountToken)); + } } else if (watcher) { watcher.onFinish(); } } public invalidate() { - if (this.fetchRetryTimeout) { - clearTimeout(this.fetchRetryTimeout); - this.fetchRetryTimeout = undefined; - this.fetchAttempt = 0; - } + this.fetchRetryScheduler.cancel(); + this.waitStrategy.reset(); + this.performingFetch = false; this.expiresAt = undefined; this.updateHandler(); this.notifyWatchers((watcher) => { @@ -72,6 +76,7 @@ export default class AccountDataCache { } private async performFetch(accountToken: AccountToken) { + this.performingFetch = true; try { // it's possible for invalidate() to be called or for a fetch for a different account token // to start before this fetch completes, so checking if the current account token is the one @@ -81,10 +86,13 @@ export default class AccountDataCache { if (this.currentAccount === accountToken) { this.setValue(accountData); this.scheduleRefetchIfExpired(accountToken, accountData); + this.waitStrategy.reset(); + this.performingFetch = false; } } catch (error) { if (this.currentAccount === accountToken) { this.handleFetchError(accountToken, error); + this.performingFetch = false; } } } @@ -97,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`); @@ -122,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); } @@ -132,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); + } +} |
