Melkeydev / go-blueprint

Go-blueprint allows users to spin up a quick Go project using a popular framework
MIT License
2.07k stars 141 forks source link

[Feature Request] Implement graceful shutdown #205

Open Adembc opened 1 month ago

Adembc commented 1 month ago

Tell us about your feature request

I'd like to propose the implementation of graceful shutdown functionality in the project. This feature would ensure that the application gracefully handles shutdown signals, allowing it to complete ongoing tasks and close connections cleanly before exiting. Would be happy to work on implementing this if it is something that is wanted.

Disclaimer

H0llyW00dzZ commented 1 month ago

Tell us about your feature request

I'd like to propose the implementation of graceful shutdown functionality in the project. This feature would ensure that the application gracefully handles shutdown signals, allowing it to complete ongoing tasks and close connections cleanly before exiting. Would be happy to work on implementing this if it is something that is wanted.

Disclaimer

  • [x] I agree

That would be great because graceful shutdown is important when dealing with SQL. For example, with MySQL, if the connection is not closed properly, it might look like a deadlock (keep opening), and potentially many open connections could remain open.

briancbarrow commented 1 month ago

@Adembc I will assign you the issue. Thanks for opening the ticket.

H0llyW00dzZ commented 1 month ago

Yo Yo yo, Will this be implemented ? Because I have a better way to implement graceful shutdown, and it's only possible in Go, not in any other language.

This A better async method written in go

example:

// Start initiates the Fiber server on the specified address.
// It logs server startup and any errors encountered during Listen.
func (s *FiberServer) Start(addr string) {
    go func() {
        Logger.LogInfof("Starting server on %s", addr)
        if err := s.app.Listen(addr); err != nil {
            Logger.LogErrorf("Server listen failed: %v", err)
        }
    }()
}

// Shutdown attempts to gracefully shut down the Fiber server without interrupting any active connections.
func (s *FiberServer) Shutdown() error {
    return s.app.Shutdown()
}

// CleanupDB disconnects from the database if a connection exists.
// It logs the outcome of the operation.
func (s *FiberServer) CleanupDB() error {
    if s.db != nil {
        if err := s.db.Close(); err != nil {
            Logger.LogErrorf("Failed to close database connection: %v", err)
            return err
        }
        Logger.LogInfo("Database connection successfully closed.")
    }
    return nil
}

// StartServer sets up and starts the server while also preparing for a graceful shutdown.
// It accepts a server interface, address, path for monitoring, and a timeout for shutdown.
func StartServer(server Server, addr, monitorPath string, shutdownTimeout time.Duration) {
    startServerAsync(server, addr, monitorPath)
    waitForShutdownSignal(shutdownTimeout, server)
}

// startServerAsync launches the server in a separate goroutine.
// This allows the server to start listening for incoming requests without blocking the main thread.
func startServerAsync(server Server, addr, monitorPath string) {
    server.Start(addr, monitorPath)
}

// waitForShutdownSignal listens for OS interrupt or SIGTERM signals to initiate a graceful server shutdown.
// The server attempts to shut down within the specified timeout, logging the process and any errors.
func waitForShutdownSignal(shutdownTimeout time.Duration, server Server) {
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, os.Interrupt, syscall.SIGTERM)

    sig := <-quit
    Logger.LogInfof("Received shutdown signal: %v", sig)

    ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
    defer cancel()

    shutdownTimer := time.NewTimer(shutdownTimeout)
    defer shutdownTimer.Stop()

    go func() {
        if err := server.Shutdown(); err != nil {
            Logger.LogErrorf("Error during server shutdown: %v", err)
        }

        if err := server.CleanupDB(); err != nil {
            Logger.LogErrorf("Database cleanup failed: %v", err)
        }

        cancel()
    }()

    select {
    case <-ctx.Done():
        Logger.LogInfo("Server shutdown completed within timeout.")
    case <-shutdownTimer.C:
        Logger.LogWarning("Server shutdown exceeded the timeout.")
    }
}
H0llyW00dzZ commented 1 month ago

Another Method works well on fiber frameworks

// Shutdown gracefully shuts down the Fiber server using the provided context.
func (s *FiberServer) Shutdown(ctx context.Context) error {
    return s.app.ShutdownWithContext(ctx)
}

// waitForShutdownSignal listens for OS interrupt or SIGTERM signals to initiate a graceful server shutdown.
// The server attempts to shut down within the specified timeout, logging the process and any errors.
func waitForShutdownSignal(shutdownTimeout time.Duration, server Server) {
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, os.Interrupt, syscall.SIGTERM)

    sig := <-quit
    Logger.LogInfof("Received shutdown signal: %v", sig)

    ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
    defer cancel()

    go func() {
        if err := server.Shutdown(ctx); err != nil {
            Logger.LogErrorf("Error during server shutdown: %v", err)
        }

        if err := server.CleanupDB(); err != nil {
            Logger.LogErrorf("Database cleanup failed: %v", err)
        }

        cancel()
    }()

    select {
    case <-ctx.Done():
        Logger.LogInfo("Server shutdown completed within timeout.")
    case <-time.After(shutdownTimeout):
        Logger.LogWarning("Server shutdown exceeded the timeout.")
        cancel()
    }
}