Graceful shutdown
A graceful stop prevents new polls, lets the active synchronous task settle, and preserves task completion or failure reporting before the process exits.
Use the managed signal path
Worker::run() installs SIGINT and SIGTERM handlers when pcntl is available. The handler requests shutdown; it does not kill the active PHP call stack.
$worker = new Worker($client, 'orders', buildId: getenv('APP_RELEASE') ?: null);
$worker->registerWorkflow('orders.process', $workflow);
$worker->registerActivity('orders.reserve', $activity);
$worker->run();
Confirm pcntl is present in the production CLI image:
php -r 'exit(extension_loaded("pcntl") ? 0 : 1);'
Match the supervisor grace period
Set the platform's termination grace period longer than the maximum time one synchronous activity can run without reaching a safe boundary. On Kubernetes, combine terminationGracePeriodSeconds with a pre-stop drain only if the platform can address the worker instance reliably.
Long activities should heartbeat progress and split work into idempotent chunks. A PHP signal cannot safely interrupt an arbitrary library call and then claim its side effect did not happen.
Integrate a framework command
Run the managed loop inside your Console command. The supplied Laravel and Symfony worker commands already use this lifecycle:
$worker->run(pollTimeoutSeconds: 1);
The process supervisor delivers SIGTERM; the managed signal path requests
shutdown and run() deregisters in its cleanup. A framework callback may call
requestShutdown() when it executes during the managed loop. That method sets
the shutdown flag; deregistration occurs when run() exits, rather than at the
instant the callback runs.
tick() alone does not register a worker, initialize its heartbeat lifecycle or
deregister it. A framework-owned while loop around tick() therefore does not
provide the managed lifecycle, and requestShutdown() does not supply the
missing setup or cleanup. Keep a finite poll timeout with run() so an idle
worker can notice shutdown promptly.
Deregistration retries
While a task is claimed, temporary connection failures and proxy HTTP failures such as 502, 503 and 504 retry the original fenced workflow lease renewal or completion request. A completion retry reuses the saved workflow commands or activity result without invoking the handler again. A lost reply may follow a committed outcome, so the Server reconciles the retry using the original task, lease owner and attempt. A revoked or expired claim cannot acquire a new fence through this retry.
These retries use exponential backoff capped at five seconds. Shutdown stops the retry and leaves an unacknowledged outcome for Server recovery. Authentication errors, malformed successful replies and explicit nonretryable Server refusals remain errors. Client start, signal and update calls are not retried by this worker policy.
With a Server that advertises worker_deregistration_fencing, the worker captures
its registration token and uses it when deregistering. Temporary connection or
database contention can be retried within one ten-second budget covering requests
and backoff. A completed receipt is replayed without removing a replacement worker.
Any remaining cancellation deadline also limits this budget.
Retries require bounded transport I/O. The default Guzzle transport supports it
when ext-curl is installed. Custom unbounded transports make one fenced attempt
and emit worker.shutdown_retry_unavailable. Older Servers retain one legacy
deregistration attempt. Persistent failures emit worker.shutdown_failed and
remain failures. An earlier worker error retains precedence.
Rolling deployment sequence
- Start the new compatible worker pool.
- Verify registration freshness and expected build ID on every task queue.
- Send
SIGTERMto the old pool. - Wait for active tasks and registrations to drain.
- Force-stop only after the documented grace window.
If a poll returns a typed terminal status such as draining, stopped, or stale registration, the managed worker stops its loop instead of treating that response as idle work.
Send SIGTERM while the worker is idle and during a bounded test activity. In both cases it should stop within the supervisor window without leaving an unreported completed side effect.