1
1
// Package xhttp implements http helpers.
2
+ //
2
3
package xhttp
3
4
4
5
import (
5
6
"context"
7
+ "errors"
6
8
"log"
7
9
"net"
8
10
"net/http"
11
+ "sync"
12
+ "sync/atomic"
9
13
"time"
10
14
11
15
"oss.terrastruct.com/util-go/xcontext"
@@ -22,23 +26,66 @@ func NewServer(log *log.Logger, h http.Handler) *http.Server {
22
26
}
23
27
}
24
28
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
+
25
64
func Serve (ctx context.Context , shutdownTimeout time.Duration , s * http.Server , l net.Listener ) error {
26
65
s .BaseContext = func (net.Listener ) context.Context {
27
66
return ctx
28
67
}
29
68
30
- done := make (chan error , 1 )
69
+ ss := newSafeServer (s )
70
+
71
+ serverClosed := make (chan struct {})
72
+ var serverError error
31
73
go func () {
32
- done <- s .Serve (l )
74
+ serverError = ss .ListenAndServe (l )
75
+ close (serverClosed )
33
76
}()
34
77
35
78
select {
36
- case err := <- done :
37
- return err
79
+ case <- serverClosed :
80
+ return serverError
38
81
case <- ctx .Done ():
39
- ctx = xcontext .WithoutCancel (ctx )
40
- ctx , cancel := context .WithTimeout (ctx , shutdownTimeout )
82
+ shutdownCtx , cancel := context .WithTimeout (xcontext .WithoutCancel (ctx ), shutdownTimeout )
41
83
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
43
90
}
44
91
}
0 commit comments