Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 36 additions & 6 deletions ext/opcache/ZendAccelerator.c
Original file line number Diff line number Diff line change
Expand Up @@ -1900,7 +1900,7 @@ static zend_persistent_script *opcache_compile_file(zend_file_handle *file_handl

static zend_op_array *file_cache_compile_file(zend_file_handle *file_handle, int type)
{
zend_persistent_script *persistent_script;
zend_persistent_script *persistent_script = NULL;
zend_op_array *op_array = NULL;
bool from_memory; /* if the script we've got is stored in SHM */

Expand All @@ -1923,11 +1923,35 @@ static zend_op_array *file_cache_compile_file(zend_file_handle *file_handle, int
}
}

HANDLE_BLOCK_INTERRUPTIONS();
SHM_UNPROTECT();
persistent_script = zend_file_cache_script_load(file_handle);
SHM_PROTECT();
HANDLE_UNBLOCK_INTERRUPTIONS();
/* In file_cache_only mode each call to zend_file_cache_script_load()
* allocates a fresh per-request arena buffer that is never released
* (see GH-9812). Reuse a previously loaded script for the same
* resolved path so subsequent includes do not grow CG(arena). */
if (file_cache_only && file_handle->opened_path) {
if (UNEXPECTED(!ZCG(file_cache_only_scripts).nTableSize)) {
zend_hash_init(&ZCG(file_cache_only_scripts), 8, NULL, NULL, 0);
}
persistent_script = zend_hash_find_ptr(
&ZCG(file_cache_only_scripts), file_handle->opened_path);
if (persistent_script && ZCG(accel_directives).validate_timestamps
&& zend_get_file_handle_timestamp(file_handle, NULL) != persistent_script->timestamp) {
zend_hash_del(&ZCG(file_cache_only_scripts), file_handle->opened_path);
persistent_script = NULL;
}
}

if (!persistent_script) {
HANDLE_BLOCK_INTERRUPTIONS();
SHM_UNPROTECT();
persistent_script = zend_file_cache_script_load(file_handle);
SHM_PROTECT();
HANDLE_UNBLOCK_INTERRUPTIONS();

if (persistent_script && file_cache_only && file_handle->opened_path) {
zend_hash_add_new_ptr(&ZCG(file_cache_only_scripts),
file_handle->opened_path, persistent_script);
}
}
if (persistent_script) {
/* see bug #15471 (old BTS) */
if (persistent_script->script.filename) {
Expand Down Expand Up @@ -2820,6 +2844,12 @@ zend_result accel_post_deactivate(void)
ZCG(cwd) = NULL;
}

if (ZCG(file_cache_only_scripts).nTableSize) {
/* See GH-9812 */
zend_hash_destroy(&ZCG(file_cache_only_scripts));
memset(&ZCG(file_cache_only_scripts), 0, sizeof(HashTable));
}

if (!ZCG(enabled) || !accel_startup_ok) {
return SUCCESS;
}
Expand Down
2 changes: 2 additions & 0 deletions ext/opcache/ZendAccelerator.h
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,8 @@ typedef struct _zend_accel_globals {
zend_persistent_script *cache_persistent_script;
/* preallocated buffer for keys */
zend_string *key;
/* per-process cache to avoid leaks on repeated includes when opcache.file_cache_only=1. */
HashTable file_cache_only_scripts;
} zend_accel_globals;

typedef struct _zend_string_table {
Expand Down
2 changes: 2 additions & 0 deletions ext/opcache/tests/gh9812.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?php
return;
55 changes: 55 additions & 0 deletions ext/opcache/tests/gh9812.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
--TEST--
GH-9812: memory usage grows on repeated include with opcache.file_cache_only=1
--SKIPIF--
<?php
@mkdir(__DIR__ . '/gh9812_cache', 0777, true);
?>
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.jit=disable
opcache.file_cache="{PWD}/gh9812_cache"
opcache.file_cache_only=1
opcache.file_update_protection=0
--EXTENSIONS--
opcache
--FILE--
<?php

$file = __DIR__ . '/gh9812.inc';

// Warm-up: write the script into the file cache
require $file;
require $file;

$baseline = memory_get_usage();
for ($i = 0; $i < 200; $i++) {
require $file;
}
$delta = memory_get_usage() - $baseline;

// Allow a small headroom for VM/JIT.
echo $delta < 4096 ? "stable\n" : "leaking: $delta bytes\n";

?>
--CLEAN--
<?php
function removeDirRecursive($dir) {
if (!is_dir($dir)) return;
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($iterator as $fileinfo) {
if ($fileinfo->isDir()) {
@rmdir($fileinfo->getRealPath());
} else {
@unlink($fileinfo->getRealPath());
}
}
@rmdir($dir);
}
removeDirRecursive(__DIR__ . '/gh9812_cache');
?>
--EXPECT--
stable
Loading