A queue worker that runs for days is a PHP process living outside every assumption the language makes about lifetime, and the first one to break is the database connection. MySQL closes an idle connection after wait_timeout, eight hours by default, and the worker discovers this the next time it has work to do: SQLSTATE[HY000] [2006] MySQL server has gone away.
$done = 0;
while (true) {
$job = $this->queue->reserve(); // blocks until there is one
if ($this->connectionIsStale()) {
$this->db = $this->connect(); // reconnect at a known point
}
$this->handle($job);
if (++$done >= 500 || memory_get_usage(true) > 128 * 1024 * 1024) {
exit(0); // upstart will start it again
}
}
private function connectionIsStale()
{
try {
$this->db->query('SELECT 1');
return false;
} catch (PDOException $e) {
return true;
}
}
The temptation is to raise wait_timeout to a week, which converts the problem into one permanently idle connection per worker and makes max_connections the thing that fails instead, at a worse moment. Reconnecting at a known point — after the job is reserved and before any work starts — is cheaper and keeps the failure in one place. PDO::ATTR_PERSISTENT does not help and quietly makes it worse, because a persistent handle is reused across a boundary the worker cannot see. The connection is also not the only long-lived state: an identity map, a static cache, an included config array and PHP’s own allocator all accumulate across jobs, which is why the durable pattern is a worker that exits on a job count or a memory ceiling and lets the supervisor start a fresh one. A process that has run 40,000 jobs is not the process anybody tested.