Skip to content

fix(fpm): let php-fpm drain in-flight requests on shutdown - #55

Merged
zebby76 merged 1 commit into
Smals-Webtech:mainfrom
zebby76:fix/fpm-drain-on-shutdown
Sep 3, 2026
Merged

fix(fpm): let php-fpm drain in-flight requests on shutdown#55
zebby76 merged 1 commit into
Smals-Webtech:mainfrom
zebby76:fix/fpm-drain-on-shutdown

Conversation

@zebby76

@zebby76 zebby76 commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

The supervisor program stops php-fpm with SIGQUIT, waits 20s and signals the whole process group
— everything a graceful stop needs, except the one setting that makes the master honour it.
process_control_timeout defaults to 0, which means the master does not wait for its children at
all: it terminates them where they are.

Every rollout, scale-down and eviction cut the requests in flight.

The earlier drain measurement, ported from base-nginx, covered a static download through nginx and
never touched php-fpm — which is why this went unnoticed.

Measured

With a request that really occupies a worker for 15s. It has to be a clock loop, not sleep():
sleep is interrupted by the stop signal and returns early, which reports a pass on an image that
drains nothing.

duration exit in-flight request
before, docker stop -t 30 1.37s 0 HTTP 502
before, docker stop -t 10 0.55s 0 HTTP 502
after, docker stop -t 30 13.4s 0 HTTP 200

Choosing the value

15s, below the supervisor stopwaitsecs of 20s so the master finishes before supervisor gives
up. It also has to sit below the platform's grace period, and that is the interaction worth
knowing about:

in flight grace period result
a 3s request 10s (plain docker stop) completes, stops in ~1.5s, exit 0
a 15s request 30s (Kubernetes/OpenShift default) completes, stops in ~13s, exit 0
a 15s request 10s (plain docker stop) killed at 10s, exit 137

The common case — a short request — is identical either way: 200, clean exit, ~1.5s. The third row
is the cost of the choice, and it is a case where the request was going to be dropped regardless;
what changes is that the exit code says so.

Kubernetes and OpenShift default terminationGracePeriodSeconds to 30 and need no change; compose
wants stop_grace_period: 30s, a bare docker stop wants -t 30. An operator who would rather
cap the wait lowers PHP_FPM_PROCESS_CONTROL_TIMEOUT instead, which always stops cleanly at the
cost of cutting longer requests.

I measured an 8s default as the alternative — it keeps docker stop clean out of the box but
never drains a long request on any platform. Given the deployment targets, draining where it is
possible won.

Documented in docs/php-fpm.md as a table rather than a sentence: the three timeouts only make
sense together.

Regression assertion

tests.web.bats gains one, using the same clock loop for the same reason. It fails against the
published 8.5.9 image and passes here.

nginx  prd   18/18
apache prd   18/18

The supervisor program stops php-fpm with SIGQUIT, waits 20s and signals the
whole process group -- everything a graceful stop needs, except the one setting
that makes the master honour it. process_control_timeout defaults to 0, which
means the master does not wait for its children at all: it terminates them where
they are. Every rollout, scale-down and eviction cut the requests in flight.

The earlier drain measurement, ported from base-nginx, covered a static download
through nginx and never touched php-fpm, which is why this went unnoticed.

Measured with a request that really occupies a worker for 15s -- a clock loop,
not sleep(), which is interrupted by the stop signal and returns early, reporting
a pass on an image that drains nothing:

  before, docker stop -t 30    1.37s   exit 0     in-flight -> HTTP 502
  before, docker stop -t 10    0.55s   exit 0     in-flight -> HTTP 502
  after,  docker stop -t 30   13.4s    exit 0     in-flight -> HTTP 200

The value is 15s, below the supervisor stopwaitsecs of 20s so the master has
finished before supervisor gives up. It has to sit below the platform's grace
period too, and that is the one interaction worth knowing about:

  in flight     grace period                    result
  a 3s request  10s (plain docker stop)         completes, stops in ~1.5s, exit 0
  a 15s request 30s (Kubernetes default)        completes, stops in ~13s, exit 0
  a 15s request 10s (plain docker stop)         killed at 10s, exit 137

Kubernetes and OpenShift default terminationGracePeriodSeconds to 30 and need no
change; compose wants stop_grace_period: 30s, a bare docker stop wants -t 30.
An operator who would rather cap the wait lowers the variable instead, which
always stops cleanly at the cost of cutting longer requests. Documented as a
table in docs/php-fpm.md rather than as a sentence, because the three timeouts
only make sense together.

The assertion added to tests.web.bats fails against the published 8.5.9 image
and passes here. It uses the same clock loop for the same reason.
@zebby76
zebby76 merged commit d77f26d into Smals-Webtech:main Sep 3, 2026
19 checks passed
zebby76 added a commit that referenced this pull request Sep 3, 2026
The supervisor program stops php-fpm with SIGQUIT, waits 20s and signals the
whole process group -- everything a graceful stop needs, except the one setting
that makes the master honour it. process_control_timeout defaults to 0, which
means the master does not wait for its children at all: it terminates them where
they are. Every rollout, scale-down and eviction cut the requests in flight.

The earlier drain measurement, ported from base-nginx, covered a static download
through nginx and never touched php-fpm, which is why this went unnoticed.

Measured with a request that really occupies a worker for 15s -- a clock loop,
not sleep(), which is interrupted by the stop signal and returns early, reporting
a pass on an image that drains nothing:

  before, docker stop -t 30    1.37s   exit 0     in-flight -> HTTP 502
  before, docker stop -t 10    0.55s   exit 0     in-flight -> HTTP 502
  after,  docker stop -t 30   13.4s    exit 0     in-flight -> HTTP 200

The value is 15s, below the supervisor stopwaitsecs of 20s so the master has
finished before supervisor gives up. It has to sit below the platform's grace
period too, and that is the one interaction worth knowing about:

  in flight     grace period                    result
  a 3s request  10s (plain docker stop)         completes, stops in ~1.5s, exit 0
  a 15s request 30s (Kubernetes default)        completes, stops in ~13s, exit 0
  a 15s request 10s (plain docker stop)         killed at 10s, exit 137

Kubernetes and OpenShift default terminationGracePeriodSeconds to 30 and need no
change; compose wants stop_grace_period: 30s, a bare docker stop wants -t 30.
An operator who would rather cap the wait lowers the variable instead, which
always stops cleanly at the cost of cutting longer requests. Documented as a
table in docs/php-fpm.md rather than as a sentence, because the three timeouts
only make sense together.

The assertion added to tests.web.bats fails against the published 8.5.9 image
and passes here. It uses the same clock loop for the same reason.
@zebby76
zebby76 deleted the fix/fpm-drain-on-shutdown branch September 4, 2026 16:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant