1diff --git a/pkg/worker/http.go b/pkg/worker/http.go
2index 1d56f86e2587ccf59a047e278e1a7a45f11cb165..973775e14ea0374073c0c59af15a908bdf1254e0 100644
3--- a/pkg/worker/http.go
4+++ b/pkg/worker/http.go
5@@ -16,11 +16,23 @@ }
6 }
7
8 func (self *ServerTask) Start(ctx context.Context) error {
9+ done := make(chan error)
10+
11 go func() {
12- // nolint: errcheck
13- self.server.ListenAndServe()
14+ done <- self.server.ListenAndServe()
15 }()
16
17- <-ctx.Done()
18- return self.server.Shutdown(ctx)
19+ select {
20+ // if ListenAndServe error for something other than context.Canceled
21+ //(e.g.: address already in use) it trigger done to return sonner with
22+ // the return error
23+ case err := <-done:
24+ return err
25+
26+ // in case of context canceled it will manually trigger the server to
27+ // shutdown, and return its error, which is most cases, but not limited, is
28+ // context.Canceled.
29+ case <-ctx.Done():
30+ return self.server.Shutdown(ctx)
31+ }
32 }
33diff --git a/pkg/worker/worker.go b/pkg/worker/worker.go
34index 6b5c21cd67bc557b2b8bd1fa048229dc4b098efb..fc97c97a78aa42a5453eef9b5451eeb7bb648956 100644
35--- a/pkg/worker/worker.go
36+++ b/pkg/worker/worker.go
37@@ -10,31 +10,42 @@ "golang.org/x/sync/errgroup"
38 )
39
40 type (
41+
42+ // Task defines a long running core component of the application.
43 Task interface {
44+ // Start defines when the component will be started.
45+ // The task MUST watch for the context and return when the context is
46+ // canceled.
47+ //
48+ // Start MUST only error on unhandled catastrophic errors, since
49+ // returning an error will cause the application to halt.
50+ //
51+ // Context canceled error is ignored and not reported as error as they
52+ // will be trigger by OS signals or some other component erring. But in
53+ // any case the task SHOULD handle and return context cancellation
54+ // error.
55 Start(context.Context) error
56 }
57
58- Work struct {
59+ // TaskPool manages the life-cycle of a pool of tasks.
60+ TaskPool struct {
61+ tasks []*work
62+ }
63+
64+ // work is wrapper around task to add metadata to it.
65+ work struct {
66 Name string
67 Task Task
68 wait time.Duration
69 }
70-
71- TaskPool struct {
72- tasks []*Work
73- }
74-)
75-
76-const (
77- format = "2006.01.02 15:04:05"
78 )
79
80 func NewTaskPool() *TaskPool {
81 return &TaskPool{}
82 }
83
84-func (w *Work) run(ctx context.Context) error {
85- // first time fire from the get go
86+func (w *work) run(ctx context.Context) error {
87+ // first time fire the task from the get go
88 timer := time.NewTimer(time.Nanosecond)
89
90 for {
91@@ -42,7 +53,7 @@ select {
92 case <-ctx.Done():
93 return ctx.Err()
94 case <-timer.C:
95- if err := w.Task.Start(ctx); err != nil && !errors.Is(err, context.Canceled) {
96+ if err := w.Task.Start(ctx); err != nil {
97 return err
98 }
99 }
100@@ -51,7 +62,7 @@ }
101 }
102
103 func (self *TaskPool) AddTask(name string, wait time.Duration, task Task) {
104- self.tasks = append(self.tasks, &Work{
105+ self.tasks = append(self.tasks, &work{
106 Name: name,
107 Task: task,
108 wait: wait,
109@@ -60,27 +71,28 @@ }
110
111 func (self *TaskPool) Start(ctx context.Context) error {
112 var g errgroup.Group
113+ ctx, cancel := context.WithCancel(ctx)
114+ defer cancel()
115
116 for _, w := range self.tasks {
117- g.Go(func(w *Work) func() error {
118+ g.Go(func(w *work) func() error {
119 return func() error {
120- slog.Info("Process starting", "time", time.Now().Format(format), "name", w.Name)
121+ slog.Info("Process starting", "name", w.Name)
122 now := time.Now()
123 if err := w.run(ctx); err != nil && !errors.Is(context.Canceled, err) {
124 since := time.Since(now)
125 slog.Error(
126 "Process erred",
127- "time", time.Now().Format(format),
128 "name", w.Name,
129 "error", err,
130 "duration", since,
131 )
132+ cancel()
133 return err
134 } else {
135 since := time.Since(now)
136 slog.Info(
137 "Process ended",
138- "time", time.Now().Format(format),
139 "name", w.Name,
140 "duration", since,
141 )