Graceful Drain
When a Nexphant worker process receives a shutdown signal, it must shut down cleanly without dropping in-flight HTTP requests, active queue jobs, or terminating database operations abruptly. This cooperative shutdown protocol is managed by the DrainController.
The Drain Controller API
The DrainController tracks the state of active execution units and manages the shutdown sequence.
You can retrieve the singleton instance via:
use Nexphant\Runtime\Runtime;
$drain = Runtime::drain(); // or DrainController::instance()State Transitions
The lifecycle of a worker during shutdown progresses through the following states:
accepting: The worker is fully healthy and accepting new requests.draining: The worker has stopped accepting new connections/requests and is waiting for current, in-flight units to complete.forced/stopped: The timeout has expired or all in-flight units have completed. Outstanding cancellation tokens are fired, and the process exits.
The Shutdown Sequence
[Signal (SIGTERM/SIGINT)]
│
▼
1. stopAccepting() ──► State becomes 'draining'
│
▼
2. waitInFlight() ──► Yield & check loop for remaining tasks
│
├─── (completed before timeout) ──► 3a. Exit safely
│
└─── (timeout exceeded) ──────────► 3b. forceStop() ──► Cancel outstanding tokens ──► Exit1. Stop Accepting Work
Invoke stopAccepting() to transition the state to draining. HTTP servers and queue consumers will immediately reject new work:
Runtime::drain()->stopAccepting();2. Track In-Flight Units
Every request, job, or fiber registers its execution under the drain controller upon start, and removes it upon completion:
use Nexphant\Runtime\Runtime;
$ownerId = Runtime::owner();
// Register the task as in-flight
Runtime::drain()->trackInFlight($ownerId);
try {
// Perform processing...
} finally {
// Mark the task as finished
Runtime::drain()->finishInFlight($ownerId);
}3. Await Completion
The supervisor calls waitInFlight($timeoutSeconds) to block until all tracked in-flight tasks have completed:
$completedSafely = Runtime::drain()->waitInFlight(timeout: 15.0);
if (!$completedSafely) {
// Timeout exceeded; force cancellation of all outstanding tasks
Runtime::drain()->forceStop('drain_timeout');
}Signal Handling Implementation
This is how signals like SIGTERM are handled inside the persistent worker process loop:
use Nexphant\Runtime\Runtime;
// Register PCNTL signal handlers
if (extension_loaded('pcntl')) {
pcntl_async_signals(true);
pcntl_signal(SIGTERM, function () {
// Start graceful drain in a separate fiber/coroutine
Runtime::spawn(function () {
$drain = Runtime::drain();
$drain->stopAccepting();
// Wait up to 10 seconds for existing tasks
$success = $drain->waitInFlight(10.0);
if (!$success) {
// Force stop remaining tasks
$drain->forceStop('sigterm_timeout');
}
// Stop the event loop and exit process
Runtime::stop();
});
});
}