Graceful Shutdown Library for Go (G.S): Design, Usage, and Code Walkthrough
This article introduces the Go‑based G.S library for graceful application shutdown, analyzes common container‑related pitfalls, explains its lightweight architecture with TerminateSignal and WaitingUnit modules, provides installation steps, a complete usage example, and detailed code explanations to help developers implement reliable signal handling.
Graceful Shutdown Library for Go (G.S)
Author Lee, a veteran with 17 years in IT, observes that many projects nearing release suffer from poor container‑design practices, such as ignoring proper signal handling (SIGINT/SIGTERM/SIGQUIT) and lacking unified standards, which leads to unstable pod restarts and scaling.
Pain‑point Analysis
Insufficient awareness of container design principles Developers often overlook container design guidelines. Container issues are mistakenly treated as operations‑only problems.
Oversimplified coding attitude Some believe container functionality is trivial and avoid writing robust code. This leads to accepting any code that merely runs, regardless of quality.
Lack of unified standards affecting code reuse Without common standards, developers implement features independently. Resulting code is hard to reuse across projects.
The author extracted, refactored, and open‑sourced a solution to address these widespread issues.
Introduction
G.S – https://github.com/shengyanli1982/gs
G.S is a Go library that provides an easy way to achieve graceful shutdown of applications, supporting multiple signals, various shutdown methods, concurrent object termination, and customizable timeout handling. It has been used in production for over two years.
Architecture Design
The library consists of two modules:
TerminateSignal
WaitingUnit
Its core principles are "lightweight, easy‑to‑use, fast".
Module Overview
1. TerminateSignal
TerminateSignal is a struct that registers callbacks to be executed when a termination signal arrives. It supports a timeout mechanism and can accept an external context.Context to synchronize with other logic.
Key methods:
CancelCallbacksRegistry(...func()) – registers callbacks for cancellation.
GetStopCtx() – returns the context associated with the stop signal.
Close(wg *sync.WaitGroup) – executes all registered callbacks, sends the shutdown signal, and waits for completion.
2. WaitingUnit
WaitingUnit blocks until all termination signals have finished executing their callbacks. It listens for SIGINT, SIGTERM, and SIGQUIT, making it an event‑driven design.
A single WaitingUnit can control multiple TerminateSignal instances, enabling coordinated graceful shutdown of many resources.
Installation
go get github.com/shengyanli1982/gsUsage Example
The following example demonstrates how to use G.S with three simulated services, each exposing a different shutdown method.
package main
import (
"fmt"
"os"
"time"
"github.com/shengyanli1982/gs"
)
// simulate a service
type testTerminateSignal struct{}
func (t *testTerminateSignal) Close() { fmt.Println("testTerminateSignal.Close()") }
// simulate another service
type testTerminateSignal2 struct{}
func (t *testTerminateSignal2) Shutdown() { fmt.Println("testTerminateSignal2.Shutdown()") }
// simulate a third service
type testTerminateSignal3 struct{}
func (t *testTerminateSignal3) Terminate() { fmt.Println("testTerminateSignal3.Terminate()") }
func main() {
// Create TerminateSignal instance
s := gs.NewDefaultTerminateSignal()
// Resources to be closed on termination
t1 := &testTerminateSignal{}
t2 := &testTerminateSignal2{}
t3 := &testTerminateSignal3{}
// Register their shutdown methods
s.CancelCallbacksRegistry(t1.Close, t2.Shutdown, t3.Terminate)
// Send an interrupt signal after 2 seconds
go func() {
time.Sleep(2 * time.Second)
p, err := os.FindProcess(os.Getpid())
if err != nil { fmt.Println(err.Error()) }
err = p.Signal(os.Interrupt)
if err != nil { fmt.Println(err.Error()) }
}()
// Wait for graceful shutdown
gs.WaitingForGracefulShutdown(s)
fmt.Println("shutdown gracefully")
}Running the program produces:
# go run main.go
testTerminateSignal3.Terminate()
testTerminateSignal.Close()
testTerminateSignal2.Shutdown()
shutdown gracefullyCode Analysis
The project mainly consists of two files: terminal.go and gracefull.go . Key excerpts are shown below.
terminal.go – TerminateSignal implementation // Register callbacks to be canceled func (s *TerminateSignal) CancelCallbacksRegistry(callbacks ...func()) { s.exec = append(s.exec, callbacks...) } // Get the stop signal's Context func (s *TerminateSignal) GetStopCtx() context.Context { return s.ctx } // Close the TerminateSignal instance func (s *TerminateSignal) Close(wg *sync.WaitGroup) { s.once.Do(func() { for _, cb := range s.exec { if cb != nil { s.wg.Add(1) go s.worker(cb) } } s.cancel() s.wg.Wait() if wg != nil { wg.Done() } }) } func (s *TerminateSignal) worker(callback func()) { defer s.wg.Done() <-s.ctx.Done() callback() }
gracefull.go – WaitingUnit implementation // Wait for all shutdown signals func WaitingForGracefulShutdown(sigs ...*TerminateSignal) { quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) <-quit signal.Stop(quit) close(quit) if len(sigs) > 0 { var wg sync.WaitGroup wg.Add(len(sigs)) for _, s := range sigs { go s.Close(&wg) } wg.Wait() } }
The WaitingForGracefulShutdown function blocks until a termination signal is received, then triggers all registered callbacks via the associated TerminateSignal instances.
Conclusion
By designing and implementing the G.S library, the author provides a minimal‑cost solution for graceful shutdown that can be reused across multiple services without extensive code changes. The library demonstrates a practical approach to reliable signal handling in Go backend applications, and the author invites feedback and contributions.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.