Skip to content

Commit e58c08c

Browse files
committed
Retry builder image preparation at startup
1 parent 7a60b21 commit e58c08c

2 files changed

Lines changed: 52 additions & 8 deletions

File tree

lib/builds/manager.go

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ const (
3434
// of a build; releaseBuildRetryDelay spaces the attempts.
3535
releaseBuildMaxAttempts = 5
3636
releaseBuildRetryDelay = time.Second
37+
38+
// builderImageRetryDelay controls how often startup retries builder image
39+
// preparation after a transient failure.
40+
builderImageRetryDelay = time.Second
3741
)
3842

3943
//go:embed images/generic/Dockerfile
@@ -205,11 +209,19 @@ func NewManager(
205209
// Start starts the build manager's background services
206210
func (m *manager) Start(ctx context.Context) error {
207211
go func() {
208-
m.ensureBuilderImage(ctx)
209-
// Recover pending builds only after the builder image is ready,
210-
// otherwise recovered builds fail with "builder image is being prepared".
211-
if m.ReadyForBuilds() {
212-
m.RecoverPendingBuilds()
212+
for {
213+
m.ensureBuilderImage(ctx)
214+
// Recover pending builds only after the builder image is ready,
215+
// otherwise recovered builds fail with "builder image is being prepared".
216+
if m.ReadyForBuilds() {
217+
m.RecoverPendingBuilds()
218+
return
219+
}
220+
select {
221+
case <-ctx.Done():
222+
return
223+
case <-time.After(builderImageRetryDelay):
224+
}
213225
}
214226
}()
215227
m.logger.Info("build manager started")

lib/builds/manager_test.go

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -309,9 +309,10 @@ func (m *mockSecretProvider) GetSecrets(ctx context.Context, secretIDs []string)
309309

310310
// mockImageManager implements images.Manager for testing
311311
type mockImageManager struct {
312-
mu sync.RWMutex
313-
images map[string]*images.Image
314-
getImageErr error
312+
mu sync.RWMutex
313+
images map[string]*images.Image
314+
createImageFunc func(ctx context.Context, req images.CreateImageRequest) (*images.Image, error)
315+
getImageErr error
315316
}
316317

317318
func newMockImageManager() *mockImageManager {
@@ -329,6 +330,9 @@ func (m *mockImageManager) ListImages(ctx context.Context) ([]images.Image, erro
329330
}
330331

331332
func (m *mockImageManager) CreateImage(ctx context.Context, req images.CreateImageRequest) (*images.Image, error) {
333+
if m.createImageFunc != nil {
334+
return m.createImageFunc(ctx, req)
335+
}
332336
img := &images.Image{
333337
Name: req.Name,
334338
Status: images.StatusPending,
@@ -898,6 +902,34 @@ func TestStart(t *testing.T) {
898902
assert.NoError(t, err)
899903
}
900904

905+
func TestStart_RetriesBuilderPreparationAfterFailure(t *testing.T) {
906+
mgr, _, _, imageMgr, tempDir := setupTestManagerWithImageMgr(t)
907+
defer os.RemoveAll(tempDir)
908+
mgr.builderReady.Store(false)
909+
910+
attempts := 0
911+
imageMgr.createImageFunc = func(ctx context.Context, req images.CreateImageRequest) (*images.Image, error) {
912+
attempts++
913+
if attempts == 1 {
914+
return nil, fmt.Errorf("transient pull failure")
915+
}
916+
imageMgr.mu.Lock()
917+
defer imageMgr.mu.Unlock()
918+
img := &images.Image{
919+
Name: req.Name,
920+
Status: images.StatusReady,
921+
}
922+
imageMgr.images[req.Name] = img
923+
return img, nil
924+
}
925+
926+
ctx, cancel := context.WithCancel(context.Background())
927+
defer cancel()
928+
929+
require.NoError(t, mgr.Start(ctx))
930+
require.Eventually(t, mgr.ReadyForBuilds, 5*time.Second, 50*time.Millisecond)
931+
}
932+
901933
func TestCreateBuild_MultipleConcurrent(t *testing.T) {
902934
mgr, _, _, tempDir := setupTestManager(t)
903935
defer os.RemoveAll(tempDir)

0 commit comments

Comments
 (0)