A resident worker breaks PHP's oldest guarantee, that every request starts clean. This simulation runs a handler 20,000 times in one process, first as-is and then with a reset:
<?php
final class Context { public static ?string $user = null; public static array $cache = []; }
function handle(int $n): string {
if ($n === 1) Context::$user = 'alice'; // only request 1 logs in
Context::$cache[] = str_repeat('x', 1024); // a "cache" nobody ever trims
return "request $n sees " . (Context::$user ?? 'guest');
}
function handleSafely(int $n): string {
try { return handle($n); } finally { Context::$user = null; Context::$cache = []; }
}
foreach (['handle', 'handleSafely'] as $fn) {
echo $fn(1), ', ', $fn(2), "\n";
foreach (range(3, 20_000) as $n) $fn($n);
printf("%s: %.1f MB after 20000 requests\n", $fn, memory_get_usage() / 1048576);
}Output
request 1 sees alice, request 2 sees alice handle: 25.4 MB after 20000 requests request 1 sees alice, request 2 sees guest handleSafely: 0.4 MB after 20000 requests
Request 2 was served as alice, a security bug FPM never shows, and the cache reached 25.4 MB. Reset request-scoped services, bound caches (or use WeakMap, WeakMap), and ping stale database connections (Connection Handling). Recycle workers anyway (FPM's pm.max_requests, FrankenPHP 912,500 's MAX_REQUESTS, RoadRunner's max_jobs, Swoole 18,923 's max_request), and restart them on every deploy, since they keep old code in memory.