cerrado @ 3fb9c66ffa0bf87cbd7cc1b5f4129f3447e94c13

feat: Initial http server code
  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+}