Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 98ea7b1

Browse files
[Cache] fix memory leak when using PhpFilesAdapter
1 parent 9a025b4 commit 98ea7b1

File tree

3 files changed

+58
-12
lines changed

3 files changed

+58
-12
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Cache\Tests\Adapter;
13+
14+
use Psr\Cache\CacheItemPoolInterface;
15+
use Symfony\Component\Cache\Adapter\PhpFilesAdapter;
16+
17+
/**
18+
* @group time-sensitive
19+
*/
20+
class PhpFilesAdapterAppendOnlyTest extends PhpFilesAdapterTest
21+
{
22+
protected $skippedTests = [
23+
'testDefaultLifeTime' => 'PhpFilesAdapter does not allow configuring a default lifetime.',
24+
'testExpiration' => 'PhpFilesAdapter in append-only mode does not expiration.',
25+
];
26+
27+
public function createCachePool(): CacheItemPoolInterface
28+
{
29+
return new PhpFilesAdapter('sf-cache', 0, null, true);
30+
}
31+
}

src/Symfony/Component/Cache/Traits/FilesystemCommonTrait.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ trait FilesystemCommonTrait
2626
private function init($namespace, $directory)
2727
{
2828
if (!isset($directory[0])) {
29-
$directory = sys_get_temp_dir().'/symfony-cache';
29+
$directory = sys_get_temp_dir().\DIRECTORY_SEPARATOR.'symfony-cache';
3030
} else {
3131
$directory = realpath($directory) ?: $directory;
3232
}
@@ -55,8 +55,8 @@ protected function doClear($namespace)
5555
{
5656
$ok = true;
5757

58-
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS)) as $file) {
59-
$ok = ($file->isDir() || $this->doUnlink($file) || !file_exists($file)) && $ok;
58+
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
59+
$ok = ($this->doUnlink($file) || !file_exists($file)) && $ok;
6060
}
6161

6262
return $ok;

src/Symfony/Component/Cache/Traits/PhpFilesTrait.php

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,13 @@ trait PhpFilesTrait
3535
private $files = [];
3636

3737
private static $startTime;
38+
private static $valuesCache = [];
3839

3940
public static function isSupported()
4041
{
4142
self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time();
4243

43-
return \function_exists('opcache_invalidate') && ('cli' !== \PHP_SAPI || filter_var(ini_get('opcache.enable_cli'), FILTER_VALIDATE_BOOLEAN)) && filter_var(ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN);
44+
return \function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN) && (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) || filter_var(ini_get('opcache.enable_cli'), FILTER_VALIDATE_BOOLEAN));
4445
}
4546

4647
/**
@@ -54,7 +55,7 @@ public function prune()
5455

5556
set_error_handler($this->includeHandler);
5657
try {
57-
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
58+
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
5859
try {
5960
if (\is_array($expiresAt = include $file)) {
6061
$expiresAt = $expiresAt[0];
@@ -100,7 +101,6 @@ protected function doFetch(array $ids)
100101
} elseif (!\is_object($value)) {
101102
$values[$id] = $value;
102103
} elseif (!$value instanceof LazyValue) {
103-
// calling a Closure is for @deprecated BC and should be removed in Symfony 5.0
104104
$values[$id] = $value();
105105
} elseif (false === $values[$id] = include $value->file) {
106106
unset($values[$id], $this->values[$id]);
@@ -123,14 +123,18 @@ protected function doFetch(array $ids)
123123
try {
124124
$file = $this->files[$id] ?? $this->files[$id] = $this->getFile($id);
125125

126-
if (\is_array($expiresAt = include $file)) {
126+
$expiresAt = $this->appendOnly && isset(self::$valuesCache[$file]) ? self::$valuesCache[$file] : include $file;
127+
128+
if (\is_array($expiresAt)) {
127129
[$expiresAt, $this->values[$id]] = $expiresAt;
128130
} elseif ($now < $expiresAt) {
129131
$this->values[$id] = new LazyValue($file);
130132
}
131133

132134
if ($now >= $expiresAt) {
133-
unset($this->values[$id], $missingIds[$k]);
135+
unset($this->values[$id], $missingIds[$k], self::$valuesCache[$file]);
136+
} elseif ($this->appendOnly && !isset(self::$valuesCache[$file]) && !$this->values[$id] instanceof LazyValue) {
137+
self::$valuesCache[$file] = [$expiresAt, $this->values[$id]];
134138
}
135139
} catch (\ErrorException $e) {
136140
unset($missingIds[$k]);
@@ -159,7 +163,9 @@ protected function doHave($id)
159163
$file = $this->files[$id] ?? $this->files[$id] = $this->getFile($id);
160164
$getExpiry = true;
161165

162-
if (\is_array($expiresAt = include $file)) {
166+
$expiresAt = $this->appendOnly && isset(self::$valuesCache[$file]) ? self::$valuesCache[$file] : include $file;
167+
168+
if (\is_array($expiresAt)) {
163169
[$expiresAt, $value] = $expiresAt;
164170
} elseif ($this->appendOnly) {
165171
$value = new LazyValue($file);
@@ -172,6 +178,10 @@ protected function doHave($id)
172178
if ($this->appendOnly) {
173179
$now = 0;
174180
$this->values[$id] = $value;
181+
182+
if ($this->appendOnly && !isset(self::$valuesCache[$file]) && !$value instanceof LazyValue) {
183+
self::$valuesCache[$file] = [$expiresAt, $value];
184+
}
175185
} else {
176186
$now = time();
177187
}
@@ -211,12 +221,14 @@ protected function doSave(array $values, $lifetime)
211221
$value = var_export($value, true);
212222
}
213223

214-
if (!$isStaticValue) {
224+
if ($isStaticValue) {
225+
$value = "<?php return [{$expiry}, {$value}];\n";
226+
} elseif ($this->appendOnly) {
227+
$value = "<?php return [{$expiry}, static function () { return {$value}; }];\n";
228+
} else {
215229
// We cannot use a closure here because of https://bugs.php.net/76982
216230
$value = str_replace('\Symfony\Component\VarExporter\Internal\\', '', $value);
217231
$value = "<?php\n\nnamespace Symfony\Component\VarExporter\Internal;\n\nreturn \$getExpiry ? {$expiry} : {$value};\n";
218-
} else {
219-
$value = "<?php return [{$expiry}, {$value}];\n";
220232
}
221233

222234
$file = $this->files[$key] = $this->getFile($key, true);
@@ -227,6 +239,7 @@ protected function doSave(array $values, $lifetime)
227239
@opcache_invalidate($file, true);
228240
@opcache_compile_file($file);
229241
}
242+
unset(self::$valuesCache[$file]);
230243
}
231244

232245
if (!$ok && !is_writable($this->directory)) {
@@ -260,6 +273,8 @@ protected function doDelete(array $ids)
260273

261274
protected function doUnlink($file)
262275
{
276+
unset(self::$valuesCache[$file]);
277+
263278
if (self::isSupported()) {
264279
@opcache_invalidate($file, true);
265280
}

0 commit comments

Comments
 (0)