Skip to content

Commit bc3c5ab

Browse files
Add retry mechanism with exponential backoff for buildkit bootstrap
Signed-off-by: Daniel Amar <[email protected]>
1 parent e600775 commit bc3c5ab

File tree

1 file changed

+63
-7
lines changed

1 file changed

+63
-7
lines changed

src/main.ts

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,46 @@ import {ContextInfo} from '@docker/actions-toolkit/lib/types/docker/docker';
1717
import * as context from './context';
1818
import * as stateHelper from './state-helper';
1919

20+
/**
21+
* Retry a function with exponential backoff
22+
*/
23+
async function retryWithBackoff<T>(
24+
operation: () => Promise<T>,
25+
maxRetries: number = 3,
26+
initialDelay: number = 1000,
27+
maxDelay: number = 10000,
28+
shouldRetry: (error: Error) => boolean = () => true
29+
): Promise<T> {
30+
let retries = 0;
31+
let delay = initialDelay;
32+
33+
while (true) {
34+
try {
35+
return await operation();
36+
} catch (error) {
37+
if (retries >= maxRetries || !shouldRetry(error)) {
38+
throw error;
39+
}
40+
41+
retries++;
42+
core.info(`Retry ${retries}/${maxRetries} after ${delay}ms due to: ${error.message}`);
43+
await new Promise(resolve => setTimeout(resolve, delay));
44+
45+
// Exponential backoff with jitter
46+
delay = Math.min(delay * 2, maxDelay) * (0.8 + Math.random() * 0.4);
47+
}
48+
}
49+
}
50+
51+
/**
52+
* Check if an error is a buildkit socket connection error
53+
*/
54+
function isBuildkitSocketError(error: Error): boolean {
55+
return error.message.includes('/run/buildkit/buildkitd.sock') ||
56+
error.message.includes('failed to list workers') ||
57+
error.message.includes('connection error');
58+
}
59+
2060
actionsToolkit.run(
2161
// main
2262
async () => {
@@ -165,13 +205,29 @@ actionsToolkit.run(
165205

166206
await core.group(`Booting builder`, async () => {
167207
const inspectCmd = await toolkit.buildx.getCommand(await context.getInspectArgs(inputs, toolkit));
168-
await Exec.getExecOutput(inspectCmd.command, inspectCmd.args, {
169-
ignoreReturnCode: true
170-
}).then(res => {
171-
if (res.stderr.length > 0 && res.exitCode != 0) {
172-
throw new Error(res.stderr.match(/(.*)\s*$/)?.[0]?.trim() ?? 'unknown error');
173-
}
174-
});
208+
209+
try {
210+
await retryWithBackoff(
211+
async () => {
212+
const res = await Exec.getExecOutput(inspectCmd.command, inspectCmd.args, {
213+
ignoreReturnCode: true
214+
});
215+
216+
if (res.stderr.length > 0 && res.exitCode != 0) {
217+
throw new Error(res.stderr.match(/(.*)\s*$/)?.[0]?.trim() ?? 'unknown error');
218+
}
219+
return res;
220+
},
221+
5, // maxRetries - retry up to 5 times for buildkit initialization
222+
1000, // initialDelay - start with 1 second
223+
15000, // maxDelay - cap at 15 seconds
224+
isBuildkitSocketError // only retry on buildkit socket errors
225+
);
226+
} catch (error) {
227+
// Log the warning but continue - this matches current behavior where builds still succeed
228+
core.warning(`Failed to bootstrap builder after multiple retries: ${error.message}`);
229+
core.warning('Continuing execution as buildkit daemon may initialize later');
230+
}
175231
});
176232

177233
if (inputs.install) {

0 commit comments

Comments
 (0)