@@ -3,9 +3,12 @@ package xhttp
3
3
4
4
import (
5
5
"context"
6
+ "errors"
6
7
"log"
7
8
"net"
8
9
"net/http"
10
+ "sync"
11
+ "sync/atomic"
9
12
"time"
10
13
11
14
"oss.terrastruct.com/util-go/xcontext"
@@ -22,23 +25,68 @@ func NewServer(log *log.Logger, h http.Handler) *http.Server {
22
25
}
23
26
}
24
27
28
+ type safeServer struct {
29
+ * http.Server
30
+ running int32
31
+ mu sync.Mutex
32
+ }
33
+
34
+ func newSafeServer (s * http.Server ) * safeServer {
35
+ return & safeServer {
36
+ Server : s ,
37
+ }
38
+ }
39
+
40
+ func (s * safeServer ) ListenAndServe (l net.Listener ) error {
41
+ s .mu .Lock ()
42
+ defer s .mu .Unlock ()
43
+
44
+ if ! atomic .CompareAndSwapInt32 (& s .running , 0 , 1 ) {
45
+ return errors .New ("server is already running" )
46
+ }
47
+ defer atomic .StoreInt32 (& s .running , 0 )
48
+
49
+ return s .Serve (l )
50
+ }
51
+
52
+ func (s * safeServer ) Shutdown (ctx context.Context ) error {
53
+ s .mu .Lock ()
54
+ defer s .mu .Unlock ()
55
+
56
+ if atomic .LoadInt32 (& s .running ) == 0 {
57
+ return nil
58
+ }
59
+
60
+ return s .Server .Shutdown (ctx )
61
+ }
62
+
25
63
func Serve (ctx context.Context , shutdownTimeout time.Duration , s * http.Server , l net.Listener ) error {
26
64
s .BaseContext = func (net.Listener ) context.Context {
27
65
return ctx
28
66
}
29
67
30
- done := make (chan error , 1 )
68
+ ss := newSafeServer (s )
69
+
70
+ serverClosed := make (chan struct {})
71
+ var serverError error
31
72
go func () {
32
- done <- s .Serve (l )
73
+ serverError = ss .ListenAndServe (l )
74
+ close (serverClosed )
33
75
}()
34
76
35
77
select {
36
- case err := <- done :
37
- return err
78
+ case <- serverClosed :
79
+ return serverError
38
80
case <- ctx .Done ():
39
- ctx = xcontext .WithoutCancel (ctx )
40
- ctx , cancel := context .WithTimeout (ctx , shutdownTimeout )
81
+ shutdownCtx , cancel := context .WithTimeout (xcontext .WithoutCancel (ctx ), shutdownTimeout )
41
82
defer cancel ()
42
- return s .Shutdown (ctx )
83
+
84
+ err := ss .Shutdown (shutdownCtx )
85
+ <- serverClosed // Wait for server to exit
86
+ if err != nil {
87
+ return err
88
+ }
89
+ return serverError
43
90
}
91
+
44
92
}
0 commit comments