Hacktoberfest 2026 : les issues que les mainteneurs ont marquées pour octobre, ouvertes et accessibles aux débutants. Parcourir les issues Hacktoberfest

Repeated opcache_reset() under concurrency permanently grows ZCSG(map_ptr_last), inflating every newly spawned php-fpm worker

Ouverte
#23,859 1 commentaire 1 réaction 0 personnes assignées Voir sur GitHub

Les mainteneurs répondent en général sous 1 jour

Personne n'a encore pris cette issue.

Évaluation

Difficulté
4/5
Temps estimé
3-5 jours
Accessibilité débutants
45/100
Type d'issue
Bug
Clarté
Clairement spécifiée
Activité
Active
Stack technique
c, php
Domaine
performance

Piste de recherche

The issue is in ext/opcache/ZendAccelerator.c and zend_persist.c, focusing on ZCSG(map_ptr_last) and CG(map_ptr_last) handling during opcache_reset(). Start by examining accel_activate() and zend_reset_cache_vars() to see how map_ptr_last is reset. The reproducer script shows how to trigger the bug with concurrent requests. Look for where cache_script_in_shared_memory() writes back to SHM. Done means verifying that repeated opcache_reset() no longer causes permanent memory growth in new workers.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Description

Bug Status: Needs Triage
Description
Description

On a busy php-fpm server, an application that calls opcache_reset() on many requests makes every newly spawned worker start bigger and bigger, without limit, until FPM is restarted.

The memory is a single large zero-filled anonymous mapping allocated by zend_map_ptr_extend() when a worker loads its first script from SHM. It is not visible to userland: on the affected production server PHP reported peak request memory of 2-38 MB while each worker's RSS was ~1.1 GB from birth.

Each opcache_reset() permanently increases ZCSG(map_ptr_last); it never shrinks. Workers born later allocate (and zero) the whole table.

Concurrency is required to reproduce. With resets happening while the pool is idle, growth is negligible (500 resets => +2 MB). With concurrent traffic and workers being spawned/reaped, the table grows on every reset.

I have not pinned down the exact code path for the concurrent case. My reading is that accel_activate() resets ZCSG(map_ptr_last) via zend_reset_cache_vars() only in the process that performs the restart, while workers that were busy keep their old (large) CG(map_ptr_last) and write it back to SHM on the next cache_script_in_shared_memory() (zend_persist.c: ZCSG(map_ptr_last) = CG(map_ptr_last);). That part is a hypothesis; the measurements below are not.

Real-world impact

A PHP application called opcache_reset() on ~4,700 requests/day (~1 every 16 s) on a shared server with ~850 req/min of traffic:

  • growth measured in production: ~0.3 MB per reset (app of ~6,000 cached scripts)
  • 21 hours after the last FPM restart, every newly spawned worker was ~960 MB at birth
  • php-fpm total RSS: 52 GB across ~50 workers; a graceful reload brought it back to 2.4 GB
  • the previous day the box (128 GB RAM) hit OOM, the kernel started killing processes and sites went down
  • OPcache SHM is shared by all pools of that PHP version, so unrelated accounts on the same server were inflated too
Reproducer

Attached script: starts its own php-fpm (temp dir, own socket), serves a small app (2,500 autoloaded classes with inheritance/interfaces/traits) and sends concurrent FastCGI requests, optionally issuing opcache_reset() concurrently. Python 3.6+, stdlib only.

python3 php_opcache_reset_repro.py <php-fpm> <opcache.so|-> 600 noreset
python3 php_opcache_reset_repro.py <php-fpm> <opcache.so|-> 600 reset

It prints, before and after, the max worker RSS and the max anonymous mapping in a worker (where the map_ptr table lives).

Results (600 cycles, 6 concurrent requests each)
PHP Build mode max worker RSS max anon mapping
8.3.33 Debian/Sury noreset 20 -> 22 MB 4 -> 2 MB
8.4.24 Debian 13 noreset 21 -> 22 MB 6 -> 2 MB
8.5.8 static-php-cli noreset 42 -> 44 MB 19 -> 19 MB
8.1.34 cPanel ea-php reset (598) 19 -> 87 MB 4 -> 57 MB
8.1.34 CloudLinux alt-php reset (598) 20 -> 85 MB 4 -> 54 MB
8.3.33 Debian/Sury reset (592) 20 -> 94 MB 4 -> 65 MB
8.3.33 cPanel ea-php reset (598) 19 -> 84 MB 6 -> 55 MB
8.4.24 Debian 13 reset (600) 20 -> 97 MB 6 -> 66 MB
8.4.25 cPanel ea-php reset (600) 20 -> 89 MB 6 -> 58 MB
8.4.25 CloudLinux alt-php reset (595) 21 -> 87 MB 6 -> 55 MB
8.5.8 static-php-cli reset (597) 43 -> 111 MB 19 -> 61 MB

Growth is linear and does not level off: 1,800 resets give ~200 MB (8.3.33) and ~206 MB (8.5.8) of anonymous mapping per worker.

Note this is distinct from GH-8646, whose fix (zend_map_ptr_reset() at request end when CG(interned_strings) is non-empty) only applies when OPcache is disabled. Here OPcache is enabled and the growth happens through opcache_reset().

Expected result

Repeated opcache_reset() should not permanently increase per-worker memory. After a reset the map_ptr table should return to a size proportional to the code actually cached.

Actual result

ZCSG(map_ptr_last) grows on every reset and never shrinks, so every worker spawned afterwards allocates and zero-fills an ever larger table, until php-fpm is restarted/reloaded.

PHP Version
Reproduced on 8.1.34, 8.3.33, 8.4.24, 8.4.25 and 8.5.8 (Debian, Sury, static-php-cli, cPanel ea-php, CloudLinux alt-php). The relevant code in ext/opcache/ZendAccelerator.c looks unchanged in master.
Operating System

AlmaLinux 10 (production), Debian 13 (local repro)

Langage dominant
C
Étoiles
40.4k
Forks
8.2k
Merge moyen
2 j 11 h
PR mergées (30 j)
116

Préparer son environnement

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Autres issues de php/php-src

Toutes les issues de php/php-src

Issues similaires

Plus d'issues C

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.