Skip to content

Commit

Permalink
Apps: add exponential backoff to eth_calls when they fail (#934)
Browse files Browse the repository at this point in the history
  • Loading branch information
sohkai committed Jul 25, 2019
1 parent 68d860b commit 5e40f0a
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 57 deletions.
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

0 comments on commit 5e40f0a

Please sign in to comment.