From a2592e0700904cd1df0ce3764204c207050c1a7d Mon Sep 17 00:00:00 2001 From: daveads Date: Sun, 6 Oct 2024 05:08:40 +0100 Subject: [PATCH] race fix --- xhttp/serve.go | 61 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 7 deletions(-) diff --git a/xhttp/serve.go b/xhttp/serve.go index 93ccc25..639600f 100644 --- a/xhttp/serve.go +++ b/xhttp/serve.go @@ -1,11 +1,15 @@ // Package xhttp implements http helpers. +// package xhttp import ( "context" + "errors" "log" "net" "net/http" + "sync" + "sync/atomic" "time" "oss.terrastruct.com/util-go/xcontext" @@ -22,23 +26,66 @@ func NewServer(log *log.Logger, h http.Handler) *http.Server { } } +type safeServer struct { + *http.Server + running int32 + mu sync.Mutex +} + +func newSafeServer(s *http.Server) *safeServer { + return &safeServer{ + Server: s, + } +} + +func (s *safeServer) ListenAndServe(l net.Listener) error { + s.mu.Lock() + defer s.mu.Unlock() + + if !atomic.CompareAndSwapInt32(&s.running, 0, 1) { + return errors.New("server is already running") + } + defer atomic.StoreInt32(&s.running, 0) + + return s.Serve(l) +} + +func (s *safeServer) Shutdown(ctx context.Context) error { + s.mu.Lock() + defer s.mu.Unlock() + + if atomic.LoadInt32(&s.running) == 0 { + return nil + } + + return s.Server.Shutdown(ctx) +} + func Serve(ctx context.Context, shutdownTimeout time.Duration, s *http.Server, l net.Listener) error { s.BaseContext = func(net.Listener) context.Context { return ctx } - done := make(chan error, 1) + ss := newSafeServer(s) + + serverClosed := make(chan struct{}) + var serverError error go func() { - done <- s.Serve(l) + serverError = ss.ListenAndServe(l) + close(serverClosed) }() select { - case err := <-done: - return err + case <-serverClosed: + return serverError case <-ctx.Done(): - ctx = xcontext.WithoutCancel(ctx) - ctx, cancel := context.WithTimeout(ctx, shutdownTimeout) + shutdownCtx, cancel := context.WithTimeout(xcontext.WithoutCancel(ctx), shutdownTimeout) defer cancel() - return s.Shutdown(ctx) + err := ss.Shutdown(shutdownCtx) + <-serverClosed // Wait for server to exit + if err != nil { + return err + } + return serverError } }