4138fe02 by astaxie

beego suppot graceful restart application

1 parent 245762f7
...@@ -19,9 +19,11 @@ import ( ...@@ -19,9 +19,11 @@ import (
19 "encoding/json" 19 "encoding/json"
20 "fmt" 20 "fmt"
21 "net/http" 21 "net/http"
22 "os"
22 "text/template" 23 "text/template"
23 "time" 24 "time"
24 25
26 "github.com/astaxie/beego/grace"
25 "github.com/astaxie/beego/toolbox" 27 "github.com/astaxie/beego/toolbox"
26 "github.com/astaxie/beego/utils" 28 "github.com/astaxie/beego/utils"
27 ) 29 )
...@@ -458,8 +460,14 @@ func (admin *adminApp) Run() { ...@@ -458,8 +460,14 @@ func (admin *adminApp) Run() {
458 http.Handle(p, f) 460 http.Handle(p, f)
459 } 461 }
460 BeeLogger.Info("Admin server Running on %s", addr) 462 BeeLogger.Info("Admin server Running on %s", addr)
461 err := http.ListenAndServe(addr, nil) 463
464 var err error
465 if Graceful {
466 err = grace.ListenAndServe(addr, nil)
467 } else {
468 err = http.ListenAndServe(addr, nil)
469 }
462 if err != nil { 470 if err != nil {
463 BeeLogger.Critical("Admin ListenAndServe: ", err) 471 BeeLogger.Critical("Admin ListenAndServe: ", err, fmt.Sprint(os.Getpid()))
464 } 472 }
465 } 473 }
......
...@@ -22,6 +22,7 @@ import ( ...@@ -22,6 +22,7 @@ import (
22 "os" 22 "os"
23 "time" 23 "time"
24 24
25 "github.com/astaxie/beego/grace"
25 "github.com/astaxie/beego/utils" 26 "github.com/astaxie/beego/utils"
26 ) 27 )
27 28
...@@ -76,57 +77,93 @@ func (app *App) Run() { ...@@ -76,57 +77,93 @@ func (app *App) Run() {
76 err = fcgi.Serve(l, app.Handlers) 77 err = fcgi.Serve(l, app.Handlers)
77 } 78 }
78 } else { 79 } else {
79 app.Server.Addr = addr 80 if Graceful {
80 app.Server.Handler = app.Handlers 81 if EnableHttpTLS {
81 app.Server.ReadTimeout = time.Duration(HttpServerTimeOut) * time.Second 82 go func() {
82 app.Server.WriteTimeout = time.Duration(HttpServerTimeOut) * time.Second 83 time.Sleep(20 * time.Microsecond)
83 84 if HttpsPort != 0 {
84 if EnableHttpTLS { 85 addr = fmt.Sprintf("%s:%d", HttpAddr, HttpsPort)
85 go func() { 86 }
86 time.Sleep(20 * time.Microsecond) 87 server := grace.NewServer(addr, app.Handlers)
87 if HttpsPort != 0 { 88 server.Server.ReadTimeout = time.Duration(HttpServerTimeOut) * time.Second
88 app.Server.Addr = fmt.Sprintf("%s:%d", HttpAddr, HttpsPort) 89 server.Server.WriteTimeout = time.Duration(HttpServerTimeOut) * time.Second
89 } 90 err := server.ListenAndServeTLS(HttpCertFile, HttpKeyFile)
90 BeeLogger.Info("https server Running on %s", app.Server.Addr)
91 err := app.Server.ListenAndServeTLS(HttpCertFile, HttpKeyFile)
92 if err != nil {
93 BeeLogger.Critical("ListenAndServeTLS: ", err)
94 time.Sleep(100 * time.Microsecond)
95 endRunning <- true
96 }
97 }()
98 }
99
100 if EnableHttpListen {
101 go func() {
102 app.Server.Addr = addr
103 BeeLogger.Info("http server Running on %s", app.Server.Addr)
104 if ListenTCP4 && HttpAddr == "" {
105 ln, err := net.Listen("tcp4", app.Server.Addr)
106 if err != nil { 91 if err != nil {
107 BeeLogger.Critical("ListenAndServe: ", err) 92 BeeLogger.Critical("ListenAndServeTLS: ", err)
108 time.Sleep(100 * time.Microsecond) 93 time.Sleep(100 * time.Microsecond)
109 endRunning <- true 94 endRunning <- true
110 return
111 } 95 }
112 err = app.Server.Serve(ln) 96 }()
97 }
98 if EnableHttpListen {
99 go func() {
100 server := grace.NewServer(addr, app.Handlers)
101 server.Server.ReadTimeout = time.Duration(HttpServerTimeOut) * time.Second
102 server.Server.WriteTimeout = time.Duration(HttpServerTimeOut) * time.Second
103 if ListenTCP4 && HttpAddr == "" {
104 server.Network = "tcp4"
105 }
106 err := server.ListenAndServe()
113 if err != nil { 107 if err != nil {
114 BeeLogger.Critical("ListenAndServe: ", err) 108 BeeLogger.Critical("ListenAndServe: ", err, fmt.Sprint(os.Getpid()))
115 time.Sleep(100 * time.Microsecond) 109 time.Sleep(100 * time.Microsecond)
116 endRunning <- true 110 endRunning <- true
117 return
118 } 111 }
119 } else { 112 }()
120 err := app.Server.ListenAndServe() 113 }
114 } else {
115 app.Server.Addr = addr
116 app.Server.Handler = app.Handlers
117 app.Server.ReadTimeout = time.Duration(HttpServerTimeOut) * time.Second
118 app.Server.WriteTimeout = time.Duration(HttpServerTimeOut) * time.Second
119
120 if EnableHttpTLS {
121 go func() {
122 time.Sleep(20 * time.Microsecond)
123 if HttpsPort != 0 {
124 app.Server.Addr = fmt.Sprintf("%s:%d", HttpAddr, HttpsPort)
125 }
126 BeeLogger.Info("https server Running on %s", app.Server.Addr)
127 err := app.Server.ListenAndServeTLS(HttpCertFile, HttpKeyFile)
121 if err != nil { 128 if err != nil {
122 BeeLogger.Critical("ListenAndServe: ", err) 129 BeeLogger.Critical("ListenAndServeTLS: ", err)
123 time.Sleep(100 * time.Microsecond) 130 time.Sleep(100 * time.Microsecond)
124 endRunning <- true 131 endRunning <- true
125 } 132 }
126 } 133 }()
127 }() 134 }
135
136 if EnableHttpListen {
137 go func() {
138 app.Server.Addr = addr
139 BeeLogger.Info("http server Running on %s", app.Server.Addr)
140 if ListenTCP4 && HttpAddr == "" {
141 ln, err := net.Listen("tcp4", app.Server.Addr)
142 if err != nil {
143 BeeLogger.Critical("ListenAndServe: ", err)
144 time.Sleep(100 * time.Microsecond)
145 endRunning <- true
146 return
147 }
148 err = app.Server.Serve(ln)
149 if err != nil {
150 BeeLogger.Critical("ListenAndServe: ", err)
151 time.Sleep(100 * time.Microsecond)
152 endRunning <- true
153 return
154 }
155 } else {
156 err := app.Server.ListenAndServe()
157 if err != nil {
158 BeeLogger.Critical("ListenAndServe: ", err)
159 time.Sleep(100 * time.Microsecond)
160 endRunning <- true
161 }
162 }
163 }()
164 }
128 } 165 }
129 }
130 166
167 }
131 <-endRunning 168 <-endRunning
132 } 169 }
......
...@@ -82,6 +82,7 @@ var ( ...@@ -82,6 +82,7 @@ var (
82 EnableDocs bool // enable generate docs & server docs API Swagger 82 EnableDocs bool // enable generate docs & server docs API Swagger
83 RouterCaseSensitive bool // router case sensitive default is true 83 RouterCaseSensitive bool // router case sensitive default is true
84 AccessLogs bool // print access logs, default is false 84 AccessLogs bool // print access logs, default is false
85 Graceful bool // use graceful start the server
85 ) 86 )
86 87
87 type beegoAppConfig struct { 88 type beegoAppConfig struct {
...@@ -509,5 +510,8 @@ func ParseConfig() (err error) { ...@@ -509,5 +510,8 @@ func ParseConfig() (err error) {
509 if casesensitive, err := AppConfig.Bool("RouterCaseSensitive"); err == nil { 510 if casesensitive, err := AppConfig.Bool("RouterCaseSensitive"); err == nil {
510 RouterCaseSensitive = casesensitive 511 RouterCaseSensitive = casesensitive
511 } 512 }
513 if graceful, err := AppConfig.Bool("Graceful"); err == nil {
514 Graceful = graceful
515 }
512 return nil 516 return nil
513 } 517 }
......
1 // Copyright 2014 beego Author. All Rights Reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 // Description: http://grisha.org/blog/2014/06/03/graceful-restart-in-golang/
16 //
17 // Usage:
18 //
19 // import(
20 // "log"
21 // "net/http"
22 // "os"
23 //
24 // "github.com/astaxie/beego/grace"
25 // )
26 //
27 // func handler(w http.ResponseWriter, r *http.Request) {
28 // w.Write([]byte("WORLD!"))
29 // }
30 //
31 // func main() {
32 // mux := http.NewServeMux()
33 // mux.HandleFunc("/hello", handler)
34 //
35 // err := grace.ListenAndServe("localhost:8080", mux1)
36 // if err != nil {
37 // log.Println(err)
38 // }
39 // log.Println("Server on 8080 stopped")
40 // os.Exit(0)
41 // }
42 package grace
43
44 import (
45 "crypto/tls"
46 "flag"
47 "fmt"
48 "log"
49 "net"
50 "net/http"
51 "os"
52 "os/exec"
53 "os/signal"
54 "strings"
55 "sync"
56 "syscall"
57 "time"
58 )
59
60 const (
61 PRE_SIGNAL = iota
62 POST_SIGNAL
63
64 STATE_INIT
65 STATE_RUNNING
66 STATE_SHUTTING_DOWN
67 STATE_TERMINATE
68 )
69
70 var (
71 regLock *sync.Mutex
72 runningServers map[string]*graceServer
73 runningServersOrder []string
74 socketPtrOffsetMap map[string]uint
75 runningServersForked bool
76
77 DefaultReadTimeOut time.Duration
78 DefaultWriteTimeOut time.Duration
79 DefaultMaxHeaderBytes int
80 DefaultTimeout time.Duration
81
82 isChild bool
83 socketOrder string
84 )
85
86 func init() {
87 regLock = &sync.Mutex{}
88 flag.BoolVar(&isChild, "graceful", false, "listen on open fd (after forking)")
89 flag.StringVar(&socketOrder, "socketorder", "", "previous initialization order - used when more than one listener was started")
90 runningServers = make(map[string]*graceServer)
91 runningServersOrder = []string{}
92 socketPtrOffsetMap = make(map[string]uint)
93
94 DefaultMaxHeaderBytes = 0
95
96 // after a restart the parent will finish ongoing requests before
97 // shutting down. set to a negative value to disable
98 DefaultTimeout = 60 * time.Second
99 }
100
101 type graceServer struct {
102 http.Server
103 GraceListener net.Listener
104 SignalHooks map[int]map[os.Signal][]func()
105 tlsInnerListener *graceListener
106 wg sync.WaitGroup
107 sigChan chan os.Signal
108 isChild bool
109 state uint8
110 Network string
111 }
112
113 // NewServer returns an intialized graceServer. Calling Serve on it will
114 // actually "start" the server.
115 func NewServer(addr string, handler http.Handler) (srv *graceServer) {
116 regLock.Lock()
117 defer regLock.Unlock()
118 if !flag.Parsed() {
119 flag.Parse()
120 }
121 if len(socketOrder) > 0 {
122 for i, addr := range strings.Split(socketOrder, ",") {
123 socketPtrOffsetMap[addr] = uint(i)
124 }
125 } else {
126 socketPtrOffsetMap[addr] = uint(len(runningServersOrder))
127 }
128
129 srv = &graceServer{
130 wg: sync.WaitGroup{},
131 sigChan: make(chan os.Signal),
132 isChild: isChild,
133 SignalHooks: map[int]map[os.Signal][]func(){
134 PRE_SIGNAL: map[os.Signal][]func(){
135 syscall.SIGHUP: []func(){},
136 syscall.SIGUSR1: []func(){},
137 syscall.SIGUSR2: []func(){},
138 syscall.SIGINT: []func(){},
139 syscall.SIGTERM: []func(){},
140 syscall.SIGTSTP: []func(){},
141 },
142 POST_SIGNAL: map[os.Signal][]func(){
143 syscall.SIGHUP: []func(){},
144 syscall.SIGUSR1: []func(){},
145 syscall.SIGUSR2: []func(){},
146 syscall.SIGINT: []func(){},
147 syscall.SIGTERM: []func(){},
148 syscall.SIGTSTP: []func(){},
149 },
150 },
151 state: STATE_INIT,
152 Network: "tcp",
153 }
154
155 srv.Server.Addr = addr
156 srv.Server.ReadTimeout = DefaultReadTimeOut
157 srv.Server.WriteTimeout = DefaultWriteTimeOut
158 srv.Server.MaxHeaderBytes = DefaultMaxHeaderBytes
159 srv.Server.Handler = handler
160
161 runningServersOrder = append(runningServersOrder, addr)
162 runningServers[addr] = srv
163
164 return
165 }
166
167 // ListenAndServe listens on the TCP network address addr
168 // and then calls Serve to handle requests on incoming connections.
169 func ListenAndServe(addr string, handler http.Handler) error {
170 server := NewServer(addr, handler)
171 return server.ListenAndServe()
172 }
173
174 // ListenAndServeTLS listens on the TCP network address addr and then calls
175 // Serve to handle requests on incoming TLS connections.
176 //
177 // Filenames containing a certificate and matching private key for the server must be provided.
178 // If the certificate is signed by a certificate authority,
179 // the certFile should be the concatenation of the server's certificate followed by the CA's certificate.
180 func ListenAndServeTLS(addr string, certFile string, keyFile string, handler http.Handler) error {
181 server := NewServer(addr, handler)
182 return server.ListenAndServeTLS(certFile, keyFile)
183 }
184
185 // Serve accepts incoming connections on the Listener l,
186 // creating a new service goroutine for each.
187 // The service goroutines read requests and then call srv.Handler to reply to them.
188 func (srv *graceServer) Serve() (err error) {
189 srv.state = STATE_RUNNING
190 err = srv.Server.Serve(srv.GraceListener)
191 log.Println(syscall.Getpid(), "Waiting for connections to finish...")
192 srv.wg.Wait()
193 srv.state = STATE_TERMINATE
194 return
195 }
196
197 // ListenAndServe listens on the TCP network address srv.Addr and then calls Serve
198 // to handle requests on incoming connections. If srv.Addr is blank, ":http" is
199 // used.
200 func (srv *graceServer) ListenAndServe() (err error) {
201 addr := srv.Addr
202 if addr == "" {
203 addr = ":http"
204 }
205
206 go srv.handleSignals()
207
208 l, err := srv.getListener(addr)
209 if err != nil {
210 log.Println(err)
211 return
212 }
213
214 srv.GraceListener = newGraceListener(l, srv)
215
216 if srv.isChild {
217 syscall.Kill(syscall.Getppid(), syscall.SIGTERM)
218 }
219
220 log.Println(syscall.Getpid(), srv.Addr)
221 return srv.Serve()
222 }
223
224 // ListenAndServeTLS listens on the TCP network address srv.Addr and then calls
225 // Serve to handle requests on incoming TLS connections.
226 //
227 // Filenames containing a certificate and matching private key for the server must
228 // be provided. If the certificate is signed by a certificate authority, the
229 // certFile should be the concatenation of the server's certificate followed by the
230 // CA's certificate.
231 //
232 // If srv.Addr is blank, ":https" is used.
233 func (srv *graceServer) ListenAndServeTLS(certFile, keyFile string) (err error) {
234 addr := srv.Addr
235 if addr == "" {
236 addr = ":https"
237 }
238
239 config := &tls.Config{}
240 if srv.TLSConfig != nil {
241 *config = *srv.TLSConfig
242 }
243 if config.NextProtos == nil {
244 config.NextProtos = []string{"http/1.1"}
245 }
246
247 config.Certificates = make([]tls.Certificate, 1)
248 config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
249 if err != nil {
250 return
251 }
252
253 go srv.handleSignals()
254
255 l, err := srv.getListener(addr)
256 if err != nil {
257 log.Println(err)
258 return
259 }
260
261 srv.tlsInnerListener = newGraceListener(l, srv)
262 srv.GraceListener = tls.NewListener(srv.tlsInnerListener, config)
263
264 if srv.isChild {
265 syscall.Kill(syscall.Getppid(), syscall.SIGTERM)
266 }
267
268 log.Println(syscall.Getpid(), srv.Addr)
269 return srv.Serve()
270 }
271
272 // getListener either opens a new socket to listen on, or takes the acceptor socket
273 // it got passed when restarted.
274 func (srv *graceServer) getListener(laddr string) (l net.Listener, err error) {
275 if srv.isChild {
276 var ptrOffset uint = 0
277 if len(socketPtrOffsetMap) > 0 {
278 ptrOffset = socketPtrOffsetMap[laddr]
279 log.Println("laddr", laddr, "ptr offset", socketPtrOffsetMap[laddr])
280 }
281
282 f := os.NewFile(uintptr(3+ptrOffset), "")
283 l, err = net.FileListener(f)
284 if err != nil {
285 err = fmt.Errorf("net.FileListener error: %v", err)
286 return
287 }
288 } else {
289 l, err = net.Listen(srv.Network, laddr)
290 if err != nil {
291 err = fmt.Errorf("net.Listen error: %v", err)
292 return
293 }
294 }
295 return
296 }
297
298 // handleSignals listens for os Signals and calls any hooked in function that the
299 // user had registered with the signal.
300 func (srv *graceServer) handleSignals() {
301 var sig os.Signal
302
303 signal.Notify(
304 srv.sigChan,
305 syscall.SIGHUP,
306 syscall.SIGUSR1,
307 syscall.SIGUSR2,
308 syscall.SIGINT,
309 syscall.SIGTERM,
310 syscall.SIGTSTP,
311 )
312
313 pid := syscall.Getpid()
314 for {
315 sig = <-srv.sigChan
316 srv.signalHooks(PRE_SIGNAL, sig)
317 switch sig {
318 case syscall.SIGHUP:
319 log.Println(pid, "Received SIGHUP. forking.")
320 err := srv.fork()
321 if err != nil {
322 log.Println("Fork err:", err)
323 }
324 case syscall.SIGUSR1:
325 log.Println(pid, "Received SIGUSR1.")
326 case syscall.SIGUSR2:
327 log.Println(pid, "Received SIGUSR2.")
328 srv.serverTimeout(0 * time.Second)
329 case syscall.SIGINT:
330 log.Println(pid, "Received SIGINT.")
331 srv.shutdown()
332 case syscall.SIGTERM:
333 log.Println(pid, "Received SIGTERM.")
334 srv.shutdown()
335 case syscall.SIGTSTP:
336 log.Println(pid, "Received SIGTSTP.")
337 default:
338 log.Printf("Received %v: nothing i care about...\n", sig)
339 }
340 srv.signalHooks(POST_SIGNAL, sig)
341 }
342 }
343
344 func (srv *graceServer) signalHooks(ppFlag int, sig os.Signal) {
345 if _, notSet := srv.SignalHooks[ppFlag][sig]; !notSet {
346 return
347 }
348 for _, f := range srv.SignalHooks[ppFlag][sig] {
349 f()
350 }
351 return
352 }
353
354 // shutdown closes the listener so that no new connections are accepted. it also
355 // starts a goroutine that will hammer (stop all running requests) the server
356 // after DefaultTimeout.
357 func (srv *graceServer) shutdown() {
358 if srv.state != STATE_RUNNING {
359 return
360 }
361
362 srv.state = STATE_SHUTTING_DOWN
363 if DefaultTimeout >= 0 {
364 go srv.serverTimeout(DefaultTimeout)
365 }
366 err := srv.GraceListener.Close()
367 if err != nil {
368 log.Println(syscall.Getpid(), "Listener.Close() error:", err)
369 } else {
370 log.Println(syscall.Getpid(), srv.GraceListener.Addr(), "Listener closed.")
371 }
372 }
373
374 // hammerTime forces the server to shutdown in a given timeout - whether it
375 // finished outstanding requests or not. if Read/WriteTimeout are not set or the
376 // max header size is very big a connection could hang...
377 //
378 // srv.Serve() will not return until all connections are served. this will
379 // unblock the srv.wg.Wait() in Serve() thus causing ListenAndServe(TLS) to
380 // return.
381 func (srv *graceServer) serverTimeout(d time.Duration) {
382 defer func() {
383 // we are calling srv.wg.Done() until it panics which means we called
384 // Done() when the counter was already at 0 and we're done.
385 // (and thus Serve() will return and the parent will exit)
386 if r := recover(); r != nil {
387 log.Println("WaitGroup at 0", r)
388 }
389 }()
390 if srv.state != STATE_SHUTTING_DOWN {
391 return
392 }
393 time.Sleep(d)
394 log.Println("[STOP - Hammer Time] Forcefully shutting down parent")
395 for {
396 if srv.state == STATE_TERMINATE {
397 break
398 }
399 srv.wg.Done()
400 }
401 }
402
403 func (srv *graceServer) fork() (err error) {
404 // only one server isntance should fork!
405 regLock.Lock()
406 defer regLock.Unlock()
407 if runningServersForked {
408 return
409 }
410 runningServersForked = true
411
412 var files = make([]*os.File, len(runningServers))
413 var orderArgs = make([]string, len(runningServers))
414 // get the accessor socket fds for _all_ server instances
415 for _, srvPtr := range runningServers {
416 // introspect.PrintTypeDump(srvPtr.EndlessListener)
417 switch srvPtr.GraceListener.(type) {
418 case *graceListener:
419 // normal listener
420 files[socketPtrOffsetMap[srvPtr.Server.Addr]] = srvPtr.GraceListener.(*graceListener).File()
421 default:
422 // tls listener
423 files[socketPtrOffsetMap[srvPtr.Server.Addr]] = srvPtr.tlsInnerListener.File()
424 }
425 orderArgs[socketPtrOffsetMap[srvPtr.Server.Addr]] = srvPtr.Server.Addr
426 }
427
428 log.Println(files)
429 path := os.Args[0]
430 var args []string
431 if len(os.Args) > 1 {
432 for _, arg := range os.Args[1:] {
433 if arg == "-graceful" {
434 break
435 }
436 args = append(args, arg)
437 }
438 }
439 args = append(args, "-graceful")
440 if len(runningServers) > 1 {
441 args = append(args, fmt.Sprintf(`-socketorder=%s`, strings.Join(orderArgs, ",")))
442 log.Println(args)
443 }
444 cmd := exec.Command(path, args...)
445 cmd.Stdout = os.Stdout
446 cmd.Stderr = os.Stderr
447 cmd.ExtraFiles = files
448 err = cmd.Start()
449 if err != nil {
450 log.Fatalf("Restart: Failed to launch, error: %v", err)
451 }
452
453 return
454 }
455
456 type graceListener struct {
457 net.Listener
458 stop chan error
459 stopped bool
460 server *graceServer
461 }
462
463 func (gl *graceListener) Accept() (c net.Conn, err error) {
464 tc, err := gl.Listener.(*net.TCPListener).AcceptTCP()
465 if err != nil {
466 return
467 }
468
469 tc.SetKeepAlive(true) // see http.tcpKeepAliveListener
470 tc.SetKeepAlivePeriod(3 * time.Minute) // see http.tcpKeepAliveListener
471
472 c = graceConn{
473 Conn: tc,
474 server: gl.server,
475 }
476
477 gl.server.wg.Add(1)
478 return
479 }
480
481 func newGraceListener(l net.Listener, srv *graceServer) (el *graceListener) {
482 el = &graceListener{
483 Listener: l,
484 stop: make(chan error),
485 server: srv,
486 }
487
488 // Starting the listener for the stop signal here because Accept blocks on
489 // el.Listener.(*net.TCPListener).AcceptTCP()
490 // The goroutine will unblock it by closing the listeners fd
491 go func() {
492 _ = <-el.stop
493 el.stopped = true
494 el.stop <- el.Listener.Close()
495 }()
496 return
497 }
498
499 func (el *graceListener) Close() error {
500 if el.stopped {
501 return syscall.EINVAL
502 }
503 el.stop <- nil
504 return <-el.stop
505 }
506
507 func (el *graceListener) File() *os.File {
508 // returns a dup(2) - FD_CLOEXEC flag *not* set
509 tl := el.Listener.(*net.TCPListener)
510 fl, _ := tl.File()
511 return fl
512 }
513
514 type graceConn struct {
515 net.Conn
516 server *graceServer
517 }
518
519 func (c graceConn) Close() error {
520 c.server.wg.Done()
521 return c.Conn.Close()
522 }
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!