Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions gin.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package gin

import (
"context"
"fmt"
"html/template"
"net"
Expand Down Expand Up @@ -186,6 +187,11 @@ type Engine struct {
maxSections uint16
trustedProxies []string
trustedCIDRs []*net.IPNet

// server holds a reference to the HTTP server for graceful shutdown.
// This is set when one of the Run* methods is called.
server *http.Server
serverLock sync.Mutex
}

var _ IRouter = (*Engine)(nil)
Expand Down Expand Up @@ -534,6 +540,30 @@ func parseIP(ip string) net.IP {
return parsedIP
}

// Shutdown gracefully shuts down the server without interrupting any active connections.
// Shutdown works by first closing all open listeners, then closing all idle connections,
// and then waiting indefinitely for connections to return to idle and then shut down.
// If the provided context expires before the shutdown is complete, Shutdown returns the
// context's error, otherwise it returns any error returned from closing the Server's
// underlying Listener(s).
//
// When Shutdown is called, Serve, ListenAndServe, and ListenAndServeTLS immediately
// return ErrServerClosed. Make sure the program doesn't exit and waits instead for
// Shutdown to return.
//
// This method returns nil if the server has not been started.
func (engine *Engine) Shutdown(ctx context.Context) error {
engine.serverLock.Lock()
srv := engine.server
engine.serverLock.Unlock()

if srv == nil {
return nil
}

return srv.Shutdown(ctx)
}

// Run attaches the router to a http.Server and starts listening and serving HTTP requests.
// It is a shortcut for http.ListenAndServe(addr, router)
// Note: this method will block the calling goroutine indefinitely unless an error happens.
Expand All @@ -551,6 +581,11 @@ func (engine *Engine) Run(addr ...string) (err error) {
Addr: address,
Handler: engine.Handler(),
}

engine.serverLock.Lock()
engine.server = server
engine.serverLock.Unlock()

err = server.ListenAndServe()
return
}
Expand All @@ -571,6 +606,11 @@ func (engine *Engine) RunTLS(addr, certFile, keyFile string) (err error) {
Addr: addr,
Handler: engine.Handler(),
}

engine.serverLock.Lock()
engine.server = server
engine.serverLock.Unlock()

err = server.ListenAndServeTLS(certFile, keyFile)
return
}
Expand All @@ -597,6 +637,11 @@ func (engine *Engine) RunUnix(file string) (err error) {
server := &http.Server{ // #nosec G112
Handler: engine.Handler(),
}

engine.serverLock.Lock()
engine.server = server
engine.serverLock.Unlock()

err = server.Serve(listener)
return
}
Expand Down Expand Up @@ -654,6 +699,11 @@ func (engine *Engine) RunListener(listener net.Listener) (err error) {
server := &http.Server{ // #nosec G112
Handler: engine.Handler(),
}

engine.serverLock.Lock()
engine.server = server
engine.serverLock.Unlock()

err = server.Serve(listener)
return
}
Expand Down
69 changes: 69 additions & 0 deletions graceful.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.

package gin

import (
"context"
"errors"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)

// ShutdownConfig holds configuration for graceful shutdown.
type ShutdownConfig struct {
// Timeout is the maximum duration to wait for active connections to finish.
// Default: 10 seconds
Timeout time.Duration

// Signals are the OS signals that will trigger shutdown.
// Default: SIGINT, SIGTERM
Signals []os.Signal
}

// RunWithShutdown starts the HTTP server and handles graceful shutdown on SIGINT/SIGTERM.
// It blocks until the server is shut down.
// The timeout parameter specifies the maximum duration to wait for active connections to finish.
func (engine *Engine) RunWithShutdown(addr string, timeout time.Duration) error {
return engine.RunWithShutdownConfig(addr, ShutdownConfig{
Timeout: timeout,
Signals: []os.Signal{syscall.SIGINT, syscall.SIGTERM},
})
}

// RunWithShutdownConfig starts the HTTP server with custom shutdown configuration.
// It blocks until the server is shut down.
func (engine *Engine) RunWithShutdownConfig(addr string, config ShutdownConfig) error {
if config.Timeout == 0 {
config.Timeout = 10 * time.Second
}
if len(config.Signals) == 0 {
config.Signals = []os.Signal{syscall.SIGINT, syscall.SIGTERM}
}

ctx, stop := signal.NotifyContext(context.Background(), config.Signals...)
defer stop()

errCh := make(chan error, 1)
go func() {
if err := engine.Run(addr); err != nil && !errors.Is(err, http.ErrServerClosed) {
errCh <- err
}
close(errCh)
}()

select {
case err := <-errCh:
return err
case <-ctx.Done():
}

shutdownCtx, cancel := context.WithTimeout(context.Background(), config.Timeout)
defer cancel()

return engine.Shutdown(shutdownCtx)
}
Loading