defer afterTest(t)
var doShutdown func() // set later
var shutdownRes = make(chan error, 1)
- cst := newClientServerTest(t, h2, HandlerFunc(func(w ResponseWriter, r *Request) {
+ var gotOnShutdown = make(chan struct{}, 1)
+ handler := HandlerFunc(func(w ResponseWriter, r *Request) {
go doShutdown()
// Shutdown is graceful, so it should not interrupt
// this in-flight response. Add a tiny sleep here to
// bugs.
time.Sleep(20 * time.Millisecond)
io.WriteString(w, r.RemoteAddr)
- }))
+ })
+ cst := newClientServerTest(t, h2, handler, func(srv *httptest.Server) {
+ srv.Config.RegisterOnShutdown(func() { gotOnShutdown <- struct{}{} })
+ })
defer cst.close()
doShutdown = func() {
if err := <-shutdownRes; err != nil {
t.Fatalf("Shutdown: %v", err)
}
+ select {
+ case <-gotOnShutdown:
+ case <-time.After(5 * time.Second):
+ t.Errorf("onShutdown callback not called, RegisterOnShutdown broken?")
+ }
res, err := cst.c.Get(cst.ts.URL)
if err == nil {
listeners map[net.Listener]struct{}
activeConn map[*conn]struct{}
doneChan chan struct{}
+ onShutdown []func()
}
func (s *Server) getDoneChan() <-chan struct{} {
srv.mu.Lock()
lnerr := srv.closeListenersLocked()
srv.closeDoneChanLocked()
+ for _, f := range srv.onShutdown {
+ go f()
+ }
srv.mu.Unlock()
ticker := time.NewTicker(shutdownPollInterval)
}
}
+// RegisterOnShutdown registers a function to call on Shutdown.
+// This can be used to gracefully shutdown connections that have
+// undergone NPN/ALPN protocol upgrade or that have been hijacked.
+// This function should start protocol-specific graceful shutdown,
+// but should not wait for shutdown to complete.
+func (srv *Server) RegisterOnShutdown(f func()) {
+ srv.mu.Lock()
+ srv.onShutdown = append(srv.onShutdown, f)
+ srv.mu.Unlock()
+}
+
// closeIdleConns closes all idle connections and reports whether the
// server is quiescent.
func (s *Server) closeIdleConns() bool {