diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2018-09-07 18:58:53 +0300 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2018-09-10 17:56:02 +0300 |
| commit | 802bc64af347069b1d29ba982bbbac7fd812cc79 (patch) | |
| tree | b5331aee7afc41a7efa1ff8fdf981bf7cb245e96 /gui | |
| parent | 35cb43ea97ff4c82dc981d3c06093e904a85831a (diff) | |
| download | mullvadvpn-802bc64af347069b1d29ba982bbbac7fd812cc79.tar.xz mullvadvpn-802bc64af347069b1d29ba982bbbac7fd812cc79.zip | |
Revamp account data cache
Diffstat (limited to 'gui')
| -rw-r--r-- | gui/packages/desktop/src/renderer/app.js | 81 |
1 files changed, 54 insertions, 27 deletions
diff --git a/gui/packages/desktop/src/renderer/app.js b/gui/packages/desktop/src/renderer/app.js index 53fea2151e..7bf5cdf534 100644 --- a/gui/packages/desktop/src/renderer/app.js +++ b/gui/packages/desktop/src/renderer/app.js @@ -46,7 +46,9 @@ export default class AppRenderer { _memoryHistory = createMemoryHistory(); _reduxStore: ReduxStore; _reduxActions: *; - _accountDataState = new AccountDataState(); + _accountDataCache = new AccountDataCache((accountToken) => { + return this._daemonRpc.getAccountData(accountToken); + }); _connectedToDaemon = false; _accountToken: ?AccountToken; _tunnelState: ?TunnelState; @@ -231,8 +233,8 @@ export default class AppRenderer { actions.account.loggedOut(); actions.history.replace('/login'); - // reset account data state on log out - this._accountDataState = new AccountDataState(); + // invalidate account data cache on log out + this._accountDataCache.invalidate(); } catch (e) { log.info('Failed to logout: ', e.message); } @@ -313,20 +315,12 @@ export default class AppRenderer { async updateAccountExpiry() { const actions = this._reduxActions; - const accountDataState = this._accountDataState; - - // Bail if something else requested an update to account data - // or if account data cache was updated recently. - if (accountDataState.isUpdating() || !accountDataState.needsUpdate()) { - return; - } + const accountDataCache = this._accountDataCache; try { const accountToken = this._accountToken; if (accountToken) { - const accountData = await accountDataState.update(() => { - return this._daemonRpc.getAccountData(accountToken); - }); + const accountData = await accountDataCache.fetch(accountToken); actions.account.updateAccountExpiry(accountData.expiry); } else { throw new NoAccountError(); @@ -629,33 +623,66 @@ export default class AppRenderer { } } -// Helper class to keep track of account data updates -class AccountDataState { +// An account data cache that helps to throttle RPC requests to get_account_data and retain the +// cached value for 1 minute. +class AccountDataCache { + _executingPromise: ?Promise<AccountData>; + _value: ?AccountData; _expiresAt: ?Date; - _isUpdating = false; + _fetch: (AccountToken) => Promise<AccountData>; - isUpdating() { - return this._isUpdating; + constructor(fetch: (AccountToken) => Promise<AccountData>) { + this._fetch = fetch; } - needsUpdate() { - return !this._expiresAt || this._expiresAt < new Date(); - } + async fetch(accountToken: AccountToken): Promise<AccountData> { + // return the same promise if still fetching from remote + const executingPromise = this._executingPromise; + if (executingPromise) { + return executingPromise; + } - async update(fn: () => Promise<AccountData>): Promise<AccountData> { - this._isUpdating = true; + // return the received value if not expired yet + const currentValue = this._value; + if (currentValue && !this._isExpired()) { + return currentValue; + } try { - const accountData = await fn(); - this._expiresAt = new Date(Date.now() + 60 * 1000); // 60s expiration + const nextPromise = this._fetch(accountToken); + this._executingPromise = nextPromise; - return accountData; + const accountData = await nextPromise; + + // it's possible that invalidate() was called before this promise was resolved. + // discard the result of "orphaned" promise. + if (this._executingPromise === nextPromise) { + this._setValue(accountData); + return accountData; + } else { + throw new Error('Cancelled'); + } } catch (error) { throw error; } finally { - this._isUpdating = false; + this._executingPromise = null; } } + + invalidate() { + this._executingPromise = null; + this._expiresAt = null; + this._value = null; + } + + _setValue(value: AccountData) { + this._expiresAt = new Date(Date.now() + 60 * 1000); // 60s expiration + this._value = value; + } + + _isExpired() { + return !this._expiresAt || this._expiresAt < new Date(); + } } const getIpcPath = (): string => { |
