Skip to content
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
19 changes: 5 additions & 14 deletions apps/randomness/src/DrandService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export class DrandService {
}

const url = `${env.EVM_DRAND_URL}/rounds/${round}`
const response = await ResultAsync.fromPromise(fetchWithRetry(url, {}, 2, 1000), unknownToError)
const response = await ResultAsync.fromPromise(fetchWithRetry(url, {}, 3, 1000, 3000), unknownToError)

if (response.isErr()) {
return err(DrandError.NetworkError)
Expand Down Expand Up @@ -150,20 +150,11 @@ export class DrandService {

await Promise.all(
drandGaps.map(async (round) => {
let drandBeacon = await this.getDrandBeacon(round)
const drandBeacon = await this.getDrandBeacon(round)

if (drandBeacon.isErr()) {
if (drandBeacon.error !== DrandError.TooEarly) {
console.error("Failed to get drand beacon", drandBeacon.error)
return
}

await sleep(1000)
drandBeacon = await this.getDrandBeacon(round)

if (drandBeacon.isErr()) {
console.error("Failed to get drand beacon", drandBeacon.error)
return
}
console.error("Failed to get drand beacon", drandBeacon?.error)
return
}

const postDrandTransactionResult = this.transactionFactory.createPostDrandTransaction({
Expand Down
55 changes: 41 additions & 14 deletions support/common/lib/utils/fetch.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { promiseWithResolvers } from "./promises"
import { sleep } from "./sleep"

/**
* Performs an HTTP request using fetch with retry capability and a timeout for each attempt.
*
Expand All @@ -6,8 +9,10 @@
* See https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#fetch_options.
* @param maxRetries The maximum number of retry attempts in case of failure or timeout.
* (Default: 3)
* @param timeout The maximum time (in milliseconds) to wait for each attempt before timing out.
* @param retryAfter The time (in milliseconds) to wait before retrying the request.
* (Default: 5000)
* @param absoluteTimeout The maximum time (in milliseconds) to wait for all retry attempts before timing out.
* (Default: 15000)
*
* @returns A promise that resolves with the response of the request (type Response).
* Throws an error if all retry attempts fail or if a timeout occurs.
Expand All @@ -16,19 +21,41 @@ export async function fetchWithRetry(
url: string,
options: RequestInit = {},
maxRetries = 3,
timeout = 5000,
retryAfter = 5000,
absoluteTimeout = 15000,
): Promise<Response> {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const signal = AbortSignal.timeout(timeout)
const response = await fetch(url, { ...options, signal })
return response
} catch (error) {
if (attempt === maxRetries) {
throw error
}
}
}
return new Promise((outerResolve, outerReject) => {
;(async () => {
let isResolved = false
const startTime = Date.now()

for (let i = 0; i < maxRetries; i++) {
const { resolve: localResolve, promise: localPromise } = promiseWithResolvers<Response>()
const elapsed = Date.now() - startTime
const signal = AbortSignal.timeout(absoluteTimeout - elapsed)

throw new Error("Failed to complete the request.")
fetch(url, { ...options, signal })
.then((response) => {
if (response.ok) {
isResolved = true
outerResolve(response)
} else if (i === maxRetries - 1) {
outerResolve(response)
} else {
localResolve(response)
}
})
.catch((err) => {
if (i === maxRetries - 1) {
outerReject(err)
} else {
localResolve(err)
}
})

await Promise.race([localPromise, sleep(retryAfter)])
if (isResolved) break
}
})()
})
}
Loading