A long-running worker holds a database connection that already died

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.