[php-src] Issue #24777: Light request fails with "Allowed memory size exhausted" on a huge allocation after heavy requests: cached chunks count
towards memory_limit and are not released
| From: | rychnal | Date: | Tue, 27 Oct 2026 10:20:02 +0000 |
| Subject: | [php-src] Issue #24777: Light request fails with "Allowed memory size exhausted" on a huge allocation after heavy requests: cached chunks count towards memory_limit and are not released |
||
| Groups: | php.bugs | ||
| Request: | Send a blank email to php-bugs+get-253235@lists.php.net to get a copy of this message | ||
Issue: https://github.com/php/php-src/issues/24777
Author: rychnal
### Description
### Description
In a long-running worker (PHP-FPM, also uWSGI), after a series of memory-heavy requests, a later
light request fails with
Allowed memory size of N bytes exhausted on its first huge
allocation (>= 2 MiB), even though the request itself uses only a few MiB.
The cause seems to be the chunk cache in zend_mm_shutdown(): free chunks are kept for
the next request (average of peak usage) and counted in heap->real_size:
https://github.com/php/php-src/blob/c7273b9ba89cc8c0beaf19c03a77332361e7a89e/Zend/zend_alloc.c#L2682
Small allocations reuse the cached chunks, so they are fine. A huge allocation is
mmaped outside of chunks, so the cached chunks cannot serve it, but they are still
counted against the limit. zend_mm_check_limit() only tries zend_mm_gc(),
which does not touch heap->cached_chunks, so the allocation fails although the
cached chunks are free memory:
https://github.com/php/php-src/blob/c7273b9ba89cc8c0beaf19c03a77332361e7a89e/Zend/zend_alloc.c#L527-L557
The only place that releases the cache during a request is zend_set_memory_limit()
(GH-8053), so ini_set('memory_limit', ...) to a lower value and back works
around it.
In https://github.com/php/php-src/pull/8053#issuecomment-1032554334
it was said that the cache "can not exceed the memory_limit". It does not exceed it, but
it can leave less headroom than a single huge allocation needs. A user reported the same symptom on
FPM 8.1 in that thread (random, non-reproducible "Allowed memory size exhausted", high
memory_get_usage(true) at request start), without a reproducer.
We see this in production (uWSGI, PHP 8.5): thousands of random OOMs on light requests (image
thumbnails) on a 7 MiB allocation in finfo::file() (libmagic buffer
FILE_BYTES_MAX + SLOP = 7340161 bytes). Blackfire shows ~15 MB peak for
these requests.
### Reproducer (PHP-FPM, single worker)
index.php:
```php
<?php
printf("pid=%d %-5s start: used=%.1fMiB real=%.0fMiB\n", getmypid(), $_GET['m'],
memory_get_usage() / 1048576, memory_get_usage(true) / 1048576);
if ($_GET['m'] === 'heavy') { // ~100 MiB of small allocations, peak 126 MiB,
below the limit
$a = [];
for ($i = 0; $i < 99500; $i++) { $a[] = str_repeat('x', 1000); }
}
if ($_GET['m'] === 'light') { // ~4 MiB of small allocations + one 7 MiB
allocation
$a = [];
for ($i = 0; $i < 3000; $i++) { $a[] = str_repeat('z', 1000); }
$s = str_repeat('y', 7340161);
echo "light OK\n";
}
```
fpm.conf:
```ini
[global]
daemonize = yes
error_log = /tmp/fpm.log
[test]
listen = 127.0.0.1:9100
pm = static
pm.max_children = 1
php_admin_value[memory_limit] = 128M
php_admin_flag[display_errors] = on
```
```bash
docker run -d --rm --name fpm-test -v "$PWD":/app php:8.5-fpm sh -c 'sleep 3600'
docker exec fpm-test sh -c 'apt-get update -qq && apt-get install -y -qq
libfcgi-bin'
docker exec fpm-test sh -c 'php-fpm -y /app/fpm.conf -R
req() { SCRIPT_FILENAME=/app/index.php REQUEST_METHOD=GET QUERY_STRING="m=$1" cgi-fcgi
-bind -connect 127.0.0.1:9100 | sed "1,/^\r\?$/d"; }
for m in heavy heavy heavy heavy heavy heavy heavy heavy light; do req $m; done'
```
Output (PHP 8.5.12, fpm-fcgi):
```
pid=101 heavy start: used=0.4MiB real=2MiB
pid=101 heavy start: used=0.4MiB real=62MiB
pid=101 heavy start: used=0.4MiB real=92MiB
pid=101 heavy start: used=0.4MiB real=108MiB
pid=101 heavy start: used=0.4MiB real=116MiB
pid=101 heavy start: used=0.4MiB real=120MiB
pid=101 heavy start: used=0.4MiB real=122MiB
pid=101 heavy start: used=0.4MiB real=122MiB
pid=101 light start: used=0.4MiB real=122MiB
Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 7344128 bytes)
```
The same happens with uWSGI (PHP plugin) and with php -S.
### Expected
The light request succeeds: the cached chunks are free memory, so they should be released (or not
counted) before an allocation fails on memory_limit.
### Possible fix
When zend_mm_check_limit() would fail, free cached chunks (as
zend_set_memory_limit() already does) and check again, before reporting the error.
### PHP Version
```plain
PHP 8.5.12 (fpm-fcgi) (built: Oct 21 2026 15:49:51) (NTS)
Copyright (c) The PHP Group
Built by https://github.com/docker-library/php
Zend Engine v4.5.12, Copyright (c) Zend Technologies
with Zend OPcache v8.5.12, Copyright (c), by Zend Technologies
Also reproduced with uWSGI 2.0.31 + PHP 8.5.12 and with php -S. zend_mm_shutdown() /
zend_mm_check_limit() are unchanged in current master.
```
### Operating System
Debian GNU/Linux 13 (trixie) – official php:8.5-fpm Docker image