Hone logo
Hone
Problems

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:

  1. Stop accepting new connections: Prevent any new incoming HTTP requests from being processed.
  2. Allow existing requests to complete: Let any requests that are currently being handled finish their execution.
  3. Perform cleanup: Execute any necessary cleanup tasks (e.g., closing database connections, releasing locks) before the server process exits.
  4. Exit cleanly: Terminate the Go program without panics or unhandled errors.

You will need to:

  • Create a basic HTTP server using Go's net/http package.
  • 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 /health endpoint should be available to check server status.
  • A /slow endpoint 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 /health and /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 /slow are 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 localhost and port 8080.
  • 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.Context to manage timeouts and cancellations within your request handlers and for the shutdown process itself.
  • A sync.WaitGroup can 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/signal package 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 pressing Ctrl+C in the terminal where the server is running.
Loading editor...
go