Graceful Shutdown for a Go Web Server
This challenge focuses on implementing a graceful shutdown mechanism for a Go web server. In real-world applications, it's crucial to ensure that when a server receives a shutdown signal (like SIGINT from Ctrl+C), it doesn't abruptly terminate. Instead, it should finish processing any in-flight requests and clean up resources before exiting. This prevents data loss and provides a better user experience.
Problem Description
Your task is to build a simple Go web server and implement a graceful shutdown mechanism. When the server receives a termination signal, it should:
- Stop accepting new connections: Prevent any new incoming HTTP requests from being processed.
- Allow existing requests to complete: Let any requests that are currently being handled finish their execution.
- Perform cleanup: Execute any necessary cleanup tasks (e.g., closing database connections, releasing locks) before the server process exits.
- Exit cleanly: Terminate the Go program without panics or unhandled errors.
You will need to:
- Create a basic HTTP server using Go's
net/httppackage. - Set up a signal handler to listen for common termination signals (e.g.,
SIGINT,SIGTERM). - Implement logic to coordinate the shutdown process across different goroutines and resources.
Key Requirements
- The server should respond to HTTP requests on a specific port (e.g., 8080).
- A
/healthendpoint should be available to check server status. - A
/slowendpoint that simulates a long-running request is highly recommended for testing. - When a termination signal is received, the server must not accept new requests.
- The server should wait for a reasonable duration for ongoing requests to finish.
- A mechanism to signal completion of shutdown to the main goroutine is required.
Expected Behavior
- Normal Operation: The server runs, responds to requests on
/healthand/slow. - Shutdown Triggered: Upon receiving a SIGINT or SIGTERM signal:
- The server logs a message indicating shutdown has started.
- New requests to any endpoint are immediately rejected (e.g., with an HTTP 503 Service Unavailable).
- Requests already in progress on
/sloware allowed to complete. - After a configurable timeout (or when all requests are done), the server logs a shutdown complete message and exits.
Edge Cases to Consider
- What happens if a request takes longer than the grace period?
- How do you handle multiple shutdown signals?
- Ensuring that cleanup tasks themselves don't block indefinitely.
Examples
Example 1: Normal Operation
Input:
[Client sends GET request to http://localhost:8080/health]
Output:
[Server responds with HTTP 200 OK and body "OK"]
Example 2: Simulating a Slow Request
Input:
[Client sends GET request to http://localhost:8080/slow]
Explanation:
The server starts processing the request. This request might take a few seconds to complete.
Example 3: Graceful Shutdown During Slow Request
Input:
1. Client sends GET request to http://localhost:8080/slow.
2. While the /slow request is processing, the server receives a SIGINT signal.
Expected Server Logs:
... (server startup logs) ...
INFO: Starting graceful shutdown...
INFO: Server stopped accepting new connections.
INFO: Waiting for existing requests to complete...
... (request to /slow completes) ...
INFO: All existing requests completed.
INFO: Performing cleanup tasks...
INFO: Cleanup complete.
INFO: Server shut down gracefully.
Expected Client Response for New Request (sent after SIGINT):
[Server responds with HTTP 503 Service Unavailable]
Expected Client Response for /slow request (if it finishes before timeout):
[Server responds with HTTP 200 OK and body "Request completed"]
Constraints
- The server must listen on
localhostand port8080. - The grace period for shutting down should be configurable, with a reasonable default (e.g., 10 seconds).
- Your solution should use Go's standard library for HTTP serving and signal handling.
- Avoid external dependencies for the core graceful shutdown logic.
Notes
- Consider using
context.Contextto manage timeouts and cancellations within your request handlers and for the shutdown process itself. - A
sync.WaitGroupcan be very useful for tracking active requests. - Think about how to unregister your HTTP handlers or disable listening for new connections once the shutdown signal is received.
- The
os/signalpackage is your primary tool for intercepting OS signals. - For testing, you can send signals to your Go process using commands like
kill -INT <pid>or by pressingCtrl+Cin the terminal where the server is running.