Close Menu

    Subscribe to Updates

    Get the latest creative news from FooBar about art, design and business.

    What's Hot

    Resident Evil Requiem DLC and Resident Evil 10 release dates may be sooner than expected

    Poco Pad X1: Destroys the iPad

    Epic Games Store follows award winners with quieter free games lineup for late February 2026

    Facebook X (Twitter) Instagram
    • Artificial Intelligence
    • Business Technology
    • Cryptocurrency
    • Gadgets
    • Gaming
    • Health
    • Software and Apps
    • Technology
    Facebook X (Twitter) Instagram Pinterest Vimeo
    Tech AI Verse
    • Home
    • Artificial Intelligence

      Read the extended transcript: President Donald Trump interviewed by ‘NBC Nightly News’ anchor Tom Llamas

      February 6, 2026

      Stocks and bitcoin sink as investors dump software company shares

      February 4, 2026

      AI, crypto and Trump super PACs stash millions to spend on the midterms

      February 2, 2026

      To avoid accusations of AI cheating, college students are turning to AI

      January 29, 2026

      ChatGPT can embrace authoritarian ideas after just one prompt, researchers say

      January 24, 2026
    • Business

      The HDD brand that brought you the 1.8-inch, 2.5-inch, and 3.5-inch hard drives is now back with a $19 pocket-sized personal cloud for your smartphones

      February 12, 2026

      New VoidLink malware framework targets Linux cloud servers

      January 14, 2026

      Nvidia Rubin’s rack-scale encryption signals a turning point for enterprise AI security

      January 13, 2026

      How KPMG is redefining the future of SAP consulting on a global scale

      January 10, 2026

      Top 10 cloud computing stories of 2025

      December 22, 2025
    • Crypto

      US Investors Might Be Leaving Bitcoin and Ethereum ETFs for International Markets

      February 14, 2026

      Binance France President Targeted in Armed Kidnapping Attempt

      February 14, 2026

      Binance Fires Investigators as $1 Billion Iran-Linked USDT Flows Surface

      February 14, 2026

      Aave Proposes 100% DAO Revenue Model, Yet Price Remains Under Pressure

      February 14, 2026

      A $3 Billion Credit Giant Is Testing Bitcoin in the Mortgage System — Here’s How

      February 14, 2026
    • Technology

      Resident Evil Requiem DLC and Resident Evil 10 release dates may be sooner than expected

      February 14, 2026

      Poco Pad X1: Destroys the iPad

      February 14, 2026

      Epic Games Store follows award winners with quieter free games lineup for late February 2026

      February 14, 2026

      OnePlus releases new February 2026 OxygenOS update with improved AI Eraser, new video editing tools, updated AI Writer, and more

      February 14, 2026

      Sony relaunches WH-1000XM6 over-ear wireless headphones with new version

      February 14, 2026
    • Others
      • Gadgets
      • Gaming
      • Health
      • Software and Apps
    Check BMI
    Tech AI Verse
    You are at:Home»Technology»Graceful Shutdown in Go: Practical Patterns
    Technology

    Graceful Shutdown in Go: Practical Patterns

    TechAiVerseBy TechAiVerseMay 5, 2025No Comments3 Mins Read4 Views
    Facebook Twitter Pinterest Telegram LinkedIn Tumblr Email Reddit
    Graceful Shutdown in Go: Practical Patterns
    Share
    Facebook Twitter LinkedIn Pinterest WhatsApp Email

    Graceful Shutdown in Go: Practical Patterns

    Graceful Shutdown in Go: Practical Patterns

    Graceful shutdown in any application generally satisfies three minimum conditions:

    1. Close the entry point by stopping new requests or messages from sources like HTTP, pub/sub systems, etc. However, keep outgoing connections to third-party services like databases or caches active.
    2. Wait for all ongoing requests to finish. If a request takes too long, respond with a graceful error.
    3. Release critical resources such as database connections, file locks, or network listeners. Do any final cleanup.

    This article focuses on HTTP servers and containerized applications, but the core ideas apply to all types of applications.

    1. Catching the Signal

    #

    Before we handle graceful shutdown, we first need to catch termination signals. These signals tell our application it’s time to exit and begin the shutdown process.

    So, what are signals?

    In Unix-like systems, signals are software interrupts. They notify a process that something has happened and it should take action. When a signal is sent, the operating system interrupts the normal flow of the process to deliver the notification.

    Here are a few possible behaviors:

    • Signal handler: A process can register a handler (a function) for a specific signal. This function runs when that signal is received.
    • Default action: If no handler is registered, the process follows the default behavior for that signal. This might mean terminating, stopping, continuing, or ignoring the process.
    • Unblockable signals: Some signals, like SIGKILL (signal number 9), cannot be caught or ignored. They may terminate the process.

    When your Go application starts, even before your main function runs, the Go runtime automatically registers signal handlers for many signals (SIGTERM, SIGQUIT, SIGILL, SIGTRAP, and others). However, for graceful shutdown, only three termination signals are typically important:

    • SIGTERM (Termination): A standard and polite way to ask a process to terminate. It does not force the process to stop. Kubernetes sends this signal when it wants your application to exit before it forcibly kills it.
    • SIGINT (Interrupt): Sent when the user wants to stop a process from the terminal, usually by pressing Ctrl+C.
    • SIGHUP (Hang up): Originally used when a terminal disconnected. Now, it is often repurposed to signal an application to reload its configuration.

    People mostly care about SIGTERM and SIGINT. SIGHUP is less used today for shutdown and more for reloading configs. You can find more about this in SIGHUP Signal for Configuration Reloads.

    By default, when your application receives a SIGTERM, SIGINT, or SIGHUP, the Go runtime will terminate the application.

    When your Go app gets a SIGTERM, the runtime first catches it using a built-in handler. It checks if a custom handler is registered. If not, the runtime disables its own handler temporarily, and sends the same signal (SIGTERM) to the application again. This time, the OS handles it using the default behavior, which is to terminate the process.

    You can override this by registering your own signal handler using the os/signal package.

    func main() {
      signalChan := make(chan os.Signal, 1)
      signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
    
      // Setup work here
    
      <-signalChan
    
      fmt.Println("Received termination signal, shutting down...")
    }
    
    Graceful shutdown begins with signal setup

    signal.Notify tells the Go runtime to deliver specified signals to a channel instead of using the default behavior. This allows you to handle them manually and prevents the application from terminating automatically.

    A buffered channel with a capacity of 1 is a good choice for reliable signal handling. Internally, Go sends signals to this channel using a select statement with a default case:

    select {
    case c <- sig:
    default:
    }
    

    This is different from the usual select used with receiving channels. When used for sending:

    • If the buffer has space, the signal is sent and the code continues.
    • If the buffer is full, the signal is discarded, and the default case runs. If you’re using an unbuffered channel and no goroutine is actively receiving, the signal will be missed.

    Even though it can only hold one signal, this buffered channel helps avoid missing that first signal while your app is still initializing and not yet listening.

    You can call Notify multiple times for the same signal. Go will send that signal to all registered channels.

    When you press Ctrl+C more than once, it doesn’t automatically kill the app. The first Ctrl+C sends a SIGINT to the foreground process. Pressing it again usually sends another SIGINT, not SIGKILL. Most terminals, like bash or other Linux shells, do not escalate the signal automatically. If you want to force a stop, you must send SIGKILL manually using kill -9.

    This is not ideal for local development, where you may want the second Ctrl+C to terminate the app forcefully. You can stop the app from listening to further signals by using signal.Stop right after the first signal is received:

    func main() {
      signalChan := make(chan os.Signal, 1)
      signal.Notify(signalChan, syscall.SIGINT)
    
      <-signalChan
    
      signal.Stop(signalChan)
      select {}
    }
    

    Starting with Go 1.16, you can simplify signal handling by using signal.NotifyContext, which ties signal handling to context cancellation:

    ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
    defer stop()
    
    // Setup tasks here
    
    <-ctx.Done()
    stop()
    

    You should still call stop() after ctx.Done() to allow a second Ctrl+C to forcefully terminate the application.

    2. Timeout Awareness

    #

    It is important to know how long your application has to shut down after receiving a termination signal. For example, in Kubernetes, the default grace period is 30 seconds, unless otherwise specified using the terminationGracePeriodSeconds field. After this period, Kubernetes sends a SIGKILL to forcefully stop the application. This signal cannot be caught or handled.

    Your shutdown logic must complete within this time, including processing any remaining requests and releasing resources.

    Assume the default is 30 seconds. It is a good practice to reserve about 20 percent of the time as a safety margin to avoid being killed before cleanup finishes. This means aiming to finish everything within 25 seconds to avoid data loss or inconsistency.

    3. Stop Accepting New Requests

    #

    When using net/http, you can handle graceful shutdown by calling the http.Server.Shutdown method. This method stops the server from accepting new connections and waits for all active requests to complete before shutting down idle connections.

    Here is how it behaves:

    • If a request is already in progress on an existing connection, the server will allow it to complete. After that, the connection is marked as idle and is closed.
    • If a client tries to make a new connection during shutdown, it will fail because the server’s listeners are already closed. This typically results in a “connection refused” error.

    In a containerized environment (and many other orchestrated environments with load balancers), do not stop accepting new requests immediately. Even after a pod is marked for termination, it might still receive traffic for a few moments because the system needs time to update the service and load balancer.

    This is especially true when using a readiness probe in Kubernetes, as a terminating pod can still receive traffic if no other endpoints are ready.

    A readiness probe determines when a container is prepared to accept traffic by periodically checking its health through configured methods like HTTP requests, TCP connections, or command executions. If the probe fails, Kubernetes removes the pod from the service’s endpoints, preventing it from receiving traffic until it becomes ready again.

    To avoid connection errors during this short window, the correct strategy is to fail the readiness probe first. This tells the orchestrator that your pod should no longer receive traffic:

    var isShuttingDown atomic.Bool
    
    func readinessHandler(w http.ResponseWriter, r *http.Request) {
        if isShuttingDown.Load() {
            w.WriteHeader(http.StatusServiceUnavailable)
            w.Write([]byte("shutting down"))
            return
        }
    
        w.WriteHeader(http.StatusOK)
        w.Write([]byte("ok"))
    }
    
    Delay shutdown by failing readiness first

    This pattern is also used as a code example in the test images. In their implementation, a closed channel is used to signal the readiness probe to return HTTP 503 when the application is preparing to shut down.

    After updating the readiness probe to indicate the pod is no longer ready, wait a few seconds to give the system time to stop sending new requests.

    The exact wait time depends on your readiness probe configuration; we will use 5 seconds for this article with the following simple configuration:

    readinessProbe:
      httpGet:
        path: /healthz
        port: 8080
      periodSeconds: 5
    

    This guide only gives you the idea behind graceful shutdown. Planning your graceful shutdown strategy depends on your application’s characteristics.

    “Isn’t it better to still use terminating pod as a fallback if there are no other pods?”

    There are 2 situations to consider:

    • During normal operation, when a pod is marked for termination, there’s typically another pod that’s already running and handling traffic.
    • During rolling updates, Kubernetes creates a new pod first and waits until it’s ready before sending SIGTERM to the pod being replaced.

    However, if the other pod suddenly breaks while a pod is terminating, the terminating pod will still receive traffic as a fallback mechanism. This raises a question: should we avoid failing the readiness probe during termination to ensure this fallback works?

    The answer is most likely no. If we don’t fail the readiness probe, we might face worse consequences if the terminating pod is abruptly killed with SIGKILL. This could lead to corrupted processes or data and cause more serious issues.

    4. Handle Pending Requests

    #

    Now that we are shutting down the server gracefully, we need to choose a timeout based on your shutdown budget:

    ctx, cancelFn := context.WithTimeout(context.Background(), timeout)
    err := server.Shutdown(ctx)
    

    The server.Shutdown function returns in only two situations:

    1. All active connections are closed and all handlers have finished processing.
    2. The context passed to Shutdown(ctx) expires before the handlers finish. In this case, the server gives up waiting and forcefully closes all remaining connections.

    In either case, Shutdown only returns after the server has completely stopped handling requests. This is why your handlers must be fast and context-aware. Otherwise, they may be cut off mid-process in case 2, which can cause issues like partial writes, data loss, inconsistent state, open transactions, or corrupted data.

    A common issue is that handlers are not automatically aware when the server is shutting down.

    So, how can we notify our handlers that the server is shutting down? The answer is by using context. There are two main ways to do this:

    a. Use context middleware to inject cancellation logic

    #

    This middleware wraps each request with a context that listens to a shutdown signal:

    func WithGracefulShutdown(next http.Handler, cancelCh <-chan struct{}) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            ctx, cancel := WithCancellation(r.Context(), cancelCh)
            defer cancel()
    
            r = r.WithContext(ctx)
            next.ServeHTTP(w, r)
        })
    }
    

    b. Use BaseContext to provide a global context to all connections

    #

    Here, we create a server with a custom BaseContext that can be canceled during shutdown. This context is shared across all incoming requests.

    ongoingCtx, cancelFn := context.WithCancel(context.Background())
    server := &http.Server{
        Addr: ":8080",
        Handler: yourHandler,
        BaseContext: func(l net.Listener) context.Context {
            return ongoingCtx
        },
    }
    
    // After attempting graceful shutdown:
    cancelFn()
    time.Sleep(5 * time.Second) // optional delay to allow context propagation
    

    In an HTTP server, you can customize two types of contexts: BaseContext and ConnContext. For graceful shutdown, BaseContext is more suitable. It allows you to create a global context with cancellation that applies to the entire server, and you can cancel it to signal all active requests that the server is shutting down.

    Full graceful shutdown with propagation delay

    All of this work around graceful shutdown won’t help if your functions do not respect context cancellation. Try to avoid using context.Background(), time.Sleep(), or any other function that ignores context.

    For example, time.Sleep(duration) can be replaced with a context-aware version like this:

    func Sleep(ctx context.Context, duration time.Duration) error {
        select {
        case <-time.After(duration):
            return nil
        case <-ctx.Done():
            return ctx.Err()
        }
    }
    

    In older versions of Go, time.After can leak memory until the timer fires. This was fixed in Go 1.23 and newer. If you’re unsure which version you’re using, consider using time.NewTimer along with Stop and an optional <-t.C check if Stop returns false.

    See: time: stop requiring Timer/Ticker.Stop for prompt GC

    Although this article focuses on HTTP servers, the same concept applies to third-party services as well. For example, the database/sql package has a DB.Close method. It closes the database connection and prevents new queries from starting. It also waits for any ongoing queries to finish before fully shutting down.

    The core principle of graceful shutdown is the same across all systems: Stop accepting new requests or messages, and give existing operations time to finish within a defined grace period.

    Some may wonder about the server.Close() method, which shuts down the ongoing connections immediately without waiting for requests to finish. Can it be used after server.Shutdown() returns an error?

    The short answer is yes, but it depends on your shutdown strategy. The Close method forcefully closes all active listeners and connections:

    • Handlers that are actively using the network will receive errors when they try to read or write.
    • The client will immediately receive a connection error, such as ECONNRESET (‘socket hang up’)
    • However, long-running handlers that are not interacting with the network may continue running in the background.

    This is why using context to propagate a shutdown signal is still the more reliable and graceful approach.

    5. Release Critical Resources

    #

    A common mistake is releasing critical resources as soon as the termination signal is received. At that point, your handlers and in-flight requests may still be using those resources. You should delay the resource cleanup until the shutdown timeout has passed or all requests are done.

    In many cases, simply letting the process exit is enough. The operating system will automatically reclaim resources. For instance:

    • Memory allocated by Go is automatically freed when the process terminates.
    • File descriptors are closed by the OS.
    • OS-level resources like process handles are reclaimed.

    However, there are important cases where explicit cleanup is still necessary during shutdown:

    • Database connections should be closed properly. If any transactions are still open, they need to be committed or rolled back. Without a proper shutdown, the database has to rely on connection timeouts.
    • Message queues and brokers often require a clean shutdown. This may involve flushing messages, committing offsets, or signaling to the broker that the client is exiting. Without this, there can be rebalancing issues or message loss.
    • External services may not detect the disconnect immediately. Closing connections manually allows those systems to clean up faster than waiting for TCP timeouts.

    A good rule is to shut down components in the reverse order of how they were initialized. This respects dependencies between components.

    Go’s defer statement makes this easier since the last deferred function is executed first:

    db := connectDB()
    defer db.Close()
    
    cache := connectCache()
    defer cache.Close()
    

    Some components require special handling. For example, if you cache data in memory, you might need to write that data to disk before exiting. In those cases, design a shutdown routine specific to that component to handle the cleanup properly.

    Summary

    #

    This is a complete example of a graceful shutdown mechanism. It is written in a flat, straightforward structure to make it easier to understand. You can customize it to fit your own application as needed.

    const (
    	_shutdownPeriod      = 15 * time.Second
    	_shutdownHardPeriod = 3 * time.Second
    	_readinessDrainDelay = 5 * time.Second
    )
    
    var isShuttingDown atomic.Bool
    
    func main() {
    	// Setup signal context
    	rootCtx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
    	defer stop()
    
    	// Readiness endpoint
    	http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
    		if isShuttingDown.Load() {
    			http.Error(w, "Shutting down", http.StatusServiceUnavailable)
    			return
    		}
    		fmt.Fprintln(w, "OK")
    	})
    
    	// Sample business logic
    	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    		select {
    		case <-time.After(2 * time.Second):
    			fmt.Fprintln(w, "Hello, world!")
    		case <-r.Context().Done():
    			http.Error(w, "Request cancelled.", http.StatusRequestTimeout)
    		}
    	})
    
    	// Ensure in-flight requests aren't cancelled immediately on SIGTERM
    	ongoingCtx, stopOngoingGracefully := context.WithCancel(context.Background())
    	server := &http.Server{
    		Addr: ":8080",
    		BaseContext: func(_ net.Listener) context.Context {
    			return ongoingCtx
    		},
    	}
    
    	go func() {
    		log.Println("Server starting on :8080.")
    		if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
    			log.Fatalf("ListenAndServe: %v", err)
    		}
    	}()
    
    	// Wait for signal
    	<-rootCtx.Done()
    	stop()
    	isShuttingDown.Store(true)
    	log.Println("Received shutdown signal, shutting down.")
    
    	// Give time for readiness check to propagate
    	time.Sleep(_readinessDrainDelay)
    	log.Println("Readiness check propagated, now waiting for ongoing requests to finish.")
    
    	shutdownCtx, cancel := context.WithTimeout(context.Background(), _shutdownPeriod)
    	defer cancel()
    	err := server.Shutdown(shutdownCtx)
    	stopOngoingGracefully()
    	if err != nil {
    		log.Println("Failed to wait for ongoing requests to finish, waiting for forced cancellation.")
    		time.Sleep(_shutdownHardPeriod)
    	}
    
    	log.Println("Server shut down gracefully.")
    }
    

    Who We Are

    #

    If you want to monitor your services, track metrics, and see how everything performs, you might want to check out VictoriaMetrics. It’s a fast, open-source, and cost-saving way to keep an eye on your infrastructure.

    And we’re Gophers, enthusiasts who love researching, experimenting, and sharing knowledge about Go and its ecosystem. If you spot anything that’s outdated or if you have questions, don’t hesitate to reach out. You can drop me a DM on X(@func25).

    Related articles:

    • Golang Series at VictoriaMetrics
    • How Go Arrays Work and Get Tricky with For-Range
    • Slices in Go: Grow Big or Go Home
    • Go Maps Explained: How Key-Value Pairs Are Actually Stored
    • Golang Defer: From Basic To Traps
    • Vendoring, or go mod vendor: What is it?
    Share. Facebook Twitter Pinterest LinkedIn Reddit WhatsApp Telegram Email
    Previous ArticleToday’s NYT Connections: Sports Edition Hints and Answers for May 5, #224
    Next Article Helmdar: 3D Scanning Brooklyn on Rollerblades
    TechAiVerse
    • Website

    Jonathan is a tech enthusiast and the mind behind Tech AI Verse. With a passion for artificial intelligence, consumer tech, and emerging innovations, he deliver clear, insightful content to keep readers informed. From cutting-edge gadgets to AI advancements and cryptocurrency trends, Jonathan breaks down complex topics to make technology accessible to all.

    Related Posts

    Resident Evil Requiem DLC and Resident Evil 10 release dates may be sooner than expected

    February 14, 2026

    Poco Pad X1: Destroys the iPad

    February 14, 2026

    Epic Games Store follows award winners with quieter free games lineup for late February 2026

    February 14, 2026
    Leave A Reply Cancel Reply

    Top Posts

    Ping, You’ve Got Whale: AI detection system alerts ships of whales in their path

    April 22, 2025673 Views

    Lumo vs. Duck AI: Which AI is Better for Your Privacy?

    July 31, 2025260 Views

    6.7 Cummins Lifter Failure: What Years Are Affected (And Possible Fixes)

    April 14, 2025153 Views

    6 Best MagSafe Phone Grips (2025), Tested and Reviewed

    April 6, 2025112 Views
    Don't Miss
    Technology February 14, 2026

    Resident Evil Requiem DLC and Resident Evil 10 release dates may be sooner than expected

    Resident Evil Requiem DLC and Resident Evil 10 release dates may be sooner than expected…

    Poco Pad X1: Destroys the iPad

    Epic Games Store follows award winners with quieter free games lineup for late February 2026

    OnePlus releases new February 2026 OxygenOS update with improved AI Eraser, new video editing tools, updated AI Writer, and more

    Stay In Touch
    • Facebook
    • Twitter
    • Pinterest
    • Instagram
    • YouTube
    • Vimeo

    Subscribe to Updates

    Get the latest creative news from SmartMag about art & design.

    About Us
    About Us

    Welcome to Tech AI Verse, your go-to destination for everything technology! We bring you the latest news, trends, and insights from the ever-evolving world of tech. Our coverage spans across global technology industry updates, artificial intelligence advancements, machine learning ethics, and automation innovations. Stay connected with us as we explore the limitless possibilities of technology!

    Facebook X (Twitter) Pinterest YouTube WhatsApp
    Our Picks

    Resident Evil Requiem DLC and Resident Evil 10 release dates may be sooner than expected

    February 14, 20263 Views

    Poco Pad X1: Destroys the iPad

    February 14, 20261 Views

    Epic Games Store follows award winners with quieter free games lineup for late February 2026

    February 14, 20263 Views
    Most Popular

    7 Best Kids Bikes (2025): Mountain, Balance, Pedal, Coaster

    March 13, 20250 Views

    VTOMAN FlashSpeed 1500: Plenty Of Power For All Your Gear

    March 13, 20250 Views

    This new Roomba finally solves the big problem I have with robot vacuums

    March 13, 20250 Views
    © 2026 TechAiVerse. Designed by Divya Tech.
    • Home
    • About Us
    • Contact Us
    • Privacy Policy
    • Terms & Conditions

    Type above and press Enter to search. Press Esc to cancel.