1diff --git a/Makefile b/Makefile
2new file mode 100644
3index 0000000000000000000000000000000000000000..7a0e125baadd8ba25b560106a0b1bd91ec30df17
4--- /dev/null
5+++ b/Makefile
6@@ -0,0 +1,5 @@
7+build:
8+ go build -o bin/cerrado
9+
10+run:
11+ go run .
12diff --git a/go.mod b/go.mod
13new file mode 100644
14index 0000000000000000000000000000000000000000..cca006e89444dbdcf6c81c9f4f0f9acf6ed0bb6b
15--- /dev/null
16+++ b/go.mod
17@@ -0,0 +1,5 @@
18+module git.gabrielgio.me/cerrado
19+
20+go 1.22.2
21+
22+require golang.org/x/sync v0.7.0 // indirect
23diff --git a/go.sum b/go.sum
24new file mode 100644
25index 0000000000000000000000000000000000000000..e8ef4a360a1437536e8a3053e4ffc82fc69ce9c5
26--- /dev/null
27+++ b/go.sum
28@@ -0,0 +1,2 @@
29+golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
30+golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
31diff --git a/main.go b/main.go
32new file mode 100644
33index 0000000000000000000000000000000000000000..7c80564f57dfa106d4a4074f6c4586bc0a9c18ba
34--- /dev/null
35+++ b/main.go
36@@ -0,0 +1,35 @@
37+package main
38+
39+import (
40+ "context"
41+ "log/slog"
42+ "net/http"
43+ "os"
44+ "os/signal"
45+ "time"
46+
47+ "git.gabrielgio.me/cerrado/pkg/worker"
48+)
49+
50+func main() {
51+ ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
52+ defer stop()
53+ if err := run(ctx); err != nil {
54+ os.Exit(1)
55+ }
56+}
57+
58+func run(ctx context.Context) error {
59+ mux := http.NewServeMux()
60+ mux.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) {
61+ if _, err := w.Write([]byte("Hello world!")); err != nil {
62+ slog.Error("Error handling index", "error", err)
63+ }
64+ })
65+ serverTask := worker.NewServerTask(&http.Server{Handler: mux, Addr: "0.0.0.0:8080"})
66+
67+ pool := worker.NewTaskPool()
68+ pool.AddTask("http-server", 5*time.Second, serverTask)
69+
70+ return pool.Start(ctx)
71+}
72diff --git a/pkg/worker/http.go b/pkg/worker/http.go
73new file mode 100644
74index 0000000000000000000000000000000000000000..1d56f86e2587ccf59a047e278e1a7a45f11cb165
75--- /dev/null
76+++ b/pkg/worker/http.go
77@@ -0,0 +1,26 @@
78+package worker
79+
80+import (
81+ "context"
82+ "net/http"
83+)
84+
85+type ServerTask struct {
86+ server *http.Server
87+}
88+
89+func NewServerTask(server *http.Server) *ServerTask {
90+ return &ServerTask{
91+ server: server,
92+ }
93+}
94+
95+func (self *ServerTask) Start(ctx context.Context) error {
96+ go func() {
97+ // nolint: errcheck
98+ self.server.ListenAndServe()
99+ }()
100+
101+ <-ctx.Done()
102+ return self.server.Shutdown(ctx)
103+}
104diff --git a/pkg/worker/worker.go b/pkg/worker/worker.go
105new file mode 100644
106index 0000000000000000000000000000000000000000..6b5c21cd67bc557b2b8bd1fa048229dc4b098efb
107--- /dev/null
108+++ b/pkg/worker/worker.go
109@@ -0,0 +1,94 @@
110+package worker
111+
112+import (
113+ "context"
114+ "errors"
115+ "log/slog"
116+ "time"
117+
118+ "golang.org/x/sync/errgroup"
119+)
120+
121+type (
122+ Task interface {
123+ Start(context.Context) error
124+ }
125+
126+ Work struct {
127+ Name string
128+ Task Task
129+ wait time.Duration
130+ }
131+
132+ TaskPool struct {
133+ tasks []*Work
134+ }
135+)
136+
137+const (
138+ format = "2006.01.02 15:04:05"
139+)
140+
141+func NewTaskPool() *TaskPool {
142+ return &TaskPool{}
143+}
144+
145+func (w *Work) run(ctx context.Context) error {
146+ // first time fire from the get go
147+ timer := time.NewTimer(time.Nanosecond)
148+
149+ for {
150+ select {
151+ case <-ctx.Done():
152+ return ctx.Err()
153+ case <-timer.C:
154+ if err := w.Task.Start(ctx); err != nil && !errors.Is(err, context.Canceled) {
155+ return err
156+ }
157+ }
158+ timer.Reset(w.wait)
159+ }
160+}
161+
162+func (self *TaskPool) AddTask(name string, wait time.Duration, task Task) {
163+ self.tasks = append(self.tasks, &Work{
164+ Name: name,
165+ Task: task,
166+ wait: wait,
167+ })
168+}
169+
170+func (self *TaskPool) Start(ctx context.Context) error {
171+ var g errgroup.Group
172+
173+ for _, w := range self.tasks {
174+ g.Go(func(w *Work) func() error {
175+ return func() error {
176+ slog.Info("Process starting", "time", time.Now().Format(format), "name", w.Name)
177+ now := time.Now()
178+ if err := w.run(ctx); err != nil && !errors.Is(context.Canceled, err) {
179+ since := time.Since(now)
180+ slog.Error(
181+ "Process erred",
182+ "time", time.Now().Format(format),
183+ "name", w.Name,
184+ "error", err,
185+ "duration", since,
186+ )
187+ return err
188+ } else {
189+ since := time.Since(now)
190+ slog.Info(
191+ "Process ended",
192+ "time", time.Now().Format(format),
193+ "name", w.Name,
194+ "duration", since,
195+ )
196+ }
197+ return nil
198+ }
199+ }(w))
200+ }
201+
202+ return g.Wait()
203+}