diff --git a/docs/advanced/portable-config.md b/docs/advanced/portable-config.md index 910ae9d..c665992 100644 --- a/docs/advanced/portable-config.md +++ b/docs/advanced/portable-config.md @@ -145,7 +145,7 @@ $portable = PortableConfig::loadSigned($store->load(), $secret); $firewall = new Firewall((new Config($cache))->combine($portable)); ``` -Under classic PHP-FPM each request is a fresh process, so this runs once per request and always reflects the current rules. To avoid querying the database on every request, put a shared cache (APCu, for example) in front of the store. +Under classic PHP-FPM userland state does not carry over between requests, so this runs once per request and always reflects the current rules. To avoid querying the database on every request, put a shared cache (APCu, for example) in front of the store. ### Long-running workers diff --git a/docs/faq.md b/docs/faq.md index 4ed2095..9dc7fc4 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -274,7 +274,7 @@ Yes, Phirewall accepts any PSR-16 (PHP Standard Recommendation for Simple Cachin ### Why does InMemoryCache not work in production? -In PHP-FPM (FastCGI Process Manager), each request starts a new process (or reuses one from the pool). The in-memory cache is empty at the start of each request, so counters always reset to zero. This means rate limits and ban counters never accumulate. +In PHP-FPM (FastCGI Process Manager), worker processes are pooled and reused across requests, but PHP tears down userland state at the end of each request. The in-memory cache therefore starts empty on every request, so counters always reset to zero. This means rate limits and ban counters never accumulate. Under long-running worker runtimes (Swoole, RoadRunner, FrankenPHP worker mode, Octane) the failure is the opposite and easy to miss: the array survives across requests within a worker, so a single-worker demo looks like it "works", but each worker is a separate process with its own array. State is never shared across workers (counters and bans fragment, so the effective rate limit is roughly N times the configured value) and the array grows for the worker's lifetime. diff --git a/docs/features/storage.md b/docs/features/storage.md index c19826d..2027733 100644 --- a/docs/features/storage.md +++ b/docs/features/storage.md @@ -33,7 +33,7 @@ $config = new Config($cache); ### Characteristics - Zero external dependencies -- Data resets on every request in PHP-FPM (each request is a new process) +- Data resets on every request in PHP-FPM (userland state does not persist between requests) - Under long-running worker runtimes (Swoole, RoadRunner, FrankenPHP worker mode, Octane) the array lives for the worker's lifetime: state is **not** shared across workers (each worker is a separate process, so counters and bans fragment) and the array only evicts already-expired entries, so it is not a memory cap. This makes it unsafe as a firewall store there; see the warning below. - Implements both `CacheInterface` (PSR-16) and `CounterStoreInterface` - Automatic expired entry purging every 1000 operations