From 3e71c489170b151d66ac2dac232356b90d458544 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Mon, 30 Jul 2018 12:25:51 +0200 Subject: [PATCH] Add more timeout on the sync to avoid issues --- src/config/constants.js | 2 +- src/config/errors.js | 1 + src/helpers/libcore.js | 62 +++++++++++++++++++++++++++----------- src/helpers/promise.js | 18 +++++++++++ static/i18n/en/errors.json | 4 +++ 5 files changed, 69 insertions(+), 18 deletions(-) diff --git a/src/config/constants.js b/src/config/constants.js index aefe35b3..023626ef 100644 --- a/src/config/constants.js +++ b/src/config/constants.js @@ -46,7 +46,7 @@ export const SYNC_ALL_INTERVAL = 120 * 1000 export const SYNC_BOOT_DELAY = 2 * 1000 export const SYNC_PENDING_INTERVAL = 10 * 1000 export const SYNC_MAX_CONCURRENT = intFromEnv('LEDGER_SYNC_MAX_CONCURRENT', 1) -export const SYNC_TIMEOUT = intFromEnv('SYNC_TIMEOUT', 30 * 1000) +export const SYNC_TIMEOUT = intFromEnv('SYNC_TIMEOUT', 60 * 1000) // Endpoints... diff --git a/src/config/errors.js b/src/config/errors.js index fa6508bd..d6140d6c 100644 --- a/src/config/errors.js +++ b/src/config/errors.js @@ -12,6 +12,7 @@ export const UserRefusedAddress = createCustomErrorClass('UserRefusedAddress') export const WrongDeviceForAccount = createCustomErrorClass('WrongDeviceForAccount') export const DeviceNotGenuineError = createCustomErrorClass('DeviceNotGenuine') export const DeviceGenuineSocketEarlyClose = createCustomErrorClass('DeviceGenuineSocketEarlyClose') +export const TimeoutTagged = createCustomErrorClass('TimeoutTagged') // db stuff, no need to translate export const NoDBPathGiven = createCustomErrorClass('NoDBPathGiven') diff --git a/src/helpers/libcore.js b/src/helpers/libcore.js index a9ed673d..630c0c14 100644 --- a/src/helpers/libcore.js +++ b/src/helpers/libcore.js @@ -16,6 +16,7 @@ import { isSegwitPath, isUnsplitPath } from 'helpers/bip32' import * as accountIdHelper from 'helpers/accountId' import { createCustomErrorClass, deserializeError } from './errors' import { getAccountPlaceholderName, getNewAccountPlaceholderName } from './accountName' +import { timeoutTagged } from './promise' const NoAddressesFound = createCustomErrorClass('NoAddressesFound') @@ -202,6 +203,7 @@ const coreSyncAccount = (core, account) => new Promise((resolve, reject) => { const eventReceiver = createEventReceiver(core, e => { const code = e.getCode() + logger.debug(`syncAccountEvent ${code}`, { type: 'libcore-sync' }) if (code === core.EVENT_CODE.UNDEFINED || code === core.EVENT_CODE.SYNCHRONIZATION_FAILED) { const payload = e.getPayload() const message = ( @@ -270,7 +272,7 @@ async function scanNextAccount(props: { const shouldSyncAccount = true // TODO: let's sync everytime. maybe in the future we can optimize. if (shouldSyncAccount) { - await coreSyncAccount(core, njsAccount) + await timeoutTagged('coreSyncAccount', 30000, coreSyncAccount(core, njsAccount)) } if (isUnsubscribed()) return [] @@ -325,10 +327,10 @@ async function getOrCreateWallet( ): NJSWallet { const pool = core.getPoolInstance() try { - const wallet = await pool.getWallet(WALLET_IDENTIFIER) + const wallet = await timeoutTagged('getWallet', 5000, pool.getWallet(WALLET_IDENTIFIER)) return wallet } catch (err) { - const currency = await pool.getCurrency(currencyId) + const currency = await timeoutTagged('getCurrency', 5000, pool.getCurrency(currencyId)) const splitConfig = isUnsplit ? SPLITTED_CURRENCIES[currencyId] || null : null const coinType = splitConfig ? splitConfig.coinType : '' const walletConfig = isSegwit @@ -342,9 +344,11 @@ async function getOrCreateWallet( } : undefined const njsWalletConfig = createWalletConfig(core, walletConfig) - const wallet = await core - .getPoolInstance() - .createWallet(WALLET_IDENTIFIER, currency, njsWalletConfig) + const wallet = await timeoutTagged( + 'createWallet', + 10000, + core.getPoolInstance().createWallet(WALLET_IDENTIFIER, currency, njsWalletConfig), + ) return wallet } } @@ -368,21 +372,33 @@ async function buildAccountRaw({ core: *, ops: NJSOperation[], }): Promise { - const njsBalance = await njsAccount.getBalance() + const njsBalance = await timeoutTagged('getBalance', 10000, njsAccount.getBalance()) const balance = njsBalance.toLong() const jsCurrency = getCryptoCurrencyById(currencyId) - const { derivations } = await wallet.getAccountCreationInfo(accountIndex) + const { derivations } = await timeoutTagged( + 'getAccountCreationInfo', + 10000, + wallet.getAccountCreationInfo(accountIndex), + ) const [walletPath, accountPath] = derivations // retrieve xpub const xpub = njsAccount.getRestoreKey() // blockHeight - const { height: blockHeight } = await njsAccount.getLastBlock() + const { height: blockHeight } = await timeoutTagged( + 'getLastBlock', + 30000, + njsAccount.getLastBlock(), + ) // get a bunch of fresh addresses - const rawAddresses = await njsAccount.getFreshPublicAddresses() + const rawAddresses = await timeoutTagged( + 'getFreshPublicAddresses', + 10000, + njsAccount.getFreshPublicAddresses(), + ) const addresses = rawAddresses.map(njsAddress => ({ str: njsAddress.toString(), @@ -500,7 +516,11 @@ export async function syncAccount({ const isUnsplit = isUnsplitPath(freshAddressPath, SPLITTED_CURRENCIES[currencyId]) let njsWallet try { - njsWallet = await core.getPoolInstance().getWallet(decodedAccountId.walletName) + njsWallet = await timeoutTagged( + 'getWallet', + 10000, + core.getPoolInstance().getWallet(decodedAccountId.walletName), + ) } catch (e) { logger.warn(`Have to reimport the account... (${e})`) njsWallet = await getOrCreateWallet( @@ -514,20 +534,28 @@ export async function syncAccount({ let njsAccount try { - njsAccount = await njsWallet.getAccount(index) + njsAccount = await timeoutTagged('getAccount', 10000, njsWallet.getAccount(index)) } catch (e) { logger.warn(`Have to recreate the account... (${e.message})`) - const extendedInfos = await njsWallet.getExtendedKeyAccountCreationInfo(index) + const extendedInfos = await timeoutTagged( + 'getEKACI', + 10000, + njsWallet.getExtendedKeyAccountCreationInfo(index), + ) extendedInfos.extendedKeys.push(decodedAccountId.xpub) - njsAccount = await njsWallet.newAccountWithExtendedKeyInfo(extendedInfos) + njsAccount = await timeoutTagged( + 'newAWEKI', + 10000, + njsWallet.newAccountWithExtendedKeyInfo(extendedInfos), + ) } - const unsub = await coreSyncAccount(core, njsAccount) + const unsub = await timeoutTagged('coreSyncAccount', 30000, coreSyncAccount(core, njsAccount)) unsub() const query = njsAccount.queryOperations() - const ops = await query.complete().execute() - const njsBalance = await njsAccount.getBalance() + const ops = await timeoutTagged('ops', 30000, query.complete().execute()) + const njsBalance = await timeoutTagged('getBalance', 10000, njsAccount.getBalance()) const syncedRawAccount = await buildAccountRaw({ njsAccount, diff --git a/src/helpers/promise.js b/src/helpers/promise.js index ad494a16..0ae2d09f 100644 --- a/src/helpers/promise.js +++ b/src/helpers/promise.js @@ -2,6 +2,7 @@ // small utilities for Promises import logger from 'logger' +import { TimeoutTagged } from 'config/errors' export const delay = (ms: number): Promise => new Promise(f => setTimeout(f, ms)) @@ -65,6 +66,23 @@ export function createCancelablePolling( return { unsubscribe, promise } } +export const timeoutTagged = (tag: string, delay: number, promise: Promise): Promise => + new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + reject(new TimeoutTagged('timeout', { tag })) + }, delay) + promise.then( + r => { + clearTimeout(timeout) + resolve(r) + }, + e => { + clearTimeout(timeout) + reject(e) + }, + ) + }) + export const promisify = (fn: any) => (...args: any) => new Promise((resolve, reject) => fn(...args, (err: Error, res: any) => { diff --git a/static/i18n/en/errors.json b/static/i18n/en/errors.json index 2249bec7..154b6da5 100644 --- a/static/i18n/en/errors.json +++ b/static/i18n/en/errors.json @@ -115,6 +115,10 @@ "title": "Oops, a time out occurred", "description": "It took too long for the server to respond." }, + "TimeoutTagged": { + "title": "Oops, a time out occurred ({{tag}})", + "description": "It took too long for the server to respond." + }, "TransportError": { "title": "Something went wrong. Please reconnect your device.", "description": "{{message}}"