From 107bf7368d405f68eb1256869850b882c80d6649 Mon Sep 17 00:00:00 2001 From: David Schlachter Date: Mon, 10 Nov 2025 09:07:02 -0500 Subject: Retry when task creation fails --- README.md | 2 -- go.mod | 5 ++++- go.sum | 2 ++ main.go | 26 ++++++++++++++------------ 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 656590c..1087f5d 100644 --- a/README.md +++ b/README.md @@ -52,5 +52,3 @@ The lines will be formatted like a cron file, but instead of a command, we will ## Future work It turns out jobs can be added AND removed by an ID that is returned from the Add function. It could be interesting to add/remove based on the last seen map (with the job IDs), instead of completely recreating all the jobs if the input file changes. - -We should probably retry failed network requests. It would not be great to have a temporary connectivity issue lead to missing an important task! diff --git a/go.mod b/go.mod index d761957..5694ea0 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module davidschlachter.com/todoist-repeater go 1.24.1 -require github.com/robfig/cron/v3 v3.0.1 +require ( + github.com/cenkalti/backoff/v5 v5.0.3 + github.com/robfig/cron/v3 v3.0.1 +) diff --git a/go.sum b/go.sum index 0667807..512dd4d 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,4 @@ +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= diff --git a/main.go b/main.go index 8d104b7..00a319f 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,7 @@ package main import ( "bufio" "bytes" + "context" "fmt" "io" "log" @@ -14,6 +15,7 @@ import ( "strings" "time" + "github.com/cenkalti/backoff/v5" "github.com/robfig/cron/v3" ) @@ -31,7 +33,10 @@ type Task struct { } func (t Task) Run() { - createTask(t.Name) + _, err := backoff.Retry(context.Background(), func() (bool, error) { return true, createTask(t.Name) }, backoff.WithBackOff(backoff.NewExponentialBackOff())) + if err != nil { + log.Printf("Failed to create task: %s", err) + } } var lastSeenInput = map[InputLine]TaskAddingJob{} @@ -164,7 +169,7 @@ func inputChanged(old, new map[InputLine]TaskAddingJob) bool { return false } -func createTask(task string) { +func createTask(task string) error { jsonBody := []byte(fmt.Sprintf(`{"content": %s}`, strconv.Quote(task))) req, err := http.NewRequest( http.MethodPost, @@ -172,29 +177,26 @@ func createTask(task string) { bytes.NewBuffer(jsonBody), ) if err != nil { - log.Printf("Failed to create request: %s", err) - return + return fmt.Errorf("creating request: %w", err) } req.Header.Add("Authorization", "Bearer "+todoistToken) req.Header.Set("Content-Type", "application/json") resp, err := httpClient.Do(req) if err != nil { - log.Printf("Failed to Do request: %s", err) - return + return fmt.Errorf("doing request: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - log.Printf("Got status code %d, expected 200 when creating task", resp.StatusCode) bodyBytes, err := io.ReadAll(resp.Body) if err != nil { - log.Printf("Failed to read body: %s", err) - return + return fmt.Errorf("reading body when preparing error message (HTTP status was %d): %w", resp.StatusCode, err) } - log.Printf("Got body: %s", string(bodyBytes)) - } else { - log.Printf("Created task '%s'", task) + return fmt.Errorf("expected 200, got status code %d: body was %s", resp.StatusCode, strconv.Quote(string(bodyBytes))) } + + log.Printf("Created task '%s'", task) + return nil } func addJobs(c *cron.Cron, tasks map[InputLine]TaskAddingJob) { -- cgit v1.2.3