@@ -10,135 +10,11 @@ import (
1010 "path/filepath"
1111 "runtime"
1212 "strings"
13- "time"
1413
1514 devpodhttp "github.com/skevetter/devpod/pkg/http"
1615 "github.com/skevetter/log"
1716)
1817
19- type RetryConfig struct {
20- MaxAttempts int
21- InitialDelay time.Duration
22- MaxDelay time.Duration
23- Deadline time.Time
24- }
25-
26- type RetryFunc func (attempt int ) error
27-
28- func RetryWithDeadline (
29- ctx context.Context ,
30- log log.Logger ,
31- cfg RetryConfig ,
32- fn RetryFunc ,
33- ) error {
34- cfg .applyDefaults ()
35- delay := cfg .InitialDelay
36-
37- for attempt := 1 ; attempt <= cfg .MaxAttempts ; attempt ++ {
38- if err := cfg .checkPreConditions (ctx , attempt - 1 ); err != nil {
39- return err
40- }
41-
42- err := fn (attempt )
43- if err == nil {
44- return nil
45- }
46-
47- if attempt == cfg .MaxAttempts {
48- return fmt .Errorf ("agent injection failed after %d attempts: %w" , attempt , err )
49- }
50-
51- delay = cfg .handleRetry (& retryContext {
52- ctx : ctx ,
53- log : log ,
54- attempt : attempt ,
55- err : err ,
56- delay : delay ,
57- })
58- if delay == 0 {
59- return ctx .Err ()
60- }
61- }
62-
63- return fmt .Errorf ("retry loop exited unexpectedly" )
64- }
65-
66- func (cfg * RetryConfig ) checkPreConditions (ctx context.Context , attemptsCompleted int ) error {
67- if err := cfg .checkDeadline (attemptsCompleted ); err != nil {
68- return err
69- }
70- return checkContextCancelled (ctx )
71- }
72-
73- type retryContext struct {
74- ctx context.Context
75- log log.Logger
76- attempt int
77- err error
78- delay time.Duration
79- }
80-
81- func (cfg * RetryConfig ) handleRetry (rctx * retryContext ) time.Duration {
82- sleep := calculateSleep (rctx .delay , cfg )
83-
84- rctx .log .Debugf ("retrying attempt %d after %v: %v" , rctx .attempt , sleep , rctx .err )
85-
86- if err := sleepWithContext (rctx .ctx , sleep ); err != nil {
87- return 0
88- }
89-
90- newDelay := rctx .delay * 2
91- return min (newDelay , cfg .MaxDelay )
92- }
93-
94- func (cfg * RetryConfig ) applyDefaults () {
95- if cfg .MaxAttempts <= 0 {
96- cfg .MaxAttempts = 1
97- }
98- if cfg .InitialDelay <= 0 {
99- cfg .InitialDelay = time .Second
100- }
101- if cfg .MaxDelay <= 0 {
102- cfg .MaxDelay = 30 * time .Second
103- }
104- }
105-
106- func (cfg * RetryConfig ) checkDeadline (attemptsCompleted int ) error {
107- if cfg .Deadline .IsZero () || ! time .Now ().After (cfg .Deadline ) {
108- return nil
109- }
110- return fmt .Errorf ("%w after %d attempts" , ErrInjectTimeout , attemptsCompleted )
111- }
112-
113- func checkContextCancelled (ctx context.Context ) error {
114- select {
115- case <- ctx .Done ():
116- return ctx .Err ()
117- default :
118- return nil
119- }
120- }
121-
122- func calculateSleep (delay time.Duration , cfg * RetryConfig ) time.Duration {
123- sleep := delay
124- if ! cfg .Deadline .IsZero () {
125- remaining := time .Until (cfg .Deadline )
126- if remaining > 0 && sleep > remaining {
127- sleep = remaining
128- }
129- }
130- return sleep
131- }
132-
133- func sleepWithContext (ctx context.Context , duration time.Duration ) error {
134- select {
135- case <- ctx .Done ():
136- return ctx .Err ()
137- case <- time .After (duration ):
138- return nil
139- }
140- }
141-
14218type BinarySource interface {
14319 GetBinary (ctx context.Context , arch string ) (io.ReadCloser , error )
14420 SourceName () string
0 commit comments