@@ -205,33 +205,50 @@ async function insertAll(input: InsertAllInput): Promise<void> {
205205 )
206206 }
207207 let attempt = 0
208- let response : Response
208+ let response : Response | undefined
209209 let refreshedOnce = false
210210 while ( true ) {
211211 attempt ++
212- response = await postInsertAll ( input , url , body )
213- // 401: refresh token and retry once; this attempt does not count toward
214- // the 5xx/429 retry budget.
215- if ( response . status === 401 && ! refreshedOnce ) {
216- refreshedOnce = true
217- logger . debug ( 'BigQuery returned 401; refreshing access token and retrying once' )
218- response = await postInsertAll ( input , url , body , true )
212+ try {
213+ response = await postInsertAll ( input , url , body )
214+ // 401: refresh token and retry once; this attempt does not count toward
215+ // the 5xx/429 retry budget.
216+ if ( response . status === 401 && ! refreshedOnce ) {
217+ refreshedOnce = true
218+ logger . debug ( 'BigQuery returned 401; refreshing access token and retrying once' )
219+ response = await postInsertAll ( input , url , body , true )
220+ }
221+ if ( ! RETRYABLE_STATUSES . has ( response . status ) ) break
222+ if ( attempt >= MAX_RETRY_ATTEMPTS ) break
223+ const retryAfterMs =
224+ parseRetryAfter ( response . headers . get ( 'retry-after' ) ) ??
225+ BASE_RETRY_DELAY_MS * 2 ** ( attempt - 1 )
226+ logger . warn ( 'BigQuery insertAll transient error; retrying' , {
227+ status : response . status ,
228+ attempt,
229+ retryAfterMs,
230+ } )
231+ // Drain the body so the connection can be reused.
232+ await response . text ( ) . catch ( ( ) => '' )
233+ await sleepUntilAborted ( retryAfterMs , input . signal )
234+ if ( input . signal . aborted ) throw input . signal . reason ?? new Error ( 'Aborted' )
235+ } catch ( error ) {
236+ // Connection-level failures (DNS, socket reset, timeout) don't produce
237+ // a Response — treat them like 5xx and retry with backoff. Re-throw
238+ // aborts so callers see the cancel reason instead of a wrapped retry.
239+ if ( input . signal . aborted ) throw input . signal . reason ?? error
240+ if ( attempt >= MAX_RETRY_ATTEMPTS ) throw error
241+ const retryAfterMs = BASE_RETRY_DELAY_MS * 2 ** ( attempt - 1 )
242+ logger . warn ( 'BigQuery insertAll network error; retrying' , {
243+ attempt,
244+ retryAfterMs,
245+ error : toError ( error ) . message ,
246+ } )
247+ await sleepUntilAborted ( retryAfterMs , input . signal )
248+ if ( input . signal . aborted ) throw input . signal . reason ?? new Error ( 'Aborted' )
219249 }
220- if ( ! RETRYABLE_STATUSES . has ( response . status ) ) break
221- if ( attempt >= MAX_RETRY_ATTEMPTS ) break
222- const retryAfterMs =
223- parseRetryAfter ( response . headers . get ( 'retry-after' ) ) ??
224- BASE_RETRY_DELAY_MS * 2 ** ( attempt - 1 )
225- logger . warn ( 'BigQuery insertAll transient error; retrying' , {
226- status : response . status ,
227- attempt,
228- retryAfterMs,
229- } )
230- // Drain the body so the connection can be reused.
231- await response . text ( ) . catch ( ( ) => '' )
232- await sleepUntilAborted ( retryAfterMs , input . signal )
233- if ( input . signal . aborted ) throw input . signal . reason ?? new Error ( 'Aborted' )
234250 }
251+ if ( ! response ) throw new Error ( 'BigQuery insertAll failed: no response' )
235252 if ( ! response . ok ) {
236253 const text = await response . text ( ) . catch ( ( ) => '' )
237254 throw new Error ( `BigQuery insertAll failed (HTTP ${ response . status } ): ${ text } ` )
0 commit comments