MCPcopy Index your code
hub / github.com/simstudioai/sim / fetchWithRetry

Function fetchWithRetry

apps/sim/lib/data-drains/destinations/gcs.ts:135–188  ·  view source on GitHub ↗
(input: RetryRequestInput)

Source from the content-addressed store, hash-verified

133}
134
135async function fetchWithRetry(input: RetryRequestInput): Promise<void> {
136 let lastError: unknown
137 for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
138 if (input.signal.aborted) throw input.signal.reason ?? new Error('Aborted')
139 const perAttempt = AbortSignal.any([input.signal, AbortSignal.timeout(PER_ATTEMPT_TIMEOUT_MS)])
140 let response: Response
141 try {
142 const headers = await input.buildHeaders()
143 response = await fetch(input.url, {
144 method: input.method,
145 body: input.body as BodyInit | undefined,
146 headers,
147 signal: perAttempt,
148 })
149 } catch (error) {
150 lastError = error
151 logger.debug('GCS request failed', {
152 action: input.action,
153 attempt,
154 bucket: input.bucket,
155 error: toError(error).message,
156 })
157 if (attempt < MAX_ATTEMPTS) {
158 await sleepUntilAborted(backoffWithJitter(attempt, null), input.signal)
159 continue
160 }
161 throw error
162 }
163 if (response.ok || input.successStatuses?.includes(response.status)) {
164 /** Drain the success body so undici can return the socket to the keep-alive pool. */
165 await response.text().catch(() => '')
166 return
167 }
168 if (!isRetryableStatus(response.status) || attempt === MAX_ATTEMPTS) {
169 const text = await response.text().catch(() => '')
170 logger.warn('GCS operation failed', {
171 action: input.action,
172 bucket: input.bucket,
173 status: response.status,
174 })
175 throw new Error(
176 `GCS ${input.action} failed (HTTP ${response.status}): ${text || response.statusText}`
177 )
178 }
179 lastError = new Error(`GCS ${input.action} responded with HTTP ${response.status}`)
180 const retryAfterMs = parseRetryAfter(response.headers.get('retry-after'))
181 /** Drain the retryable response body so undici can return the socket to the keep-alive pool. */
182 await response.text().catch(() => '')
183 await sleepUntilAborted(backoffWithJitter(attempt, retryAfterMs), input.signal)
184 }
185 throw lastError instanceof Error
186 ? lastError
187 : new Error(`GCS ${input.action} failed after retries`)
188}
189
190/** GCS uses HTTP headers (x-goog-meta-*) to carry custom metadata; the spec forbids non-ASCII. */
191const ASCII_ONLY_RE = /^[\x20-\x7e]*$/

Callers 2

uploadObjectFunction · 0.70
deleteObjectFunction · 0.70

Calls 9

toErrorFunction · 0.90
sleepUntilAbortedFunction · 0.90
backoffWithJitterFunction · 0.90
parseRetryAfterFunction · 0.90
debugMethod · 0.80
textMethod · 0.80
isRetryableStatusFunction · 0.70
warnMethod · 0.65
getMethod · 0.65

Tested by

no test coverage detected