wait for all connections to close before exiting on shutdown.
Using httpServer.Shutdown will: 1. Close the listener (preventing new connections). 2. Close each connection as outstanding requests finish. This prevent us from shutting down before outstanding requests get a chance to respond. fixes #4055 License: MIT Signed-off-by: Steven Allen <steven@stebalien.com>
Steven Allen committed
Jul 31, 2018 at 12:45 UTC
00158c4b9240fa62743220f288a8c40fd4e850d6
2 files changed
+35
-25
core/corehttp/corehttp.go
+33
-23
@@ -5,6 +5,7 @@ high-level HTTP interfaces to IPFS.
5
package corehttp
6
7
import (
8
+ "context"
9
"fmt"
10
"net"
11
"net/http"
@@ -12,6 +13,7 @@ import (
13
14
core "github.com/ipfs/go-ipfs/core"
15
"gx/ipfs/QmSF8fPo3jgVBAy8fpdjjYqgG87dkJgUprRBHRd2tmfgpP/goprocess"
16
+ periodicproc "gx/ipfs/QmSF8fPo3jgVBAy8fpdjjYqgG87dkJgUprRBHRd2tmfgpP/goprocess/periodic"
17
manet "gx/ipfs/QmV6FjemM1K8oXjrvuq3wuVWWoU2TLDPmNnKrxHzY3v6Ai/go-multiaddr-net"
18
ma "gx/ipfs/QmYmsdtJ3HsodkePE3eU3TsCaP2YvPZJ4LoXnNkDE5Tpt7/go-multiaddr"
19
logging "gx/ipfs/QmcVVHfdyv15GVPk7NrxdWjh2hLVccXnoD8j2tyQShiXJb/go-log"
@@ -19,6 +21,10 @@ import (
21
22
var log = logging.Logger("core/server")
23
24
+// shutdownTimeout is the timeout after which we'll stop waiting for hung
25
+// commands to return on shutdown.
26
+const shutdownTimeout = 30 * time.Second
27
+
28
// ServeOption registers any HTTP handlers it provides on the given mux.
29
// It returns the mux to expose to future options, which may be a new mux if it
30
// is interested in mediating requests to future options, or the same mux
@@ -65,6 +71,9 @@ func ListenAndServe(n *core.IpfsNode, listeningMultiAddr string, options ...Serv
71
}
72
73
func Serve(node *core.IpfsNode, lis net.Listener, options ...ServeOption) error {
74
+ // make sure we close this no matter what.
75
+ defer lis.Close()
76
+
77
handler, err := makeHandler(node, lis, options...)
78
if err != nil {
79
return err
@@ -75,43 +84,44 @@ func Serve(node *core.IpfsNode, lis net.Listener, options ...ServeOption) error
84
return err
85
}
86
78
- // if the server exits beforehand
79
- var serverError error
80
- serverExited := make(chan struct{})
81
-
87
select {
88
case <-node.Process().Closing():
89
return fmt.Errorf("failed to start server, process closing")
90
default:
91
}
92
88
- node.Process().Go(func(p goprocess.Process) {
89
- serverError = http.Serve(lis, handler)
90
- close(serverExited)
93
+ server := &http.Server{
94
+ Handler: handler,
95
+ }
96
+
97
+ var serverError error
98
+ serverProc := node.Process().Go(func(p goprocess.Process) {
99
+ serverError = server.Serve(lis)
100
})
101
102
// wait for server to exit.
103
select {
95
- case <-serverExited:
96
-
104
+ case <-serverProc.Closed():
105
// if node being closed before server exits, close server
106
case <-node.Process().Closing():
107
log.Infof("server at %s terminating...", addr)
108
101
- lis.Close()
102
-
103
- outer:
104
- for {
105
- // wait until server exits
106
- select {
107
- case <-serverExited:
108
- // if the server exited as we are closing, we really dont care about errors
109
- serverError = nil
110
- break outer
111
- case <-time.After(5 * time.Second):
112
- log.Infof("waiting for server at %s to terminate...", addr)
113
- }
114
- }
109
+ warnProc := periodicproc.Tick(5*time.Second, func(_ goprocess.Process) {
110
+ log.Infof("waiting for server at %s to terminate...", addr)
111
+ })
112
+
113
+ // This timeout shouldn't be necessary if all of our commands
114
+ // are obeying their contexts but we should have *some* timeout.
115
+ ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
116
+ defer cancel()
117
+ err := server.Shutdown(ctx)
118
+
119
+ // Should have already closed but we still need to wait for it
120
+ // to set the error.
121
+ <-serverProc.Closed()
122
+ serverError = err
123
+
124
+ warnProc.Close()
125
}
126
127
log.Infof("server at %s terminated", addr)
test/sharness/t0023-shutdown.sh
+2
-2
@@ -13,7 +13,7 @@ test_init_ipfs
13
test_launch_ipfs_daemon
14
15
test_expect_success "shutdown succeeds" '
16
- ipfs shutdown || true # bug: https://github.com/ipfs/go-ipfs/issues/4055
16
+ ipfs shutdown
17
'
18
19
test_expect_success "daemon no longer running" '
@@ -27,7 +27,7 @@ test_expect_success "daemon no longer running" '
27
test_launch_ipfs_daemon --offline
28
29
test_expect_success "shutdown succeeds" '
30
- ipfs shutdown || true # bug: https://github.com/ipfs/go-ipfs/issues/4055
30
+ ipfs shutdown
31
'
32
33
test_expect_success "daemon no longer running" '