Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apps: add exponential backoff to eth_calls when they fail #934

Merged
merged 2 commits into from
Jul 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 46 additions & 19 deletions apps/finance/app/src/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const app = new Aragon()

/*
* Calls `callback` exponentially, everytime `retry()` is called.
* Returns a promise that resolves with the callback's result if it (eventually) succeeds.
*
* Usage:
*
Expand All @@ -48,32 +49,49 @@ const app = new Aragon()
* }, 1000, 2)
*
*/
const retryEvery = (callback, initialRetryTimer = 1000, increaseFactor = 5) => {
const attempt = (retryTimer = initialRetryTimer) => {
// eslint-disable-next-line standard/no-callback-literal
callback(() => {
console.error(`Retrying in ${retryTimer / 1000}s...`)
const retryEvery = async (
callback,
{ initialRetryTimer = 1000, increaseFactor = 3, maxRetries = 3 } = {}
) => {
const sleep = time => new Promise(resolve => setTimeout(resolve, time))

let retryNum = 0
const attempt = async (retryTimer = initialRetryTimer) => {
try {
return await callback()
} catch (err) {
if (retryNum === maxRetries) {
throw err
}
++retryNum

// Exponentially backoff attempts
setTimeout(() => attempt(retryTimer * increaseFactor), retryTimer)
})
const nextRetryTime = retryTimer * increaseFactor
console.log(
`Retrying in ${nextRetryTime}s... (attempt ${retryNum} of ${maxRetries})`
)
await sleep(nextRetryTime)
return attempt(nextRetryTime)
}
}
attempt()

return attempt()
}

// Get the token address to initialize ourselves
retryEvery(retry => {
app.call('vault').subscribe(
vaultAddress => initialize(vaultAddress, ETHER_TOKEN_FAKE_ADDRESS),
err => {
// Get the vault address to initialize ourselves
retryEvery(() =>
app
.call('vault')
.toPromise()
.then(vaultAddress => initialize(vaultAddress, ETHER_TOKEN_FAKE_ADDRESS))
.catch(err => {
console.error(
'Could not start background script execution due to the contract not loading the token:',
'Could not start background script execution due to the contract not loading the vault:',
err
)
retry()
}
)
})
throw err
})
)

async function initialize(vaultAddress, ethAddress) {
const vaultContract = app.external(vaultAddress, vaultAbi)
Expand Down Expand Up @@ -392,7 +410,16 @@ async function loadTokenSymbol(tokenContract, tokenAddress, { network }) {

async function loadTransactionDetails(id) {
return marshallTransactionDetails(
await app.call('getTransaction', id).toPromise()
// Wrap with retry in case the transaction is somehow not present
await retryEvery(() =>
app
.call('getTransaction', id)
.toPromise()
.catch(err => {
console.error(`Error fetching transaction (${id})`, err)
throw err
})
)
)
}

Expand Down
55 changes: 38 additions & 17 deletions apps/token-manager/app/src/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const app = new Aragon()

/*
* Calls `callback` exponentially, everytime `retry()` is called.
* Returns a promise that resolves with the callback's result if it (eventually) succeeds.
*
* Usage:
*
Expand All @@ -20,29 +21,49 @@ const app = new Aragon()
* }, 1000, 2)
*
*/
const retryEvery = (callback, initialRetryTimer = 1000, increaseFactor = 5) => {
const attempt = (retryTimer = initialRetryTimer) => {
// eslint-disable-next-line standard/no-callback-literal
callback(() => {
console.error(`Retrying in ${retryTimer / 1000}s...`)
const retryEvery = async (
callback,
{ initialRetryTimer = 1000, increaseFactor = 3, maxRetries = 3 } = {}
) => {
const sleep = time => new Promise(resolve => setTimeout(resolve, time))

let retryNum = 0
const attempt = async (retryTimer = initialRetryTimer) => {
try {
return await callback()
} catch (err) {
if (retryNum === maxRetries) {
throw err
}
++retryNum

// Exponentially backoff attempts
setTimeout(() => attempt(retryTimer * increaseFactor), retryTimer)
})
const nextRetryTime = retryTimer * increaseFactor
console.log(
`Retrying in ${nextRetryTime}s... (attempt ${retryNum} of ${maxRetries})`
)
await sleep(nextRetryTime)
return attempt(nextRetryTime)
}
}
attempt()

return attempt()
}

// Get the token address to initialize ourselves
retryEvery(retry => {
app.call('token').subscribe(initialize, err => {
console.error(
'Could not start background script execution due to the contract not loading the token:',
err
)
retry()
})
})
retryEvery(() =>
app
.call('token')
.toPromise()
.then(initialize)
.catch(err => {
console.error(
'Could not start background script execution due to the contract not loading the token:',
err
)
throw err
})
)

async function initialize(tokenAddress) {
const token = app.external(tokenAddress, tokenAbi)
Expand Down
75 changes: 54 additions & 21 deletions apps/voting/app/src/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ let connectedAccount

/*
* Calls `callback` exponentially, everytime `retry()` is called.
* Returns a promise that resolves with the callback's result if it (eventually) succeeds.
*
* Usage:
*
Expand All @@ -27,21 +28,37 @@ let connectedAccount
* }, 1000, 2)
*
*/
const retryEvery = (callback, initialRetryTimer = 1000, increaseFactor = 5) => {
const attempt = (retryTimer = initialRetryTimer) => {
// eslint-disable-next-line standard/no-callback-literal
callback(() => {
console.error(`Retrying in ${retryTimer / 1000}s...`)
const retryEvery = async (
callback,
{ initialRetryTimer = 1000, increaseFactor = 3, maxRetries = 3 } = {}
) => {
const sleep = time => new Promise(resolve => setTimeout(resolve, time))

let retryNum = 0
const attempt = async (retryTimer = initialRetryTimer) => {
try {
return await callback()
} catch (err) {
if (retryNum === maxRetries) {
throw err
}
++retryNum

// Exponentially backoff attempts
setTimeout(() => attempt(retryTimer * increaseFactor), retryTimer)
})
const nextRetryTime = retryTimer * increaseFactor
console.log(
`Retrying in ${nextRetryTime}s... (attempt ${retryNum} of ${maxRetries})`
)
await sleep(nextRetryTime)
return attempt(nextRetryTime)
}
}
attempt()

return attempt()
}

// Get the token address to initialize ourselves
retryEvery(retry => {
retryEvery(() =>
app
.call('token')
.toPromise()
Expand All @@ -51,9 +68,9 @@ retryEvery(retry => {
'Could not start background script execution due to the contract not loading the token:',
err
)
retry()
throw err
})
})
)

async function initialize(tokenAddr) {
return app.store(
Expand Down Expand Up @@ -210,12 +227,21 @@ async function getAccountVotes({ connectedAccount, votes = [] }) {
}

async function getVoterState({ connectedAccount, voteId }) {
return app
.call('getVoterState', voteId, connectedAccount)
.toPromise()
.then(voteTypeFromContractEnum)
.then(voteType => ({ voteId, voteType }))
.catch(console.error)
// Wrap with retry in case the vote is somehow not present
return retryEvery(() =>
app
.call('getVoterState', voteId, connectedAccount)
.toPromise()
.then(voteTypeFromContractEnum)
.then(voteType => ({ voteId, voteType }))
.catch(err => {
console.error(
`Error fetching voter state (${connectedAccount}, ${voteId})`,
err
)
throw err
})
)
}

async function loadVoteDescription(vote) {
Expand Down Expand Up @@ -245,10 +271,17 @@ async function loadVoteDescription(vote) {
}

function loadVoteData(voteId) {
return app
.call('getVote', voteId)
.toPromise()
.then(vote => loadVoteDescription(marshallVote(vote)))
// Wrap with retry in case the vote is somehow not present
return retryEvery(() =>
app
.call('getVote', voteId)
.toPromise()
.then(vote => loadVoteDescription(marshallVote(vote)))
.catch(err => {
console.error(`Error fetching vote (${voteId})`, err)
throw err
})
)
}

async function updateVotes(votes, voteId, transform) {
Expand Down