server: Revert to closing contexts on Shutdown
This commit is contained in:
parent
c5b304216c
commit
35f7958083
@ -17,10 +17,6 @@ var (
|
||||
// when the response status code does not permit a body.
|
||||
ErrBodyNotAllowed = errors.New("gemini: response status code does not allow body")
|
||||
|
||||
// ErrServerClosed is returned by the Server's Serve and ListenAndServe
|
||||
// methods after a call to Shutdown or Close.
|
||||
ErrServerClosed = errors.New("gemini: server closed")
|
||||
|
||||
// ErrHandlerTimeout is returned on ResponseWriter Write calls
|
||||
// in handlers which have timed out.
|
||||
ErrHandlerTimeout = errors.New("gemini: Handler timeout")
|
||||
|
166
server.go
166
server.go
@ -45,11 +45,12 @@ type Server struct {
|
||||
// If nil, logging is done via the log package's standard logger.
|
||||
ErrorLog *log.Logger
|
||||
|
||||
listeners map[*net.Listener]struct{}
|
||||
conns map[*net.Conn]struct{}
|
||||
closedChan chan struct{} // closed when the server is closed
|
||||
doneChan chan struct{} // closed when no more connections are open
|
||||
mu sync.Mutex
|
||||
listeners map[*net.Listener]context.CancelFunc
|
||||
conns map[*net.Conn]context.CancelFunc
|
||||
closed bool // true if Closed or Shutdown called
|
||||
shutdown bool // true if Shutdown called
|
||||
doneChan chan struct{}
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
const (
|
||||
@ -58,18 +59,10 @@ const (
|
||||
serverClosed
|
||||
)
|
||||
|
||||
// closed returns a channel that's closed when the server is closed.
|
||||
func (srv *Server) closed() chan struct{} {
|
||||
func (srv *Server) isClosed() bool {
|
||||
srv.mu.Lock()
|
||||
defer srv.mu.Unlock()
|
||||
return srv.closedLocked()
|
||||
}
|
||||
|
||||
func (srv *Server) closedLocked() chan struct{} {
|
||||
if srv.closedChan == nil {
|
||||
srv.closedChan = make(chan struct{})
|
||||
}
|
||||
return srv.closedChan
|
||||
return srv.closed
|
||||
}
|
||||
|
||||
// done returns a channel that's closed when the server is closed and
|
||||
@ -87,14 +80,16 @@ func (srv *Server) doneLocked() chan struct{} {
|
||||
return srv.doneChan
|
||||
}
|
||||
|
||||
// tryFinishShutdown closes srv.done() if the server is closed and
|
||||
// tryCloseDone closes srv.done() if the server is closed and
|
||||
// there are no active listeners or connections.
|
||||
func (srv *Server) tryFinishShutdown() {
|
||||
func (srv *Server) tryCloseDone() {
|
||||
srv.mu.Lock()
|
||||
defer srv.mu.Unlock()
|
||||
select {
|
||||
case <-srv.closedLocked():
|
||||
default:
|
||||
srv.tryCloseDoneLocked()
|
||||
}
|
||||
|
||||
func (srv *Server) tryCloseDoneLocked() {
|
||||
if !srv.closed {
|
||||
return
|
||||
}
|
||||
if len(srv.listeners) == 0 && len(srv.conns) == 0 {
|
||||
@ -107,23 +102,27 @@ func (srv *Server) tryFinishShutdown() {
|
||||
}
|
||||
}
|
||||
|
||||
// Close immediately closes all active net.Listeners and connections.
|
||||
// Close immediately closes all active net.Listeners and connections
|
||||
// by cancelling their contexts.
|
||||
// For a graceful shutdown, use Shutdown.
|
||||
func (srv *Server) Close() error {
|
||||
ch := srv.closed()
|
||||
select {
|
||||
case <-ch:
|
||||
return nil
|
||||
default:
|
||||
close(ch)
|
||||
}
|
||||
|
||||
srv.tryFinishShutdown()
|
||||
|
||||
// Force all active connections to close.
|
||||
srv.mu.Lock()
|
||||
for conn := range srv.conns {
|
||||
(*conn).Close()
|
||||
{
|
||||
if srv.closed {
|
||||
srv.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
srv.closed = true
|
||||
|
||||
srv.tryCloseDoneLocked()
|
||||
|
||||
// Close all active connections and listeners.
|
||||
for _, cancel := range srv.listeners {
|
||||
cancel()
|
||||
}
|
||||
for _, cancel := range srv.conns {
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
srv.mu.Unlock()
|
||||
|
||||
@ -134,28 +133,34 @@ func (srv *Server) Close() error {
|
||||
}
|
||||
|
||||
// Shutdown gracefully shuts down the server without interrupting any
|
||||
// active connections. Shutdown works by first closing all open
|
||||
// listeners and then waiting indefinitely for connections
|
||||
// to close and then shut down.
|
||||
// active connections. Shutdown works by first cancelling the contexts
|
||||
// of all open listeners and then waiting indefinitely for connections
|
||||
// to close.
|
||||
// If the provided context expires before the shutdown is complete,
|
||||
// Shutdown returns the context's error.
|
||||
//
|
||||
// When Shutdown is called, Serve and ListenAndServe immediately
|
||||
// return ErrServerClosed. Make sure the program doesn't exit and
|
||||
// waits instead for Shutdown to return.
|
||||
// return an error. Make sure the program doesn't exit and waits instead for
|
||||
// Shutdown to return.
|
||||
//
|
||||
// Once Shutdown has been called on a server, it may not be reused;
|
||||
// future calls to methods such as Serve will return ErrServerClosed.
|
||||
func (srv *Server) Shutdown(ctx context.Context) error {
|
||||
ch := srv.closed()
|
||||
select {
|
||||
case <-ch:
|
||||
return nil
|
||||
default:
|
||||
close(ch)
|
||||
}
|
||||
srv.mu.Lock()
|
||||
{
|
||||
if srv.closed {
|
||||
srv.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
srv.closed = true
|
||||
srv.shutdown = true
|
||||
|
||||
srv.tryFinishShutdown()
|
||||
// Close all active listeners.
|
||||
for _, cancel := range srv.listeners {
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
srv.mu.Unlock()
|
||||
|
||||
// Wait for active connections to finish.
|
||||
select {
|
||||
@ -172,13 +177,13 @@ func (srv *Server) Shutdown(ctx context.Context) error {
|
||||
//
|
||||
// If srv.Addr is blank, ":1965" is used.
|
||||
//
|
||||
// ListenAndServe always returns a non-nil error. After Shutdown or Close, the
|
||||
// returned error is ErrServerClosed.
|
||||
// ListenAndServe always returns a non-nil error.
|
||||
func (srv *Server) ListenAndServe(ctx context.Context) error {
|
||||
select {
|
||||
case <-srv.closed():
|
||||
return ErrServerClosed
|
||||
default:
|
||||
if srv.isClosed() {
|
||||
// Cancel context
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
cancel()
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
addr := srv.Addr
|
||||
@ -206,18 +211,16 @@ func (srv *Server) getCertificate(h *tls.ClientHelloInfo) (*tls.Certificate, err
|
||||
return srv.GetCertificate(h.ServerName)
|
||||
}
|
||||
|
||||
func (srv *Server) trackListener(l *net.Listener) bool {
|
||||
func (srv *Server) trackListener(l *net.Listener, cancel context.CancelFunc) bool {
|
||||
srv.mu.Lock()
|
||||
defer srv.mu.Unlock()
|
||||
select {
|
||||
case <-srv.closedLocked():
|
||||
if srv.closed {
|
||||
return false
|
||||
default:
|
||||
}
|
||||
if srv.listeners == nil {
|
||||
srv.listeners = make(map[*net.Listener]struct{})
|
||||
srv.listeners = make(map[*net.Listener]context.CancelFunc)
|
||||
}
|
||||
srv.listeners[l] = struct{}{}
|
||||
srv.listeners[l] = cancel
|
||||
return true
|
||||
}
|
||||
|
||||
@ -231,15 +234,19 @@ func (srv *Server) deleteListener(l *net.Listener) {
|
||||
// service goroutine for each. The service goroutines read requests and
|
||||
// then calls the appropriate Handler to reply to them.
|
||||
//
|
||||
// Serve always returns a non-nil error and closes l. After Shutdown or Close,
|
||||
// the returned error is ErrServerClosed.
|
||||
// Serve always returns a non-nil error and closes l.
|
||||
func (srv *Server) Serve(ctx context.Context, l net.Listener) error {
|
||||
defer l.Close()
|
||||
|
||||
if !srv.trackListener(&l) {
|
||||
return ErrServerClosed
|
||||
lnctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
if !srv.trackListener(&l, cancel) {
|
||||
// Cancel context
|
||||
cancel()
|
||||
return lnctx.Err()
|
||||
}
|
||||
defer srv.tryFinishShutdown()
|
||||
defer srv.tryCloseDone()
|
||||
defer srv.deleteListener(&l)
|
||||
|
||||
errch := make(chan error, 1)
|
||||
@ -248,12 +255,10 @@ func (srv *Server) Serve(ctx context.Context, l net.Listener) error {
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-lnctx.Done():
|
||||
return lnctx.Err()
|
||||
case err := <-errch:
|
||||
return err
|
||||
case <-srv.closed():
|
||||
return ErrServerClosed
|
||||
}
|
||||
}
|
||||
|
||||
@ -283,13 +288,17 @@ func (srv *Server) serve(ctx context.Context, l net.Listener) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (srv *Server) trackConn(conn *net.Conn) {
|
||||
func (srv *Server) trackConn(conn *net.Conn, cancel context.CancelFunc) bool {
|
||||
srv.mu.Lock()
|
||||
defer srv.mu.Unlock()
|
||||
if srv.conns == nil {
|
||||
srv.conns = make(map[*net.Conn]struct{})
|
||||
if srv.closed && !srv.shutdown {
|
||||
return false
|
||||
}
|
||||
srv.conns[conn] = struct{}{}
|
||||
if srv.conns == nil {
|
||||
srv.conns = make(map[*net.Conn]context.CancelFunc)
|
||||
}
|
||||
srv.conns[conn] = cancel
|
||||
return true
|
||||
}
|
||||
|
||||
func (srv *Server) deleteConn(conn *net.Conn) {
|
||||
@ -300,12 +309,19 @@ func (srv *Server) deleteConn(conn *net.Conn) {
|
||||
|
||||
// ServeConn serves a Gemini response over the provided connection.
|
||||
// It closes the connection when the response has been completed.
|
||||
// ServeConn can be used even after Shutdown or Close have been called.
|
||||
// Note that ServeConn will succeed even if a call to Shutdown is ongoing.
|
||||
func (srv *Server) ServeConn(ctx context.Context, conn net.Conn) error {
|
||||
defer conn.Close()
|
||||
|
||||
srv.trackConn(&conn)
|
||||
defer srv.tryFinishShutdown()
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
if !srv.trackConn(&conn, cancel) {
|
||||
// Cancel context
|
||||
cancel()
|
||||
return ctx.Err()
|
||||
}
|
||||
defer srv.tryCloseDone()
|
||||
defer srv.deleteConn(&conn)
|
||||
|
||||
if d := srv.ReadTimeout; d != 0 {
|
||||
|
Loading…
Reference in New Issue
Block a user