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/signalsReturns 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.