Skip to content

Commit a3791a3

Browse files
committed
fix for race issue
1 parent 51f1706 commit a3791a3

File tree

1 file changed

+54
-7
lines changed

1 file changed

+54
-7
lines changed

xhttp/serve.go

+54-7
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
// Package xhttp implements http helpers.
2+
//
23
package xhttp
34

45
import (
56
"context"
7+
"errors"
68
"log"
79
"net"
810
"net/http"
11+
"sync"
12+
"sync/atomic"
913
"time"
1014

1115
"oss.terrastruct.com/util-go/xcontext"
@@ -22,23 +26,66 @@ func NewServer(log *log.Logger, h http.Handler) *http.Server {
2226
}
2327
}
2428

29+
type safeServer struct {
30+
*http.Server
31+
running int32
32+
mu sync.Mutex
33+
}
34+
35+
func newSafeServer(s *http.Server) *safeServer {
36+
return &safeServer{
37+
Server: s,
38+
}
39+
}
40+
41+
func (s *safeServer) ListenAndServe(l net.Listener) error {
42+
s.mu.Lock()
43+
defer s.mu.Unlock()
44+
45+
if !atomic.CompareAndSwapInt32(&s.running, 0, 1) {
46+
return errors.New("server is already running")
47+
}
48+
defer atomic.StoreInt32(&s.running, 0)
49+
50+
return s.Serve(l)
51+
}
52+
53+
func (s *safeServer) Shutdown(ctx context.Context) error {
54+
s.mu.Lock()
55+
defer s.mu.Unlock()
56+
57+
if atomic.LoadInt32(&s.running) == 0 {
58+
return nil
59+
}
60+
61+
return s.Server.Shutdown(ctx)
62+
}
63+
2564
func Serve(ctx context.Context, shutdownTimeout time.Duration, s *http.Server, l net.Listener) error {
2665
s.BaseContext = func(net.Listener) context.Context {
2766
return ctx
2867
}
2968

30-
done := make(chan error, 1)
69+
ss := newSafeServer(s)
70+
71+
serverClosed := make(chan struct{})
72+
var serverError error
3173
go func() {
32-
done <- s.Serve(l)
74+
serverError = ss.ListenAndServe(l)
75+
close(serverClosed)
3376
}()
3477

3578
select {
36-
case err := <-done:
37-
return err
79+
case <-serverClosed:
80+
return serverError
3881
case <-ctx.Done():
39-
ctx = xcontext.WithoutCancel(ctx)
40-
ctx, cancel := context.WithTimeout(ctx, shutdownTimeout)
82+
shutdownCtx, cancel := context.WithTimeout(xcontext.WithoutCancel(ctx), shutdownTimeout)
4183
defer cancel()
42-
return s.Shutdown(ctx)
84+
err := ss.Shutdown(shutdownCtx)
85+
<-serverClosed // Wait for server to exit
86+
if err != nil {
87+
return err
88+
}
89+
return serverError
4390
}
4491
}

0 commit comments

Comments
 (0)