Context and Signals
Understanding Context in Go
Context is a powerful mechanism for carrying deadlines, cancellation signals, and request-scoped values across API boundaries and between processes.
Context Hierarchy
graph TD
A[Root Context] --> B[Derived Context 1]
A --> C[Derived Context 2]
B --> D[Child Context]
C --> E[Child Context]
Types of Context
Context Type |
Description |
Use Case |
context.Background() |
Empty root context |
Initial parent context |
context.TODO() |
Placeholder context |
Temporary or undetermined context |
context.WithCancel() |
Cancellable context |
Manual cancellation |
context.WithTimeout() |
Context with deadline |
Time-limited operations |
context.WithDeadline() |
Context with specific time |
Precise time-based cancellation |
context.WithValue() |
Context with key-value |
Passing request-scoped data |
Handling OS Signals
func handleSignals(ctx context.Context) {
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan,
syscall.SIGINT, // Ctrl+C
syscall.SIGTERM, // Termination signal
)
go func() {
select {
case sig := <-sigChan:
fmt.Printf("Received signal: %v\n", sig)
cancel()
case <-ctx.Done():
return
}
}()
}
Complete Signal Handling Example
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Setup signal handling
handleSignals(ctx)
// Long-running task
go func() {
for {
select {
case <-ctx.Done():
fmt.Println("Task gracefully stopped")
return
default:
// Perform work
time.Sleep(time.Second)
}
}
}()
// Simulate long-running application
time.Sleep(5 * time.Minute)
}
Signal Handling Strategies
graph TD
A[Signal Handling] --> B[Graceful Shutdown]
A --> C[Cleanup Operations]
A --> D[Resource Release]
Best Practices
- Always propagate context through function calls
- Use
context.Background()
as the root context
- Cancel context as soon as work is done
- Set appropriate timeouts
- Handle signals consistently
Advanced Context Techniques
Passing Values Safely
type key string
func contextWithUserID(ctx context.Context, userID string) context.Context {
return context.WithValue(ctx, key("userID"), userID)
}
func getUserID(ctx context.Context) string {
if value := ctx.Value(key("userID")); value != nil {
return value.(string)
}
return ""
}
- Context has minimal overhead
- Use sparingly and only when necessary
- Avoid deep context hierarchies
- Release contexts promptly
Error Handling with Context
func processRequest(ctx context.Context) error {
select {
case <-ctx.Done():
return fmt.Errorf("request cancelled: %v", ctx.Err())
default:
// Process request
return nil
}
}
By mastering context and signal handling, developers can create robust, responsive applications that gracefully manage resources and handle system interruptions.