From bc3c5abd64fd22a0623b7c0a1c0b55c84ea02160 Mon Sep 17 00:00:00 2001 From: Daniel Amar Date: Mon, 5 May 2025 15:25:06 -0400 Subject: [PATCH] Add retry mechanism with exponential backoff for buildkit bootstrap Signed-off-by: Daniel Amar --- src/main.ts | 70 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 63 insertions(+), 7 deletions(-) diff --git a/src/main.ts b/src/main.ts index 13f0261..f7c451b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -17,6 +17,46 @@ import {ContextInfo} from '@docker/actions-toolkit/lib/types/docker/docker'; import * as context from './context'; import * as stateHelper from './state-helper'; +/** + * Retry a function with exponential backoff + */ +async function retryWithBackoff( + operation: () => Promise, + maxRetries: number = 3, + initialDelay: number = 1000, + maxDelay: number = 10000, + shouldRetry: (error: Error) => boolean = () => true +): Promise { + let retries = 0; + let delay = initialDelay; + + while (true) { + try { + return await operation(); + } catch (error) { + if (retries >= maxRetries || !shouldRetry(error)) { + throw error; + } + + retries++; + core.info(`Retry ${retries}/${maxRetries} after ${delay}ms due to: ${error.message}`); + await new Promise(resolve => setTimeout(resolve, delay)); + + // Exponential backoff with jitter + delay = Math.min(delay * 2, maxDelay) * (0.8 + Math.random() * 0.4); + } + } +} + +/** + * Check if an error is a buildkit socket connection error + */ +function isBuildkitSocketError(error: Error): boolean { + return error.message.includes('/run/buildkit/buildkitd.sock') || + error.message.includes('failed to list workers') || + error.message.includes('connection error'); +} + actionsToolkit.run( // main async () => { @@ -165,13 +205,29 @@ actionsToolkit.run( await core.group(`Booting builder`, async () => { const inspectCmd = await toolkit.buildx.getCommand(await context.getInspectArgs(inputs, toolkit)); - await Exec.getExecOutput(inspectCmd.command, inspectCmd.args, { - ignoreReturnCode: true - }).then(res => { - if (res.stderr.length > 0 && res.exitCode != 0) { - throw new Error(res.stderr.match(/(.*)\s*$/)?.[0]?.trim() ?? 'unknown error'); - } - }); + + try { + await retryWithBackoff( + async () => { + const res = await Exec.getExecOutput(inspectCmd.command, inspectCmd.args, { + ignoreReturnCode: true + }); + + if (res.stderr.length > 0 && res.exitCode != 0) { + throw new Error(res.stderr.match(/(.*)\s*$/)?.[0]?.trim() ?? 'unknown error'); + } + return res; + }, + 5, // maxRetries - retry up to 5 times for buildkit initialization + 1000, // initialDelay - start with 1 second + 15000, // maxDelay - cap at 15 seconds + isBuildkitSocketError // only retry on buildkit socket errors + ); + } catch (error) { + // Log the warning but continue - this matches current behavior where builds still succeed + core.warning(`Failed to bootstrap builder after multiple retries: ${error.message}`); + core.warning('Continuing execution as buildkit daemon may initialize later'); + } }); if (inputs.install) {