Корректное завершение работы

OxPHP обрабатывает SIGTERM и SIGINT, чтобы выполняющиеся запросы успевали завершиться до выхода из процесса. Это важно для развёртываний без простоя и плавающих обновлений (rolling updates) в системах оркестрации контейнеров.

Обработка сигналов

OxPHP реагирует на два сигнала завершения:

Сигнал Источник Поведение
SIGTERM Оркестраторы контейнеров, docker stop, kill Запускает корректное завершение работы
SIGINT Ctrl+C в терминале Запускает корректное завершение работы

Оба сигнала запускают одну и ту же последовательность завершения. Достаточно только первого сигнала. Сервер немедленно начинает слив соединений.

Последовательность завершения

При получении сигнала завершения OxPHP выполняет следующую последовательность:

  1. Прекращение приёма новых соединений — сервер перестаёт принимать новые TCP-соединения на основном порту. PHP-воркеры продолжают работать, обрабатывая выполняющиеся запросы.
  2. Сворачивание активных соединений — HTTP/2-клиенты получают кадр GOAWAY, а простаивающие keep-alive-соединения HTTP/1.1 закрываются, чтобы клиенты переходили на исправный экземпляр, а не мультиплексировали новые запросы в завершающийся. Открытые потоки завершаются быстро и аккуратно — сюда относится любой ответ, который уже начал сбрасывать вывод по частям (chunked), — как конечные загрузки, так и SSE — см. Server-Sent Events.
  3. Слив выполняющихся запросов — обычные активные запросы не прерываются и завершаются с полными ответами. Сервер проверяет их завершение каждые 100 мс. Внутренний сервер проверок работоспособности и метрик остаётся доступным на протяжении всего слива, поэтому проверки готовности (readiness probes) продолжают работать.
  4. Соблюдение крайнего срока слива — запросы, всё ещё выполняющиеся по истечении DRAIN_TIMEOUT_SECONDS, отменяются (их коллбэки register_shutdown_function() при этом всё равно выполняются) и получают ~2 секунды на завершение, прежде чем сервер продолжит.
  5. Сброс плагинов — записи access-log и APM-спаны, накопленные в буфере во время окна слива, сбрасываются.
  6. Остановка асинхронного пула — фоновый пул асинхронных задач останавливается.
  7. Остановка внутреннего сервера — сервер проверок работоспособности и метрик останавливается после завершения слива.
  8. Выход — процесс завершается с кодом состояния 0.
Note

PHP-воркеры останавливаются явно на шаге 1 — метод shutdown() исполнителя вызывается в рамках остановки основного сервера, что даёт потокам воркеров сигнал завершиться после обработки любого текущего запроса.

Конфигурация

Переменная По умолчанию Описание
DRAIN_TIMEOUT_SECONDS 25 Максимальное число секунд, которое выполняющиеся запросы получают на завершение, прежде чем будут отменены; процесс завершается в течение ~2 секунд после крайнего срока. Значение по умолчанию оставляет запас для завершения после крайнего срока и сброса телеметрии в рамках стандартного 30-секундного периода отсрочки завершения (termination grace period) в Kubernetes

Задавайте DRAIN_TIMEOUT_SECONDS с учётом самого медленного ожидаемого запроса:

  • API-серверы с быстрыми ответами: 1015 секунд
  • Приложения с загрузкой файлов или долгими запросами: 3060 секунд
  • Режим воркеров с фоновой обработкой: под самую длинную ожидаемую операцию

Kubernetes

В Kubernetes процесс завершения при плавающем обновлении (rolling update) выглядит так:

  1. Kubernetes отправляет SIGTERM в под.
  2. Под удаляется из списка эндпоинтов Service.
  3. OxPHP сливает выполняющиеся соединения в пределах DRAIN_TIMEOUT_SECONDS, затем отменяет отставших и завершается ещё в течение ~2 секунд.
  4. Если под всё ещё работает по истечении terminationGracePeriodSeconds, Kubernetes отправляет SIGKILL.

Задавайте terminationGracePeriodSeconds больше, чем DRAIN_TIMEOUT_SECONDS + 2, чтобы слив — включая завершение после крайнего срока и сброс телеметрии — успел завершиться до принудительного завершения:

yaml
apiVersion: apps/v1 kind: Deployment spec: template: spec: terminationGracePeriodSeconds: 45 containers: - name: oxphp image: ghcr.io/oxphp/oxphp:0.10.0 env: - name: DRAIN_TIMEOUT_SECONDS value: "30"

Хук pre-stop

Если ваш сервис получает трафик от внешних балансировщиков нагрузки, которые медленно распространяют изменения эндпоинтов, добавьте хук pre-stop, чтобы отложить последовательность завершения:

yaml
lifecycle: preStop: exec: command: ["sleep", "5"]

Это даёт балансировщику нагрузки время убрать под из своего списка целей, прежде чем OxPHP прекратит принимать соединения.

Docker

Docker отправляет SIGTERM при выполнении docker stop. Стандартный таймаут остановки Docker составляет 10 секунд, после чего Docker отправляет SIGKILL.

Чтобы дать OxPHP достаточно времени на слив, увеличьте таймаут остановки:

bash
docker stop --time 45 my-oxphp-container

Или задайте его в вашем файле Compose:

compose.yaml
services: oxphp: image: ghcr.io/oxphp/oxphp:0.10.0 stop_grace_period: 45s environment: DRAIN_TIMEOUT_SECONDS: "30"

Сообщения в логах

Во время корректного завершения работы OxPHP выводит структурированные сообщения в логах, которые можно отслеживать:

Успешный слив:

json
{"level":"INFO","message":"Received shutdown signal, draining connections"} {"level":"INFO","message":"Draining in-flight connections","active_connections":3} {"level":"INFO","message":"All connections drained"} {"level":"INFO","message":"Server stopped"}

Достигнут крайний срок слива:

json
{"level":"INFO","message":"Received shutdown signal, draining connections"} {"level":"WARN","message":"Drain timeout reached, cancelling in-flight requests","remaining_connections":1} {"level":"INFO","message":"All connections drained"} {"level":"INFO","message":"Server stopped"}

Если вы регулярно видите предупреждение «Drain timeout reached», увеличьте DRAIN_TIMEOUT_SECONDS или исследуйте долго выполняющиеся запросы с помощью гистограммы oxphp_request_duration_us.

См. также