; ---------------------------------------------------------------------------
; PHP-FPM pool configuration for the PMS backend.
;
; Replaces the stock www.conf from the php:8.4-fpm image, which shipped
; pm = dynamic / pm.max_children = 5 - a hard ceiling of FIVE concurrent PHP
; requests for the entire application.
;
; Sized for 4 vCPU / 8 GB shared with MySQL, nginx and the frontend.
; See docker/php/php.ini for the matching memory_limit.
; ---------------------------------------------------------------------------

[global]
error_log = /proc/self/fd/2
daemonize = no

; If the pool loses 10 children inside a minute, restart the master rather than
; limping along with a degraded pool.
emergency_restart_threshold = 10
emergency_restart_interval  = 1m
process_control_timeout     = 10s

[www]
user  = www-data
group = www-data

; Matches zz-docker.conf from the base image; stated explicitly so this file is
; self-contained.
listen         = 9000
listen.backlog = 511

; --- Process manager --------------------------------------------------------
; static, not dynamic: this is a low-to-moderate traffic internal tool, where
; `dynamic` spends CPU constantly forking and reaping workers, and `ondemand`
; makes nearly every request pay a cold fork plus a Laravel bootstrap. `static`
; costs a flat ~810 MB resident (18 x ~45 MB) and gives the flattest p99.
pm = static

; DERIVATION of 18:
;   CPU constraint  - ~2.5 of 4 vCPU available to PHP, ~35 ms CPU/request with
;                     OPcache => ~71 req/s. Little's Law at ~50 ms wall time
;                     => 3.6 busy children saturate the CPU. Round to 4.
;                     x3 burst headroom = 12, +6 for workers blocked on the
;                     outbound Gemini/Upwork HTTP calls = 18.
;   RAM constraint  - 18 x 160M memory_limit = 2880M, inside the ~3138M budget
;                     left after MySQL, OPcache SHM, nginx and the OS.
; Take the smaller of the two. Both land at 18.
;
; Raising this REQUIRES lowering memory_limit in php.ini, or the box can OOM.
; Verify the real per-worker RSS before changing it (see the audit notes).
pm.max_children = 18

; Recycle workers to shed the high-water RSS left behind by large uploads
; (base64 encoding an image transiently costs ~3.67x the file size). With
; OPcache warm a respawn costs ~15-30 ms. Do not set this to 0.
pm.max_requests = 500

; Ignored while pm = static. Kept for reference if you ever switch to dynamic;
; they satisfy start_servers = (min_spare + max_spare) / 2.
;pm.start_servers     = 7
;pm.min_spare_servers = 4
;pm.max_spare_servers = 10

; --- Monitoring -------------------------------------------------------------
; Not exposed through nginx, so these are only reachable from inside the
; container (e.g. via cgi-fcgi). Deliberately not published.
pm.status_path = /fpm-status
ping.path      = /fpm-ping

; --- Timeouts ---------------------------------------------------------------
; DERIVATION of 75s: the extraction path allows 30s cURL + 1s sleep + 30s retry
; = 61s worst case, plus ~4s for bootstrap and payload encoding, plus margin.
; nginx is set to fastcgi_read_timeout 90s so that FPM kills the request first
; and logs a backtrace, instead of nginx returning 504 while FPM keeps burning
; the worker.
;
; TARGET: drop this to 30s once the long outbound HTTP calls are moved onto the
; queue. 75s is an interim value that matches current application behaviour.
request_terminate_timeout = 75s

; Dumps a full PHP backtrace for any request over 5s, at essentially zero cost.
; This is the highest-value diagnostic available on this stack.
request_slowlog_timeout = 5s
slowlog                 = /proc/self/fd/2

; --- Environment ------------------------------------------------------------
; php-fpm's built-in default is clear_env = yes, which hands workers an EMPTY
; environment - meaning the variables supplied via env_file in
; docker-compose.prod.yml never reach PHP at runtime. The app currently works
; only because `php artisan config:cache` runs from the entrypoint as a CLI
; process, which does see the environment. Setting this to `no` makes the
; container's declared environment behave the way the compose file implies.
clear_env = no

; Outbound HTTP(S) goes through the local forward proxy, because containers on
; this host have no working bridge NAT and every direct external call fails to
; resolve. libcurl reads these variables itself, so Gemini and ERP calls start
; working with no application change. See docker/proxy/tinyproxy.conf.
;
; http_proxy must be lower case: libcurl deliberately ignores HTTP_PROXY to
; avoid honouring a client-supplied Proxy header in CGI environments.
;
; DELETE these three lines once the host's docker NAT is repaired, otherwise
; all external traffic keeps taking an unnecessary extra hop.
env[http_proxy]  = http://172.30.0.1:18080
env[https_proxy] = http://172.30.0.1:18080
env[no_proxy]    = localhost,127.0.0.1,mysql,backend,backend_web,frontend

catch_workers_output    = yes
decorate_workers_output = no

; --- Per-pool PHP overrides -------------------------------------------------
; php_admin_value cannot be overridden by ini_set() at runtime.
php_admin_value[memory_limit] = 160M
php_admin_value[error_log]    = /proc/self/fd/2
php_admin_flag[log_errors]    = on
