A simple Go package for handling system signals and user input in a clean, channel-based way.
The signals package provides easy-to-use functions that return channels which close when specific signals are received. This allows for clean signal handling in Go applications using select statements.
go get github.com/nyxstack/signals
Returns a channel that closes when SIGINT (Ctrl+C) is received.
Returns a channel that closes when SIGTERM is received (commonly used by Kubernetes and process managers).
Returns a channel that closes when SIGHUP is received (terminal hangup, reload, or session end).
Returns a channel that closes when SIGUSR1 is received (user-defined signal 1).
Returns a channel that closes when SIGUSR2 is received (user-defined signal 2).
Returns a channel that closes when the user types 'q' followed by Enter on stdin.
Returns a channel that closes when the user presses Enter.
Returns a channel that closes on any key press.
Returns a channel that closes on SIGINT, SIGTERM, or user 'q'.
Returns a channel that closes on SIGTERM or SIGHUP.
package main import ( "fmt" "time" "github.com/nyxstack/signals" ) func main() { fmt.Println("Application started. Press Ctrl+C to exit.") // Wait for interrupt signal <-signals.Interrupt() fmt.Println("Received interrupt signal, shutting down...") }
package main import ( "fmt" "time" "github.com/nyxstack/signals" ) func main() { fmt.Println("Server starting...") // Simulate some work go func() { for { fmt.Println("Working...") time.Sleep(2 * time.Second) } }() // Wait for any shutdown signal select { case <-signals.Interrupt(): fmt.Println("Received SIGINT (Ctrl+C)") case <-signals.Terminate(): fmt.Println("Received SIGTERM") case <-signals.Hangup(): fmt.Println("Received SIGHUP") case <-signals.Quit(): fmt.Println("User typed 'q'") } fmt.Println("Gracefully shutting down...") }
package main import ( "context" "fmt" "time" "github.com/nyxstack/signals" ) func main() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() // Start background work go worker(ctx) // Wait for shutdown signal select { case <-signals.Interrupt(): case <-signals.Terminate(): } fmt.Println("Shutdown signal received, stopping...") cancel() // Give time for graceful shutdown time.Sleep(1 * time.Second) fmt.Println("Application stopped") } func worker(ctx context.Context) { ticker := time.NewTicker(1 * time.Second) defer ticker.Stop() for { select { case <-ctx.Done(): fmt.Println("Worker stopping...") return case <-ticker.C: fmt.Println("Working...") } } }
- Web Servers: Graceful shutdown on SIGTERM/SIGINT
- CLI Tools: Clean exit on Ctrl+C or user input
- Kubernetes Applications: Proper handling of pod termination signals
- Daemon Processes: Configuration reload on SIGHUP
- Interactive Applications: User-controlled exit with 'q' key
Each signal function spawns a goroutine to monitor for signals or input. Understanding the lifecycle is important:
- Goroutine runs until the signal is received
- After signal receipt, the goroutine cleans up automatically using
defer signal.Stop() - These are lightweight and designed for one-time use per call
- Goroutines block on
stdinreads until input is received orstdincloses - Designed for typical CLI usage where the program exits after receiving input
- For long-running applications with multiple input reads, call the function each time rather than reusing the channel
- Spawn multiple signal monitoring goroutines internally
- When any monitored signal fires, the returned channel closes
- Unused signal goroutines clean up when their channels are garbage collected
- One-time use: These functions are designed for blocking until a signal/input is received, then the program typically exits
- Don't abandon channels: If you stop listening to a returned channel, call the function again when needed rather than keeping old channels around
- Long-running apps: For applications that need repeated signal handling, each new operation should call the function again
- Testing: In tests, close stdin or send signals to ensure goroutines terminate properly
- Go 1.24.2 or later
Contributions are welcome! Please feel free to submit issues and pull requests. When contributing:
- Follow Go conventions and best practices
- Include tests for new signal handlers
- Update documentation for any new functions
- Ensure backward compatibility
MIT License - see LICENSE file for details.