Worker Lifecycles

Shared State, Leaks and Worker Lifecycles

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:

State that leaks between requests, and the fixPHP
<?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.