Compare commits

..

1 Commits

Author SHA1 Message Date
agent-company c7d68c742d feat: implement graceful shutdown on SIGTERM/SIGINT with request draining
Replace http.ListenAndServe with http.Server and signal handling to
support graceful shutdown. On SIGTERM or SIGINT, the server drains
in-flight requests with a 15-second timeout before exiting cleanly.
This prevents dropped connections during Kubernetes pod termination.

Closes leeworks-agents/gitea-mobile#201

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 15:07:29 +00:00
6 changed files with 38 additions and 12 deletions
+34 -3
View File
@@ -1,10 +1,14 @@
package main
import (
"context"
"log"
"log/slog"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"gitea.leeworks.dev/0xwheatyz/gitea-mobile/internal/config"
giteaclient "gitea.leeworks.dev/0xwheatyz/gitea-mobile/internal/gitea"
@@ -36,8 +40,35 @@ func main() {
handler = middleware.Auth(cfg.SessionSecret, cfg.GiteaToken)(handler)
handler = middleware.Logging()(handler)
slog.Info("server starting", "addr", cfg.ListenAddr, "gitea_url", cfg.GiteaURL)
if err := http.ListenAndServe(cfg.ListenAddr, handler); err != nil {
log.Fatalf("server error: %v", err)
srv := &http.Server{
Addr: cfg.ListenAddr,
Handler: handler,
}
// Channel to receive shutdown signals.
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGTERM, syscall.SIGINT)
// Start server in a goroutine.
go func() {
slog.Info("server starting", "addr", cfg.ListenAddr, "gitea_url", cfg.GiteaURL)
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("server error: %v", err)
}
}()
// Block until a shutdown signal is received.
sig := <-quit
slog.Info("shutdown signal received, draining in-flight requests", "signal", sig.String())
// Give in-flight requests up to 15 seconds to complete.
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
slog.Error("server forced to shutdown", "error", err)
os.Exit(1)
}
slog.Info("server stopped gracefully")
}
+1 -1
View File
@@ -14,7 +14,7 @@
</span>
<span>{{.Issue.RepoOwner}}/{{.Issue.RepoName}} #{{.Issue.Number}}</span>
{{range .Issue.Labels}}
<span class="label label-pill" style="background-color:#{{.Color}};color:#fff;border:1px solid #{{.Color}}">{{.Name}}</span>
<span class="label" style="color:#{{.Color}};border:1px solid #{{.Color}}">{{.Name}}</span>
{{end}}
</div>
{{if .RenderedBody}}
+1 -1
View File
@@ -5,7 +5,7 @@
<div class="card-meta">
<span>{{.RepoOwner}}/{{.RepoName}} #{{.Number}}</span>
{{range .Labels}}
<span class="label label-pill" style="background-color:#{{.Color}};color:#fff;border:1px solid #{{.Color}}">{{.Name}}</span>
<span class="label" style="color:#{{.Color}};border:1px solid #{{.Color}}">{{.Name}}</span>
{{end}}
{{if .Assignee}}
<img src="{{.Assignee.AvatarURL}}" alt="{{.Assignee.Login}}" class="avatar" title="Assigned to {{.Assignee.Login}}">
+1 -1
View File
@@ -7,7 +7,7 @@
{{if eq .Pull.State "closed"}}<span class="state-closed">{{.Pull.State}}</span>{{else}}<span class="state-open">{{.Pull.State}}</span>{{end}}
<span>{{.Pull.RepoOwner}}/{{.Pull.RepoName}} #{{.Pull.Number}}</span>
{{range .Pull.Labels}}
<span class="label label-pill" style="background-color:#{{.Color}};color:#fff;border:1px solid #{{.Color}}">{{.Name}}</span>
<span class="label" style="color:#{{.Color}};border:1px solid #{{.Color}}">{{.Name}}</span>
{{end}}
</div>
<div class="card-meta" style="margin-top:0.5rem;">
+1 -1
View File
@@ -8,7 +8,7 @@
<div class="card-meta">
<span>{{.RepoOwner}}/{{.RepoName}} #{{.Number}}</span>
{{range .Labels}}
<span class="label label-pill" style="background-color:#{{.Color}};color:#fff;border:1px solid #{{.Color}}">{{.Name}}</span>
<span class="label" style="color:#{{.Color}};border:1px solid #{{.Color}}">{{.Name}}</span>
{{end}}
<span class="diff-add">+{{.Additions}}</span>
<span class="diff-del">-{{.Deletions}}</span>
-5
View File
@@ -176,11 +176,6 @@ a:active {
white-space: nowrap;
}
.label-pill {
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
font-weight: 600;
}
.type-badge {
font-size: 0.65rem;
text-transform: uppercase;