feature #27549 [Cache] Unconditionally use PhpFilesAdapter for system pools (nicolas-grekas)

This PR was merged into the 4.2-dev branch.

Discussion
----------

[Cache] Unconditionally use PhpFilesAdapter for system pools

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | yes
| Tests pass?   | yes
| Fixed tickets | -
| License       | MIT
| Doc PR        | -

Now that we're about to leverage OPCache shared memory even for objects (see #27543), there's no reason anymore to use APCu for system caches. Let's remove some complexity here.

As a bonus, this makes system caches pruneable using the `cache:pool:prune` command.

Commits
-------

51381e530a [Cache] Unconditionally use PhpFilesAdapter for system pools
This commit is contained in:
Fabien Potencier 2018-06-11 10:37:54 +02:00
commit d4f5d46b13
15 changed files with 51 additions and 58 deletions

View File

@ -1538,7 +1538,6 @@ class FrameworkExtension extends Extension
{ {
$version = new Parameter('container.build_id'); $version = new Parameter('container.build_id');
$container->getDefinition('cache.adapter.apcu')->replaceArgument(2, $version); $container->getDefinition('cache.adapter.apcu')->replaceArgument(2, $version);
$container->getDefinition('cache.adapter.system')->replaceArgument(2, $version);
$container->getDefinition('cache.adapter.filesystem')->replaceArgument(2, $config['directory']); $container->getDefinition('cache.adapter.filesystem')->replaceArgument(2, $config['directory']);
if (isset($config['prefix_seed'])) { if (isset($config['prefix_seed'])) {

View File

@ -35,15 +35,15 @@
<tag name="cache.pool" /> <tag name="cache.pool" />
</service> </service>
<service id="cache.adapter.system" class="Symfony\Component\Cache\Adapter\AdapterInterface" abstract="true"> <service id="cache.adapter.system" class="Symfony\Component\Cache\Adapter\PhpFilesAdapter" abstract="true">
<factory class="Symfony\Component\Cache\Adapter\AbstractAdapter" method="createSystemCache" />
<tag name="cache.pool" clearer="cache.system_clearer" /> <tag name="cache.pool" clearer="cache.system_clearer" />
<tag name="monolog.logger" channel="cache" /> <tag name="monolog.logger" channel="cache" />
<argument /> <!-- namespace --> <argument /> <!-- namespace -->
<argument>0</argument> <!-- default lifetime --> <argument>0</argument> <!-- default lifetime -->
<argument /> <!-- version -->
<argument>%kernel.cache_dir%/pools</argument> <argument>%kernel.cache_dir%/pools</argument>
<argument type="service" id="logger" on-invalid="ignore" /> <call method="setLogger">
<argument type="service" id="logger" on-invalid="ignore" />
</call>
</service> </service>
<service id="cache.adapter.apcu" class="Symfony\Component\Cache\Adapter\ApcuAdapter" abstract="true"> <service id="cache.adapter.apcu" class="Symfony\Component\Cache\Adapter\ApcuAdapter" abstract="true">

View File

@ -102,9 +102,13 @@ abstract class AbstractAdapter implements AdapterInterface, CacheInterface, Logg
* @param LoggerInterface|null $logger * @param LoggerInterface|null $logger
* *
* @return AdapterInterface * @return AdapterInterface
*
* @deprecated since Symfony 4.2
*/ */
public static function createSystemCache($namespace, $defaultLifetime, $version, $directory, LoggerInterface $logger = null) public static function createSystemCache($namespace, $defaultLifetime, $version, $directory, LoggerInterface $logger = null)
{ {
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2.', __CLASS__), E_USER_DEPRECATED);
if (null === self::$apcuSupported) { if (null === self::$apcuSupported) {
self::$apcuSupported = ApcuAdapter::isSupported(); self::$apcuSupported = ApcuAdapter::isSupported();
} }

View File

@ -43,7 +43,6 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
{ {
$this->file = $file; $this->file = $file;
$this->pool = $fallbackPool; $this->pool = $fallbackPool;
$this->zendDetectUnicode = ini_get('zend.detect_unicode');
$this->createCacheItem = \Closure::bind( $this->createCacheItem = \Closure::bind(
function ($key, $value, $isHit) { function ($key, $value, $isHit) {
$item = new CacheItem(); $item = new CacheItem();

View File

@ -24,14 +24,11 @@ class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface
*/ */
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null) public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null)
{ {
if (!static::isSupported()) { self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time();
throw new CacheException('OPcache is not enabled');
}
parent::__construct('', $defaultLifetime); parent::__construct('', $defaultLifetime);
$this->init($namespace, $directory); $this->init($namespace, $directory);
$e = new \Exception(); $e = new \Exception();
$this->includeHandler = function () use ($e) { throw $e; }; $this->includeHandler = function () use ($e) { throw $e; };
$this->zendDetectUnicode = ini_get('zend.detect_unicode');
} }
} }

View File

@ -7,6 +7,7 @@ CHANGELOG
* added `CacheInterface`, which provides stampede protection via probabilistic early expiration and should become the preferred way to use a cache * added `CacheInterface`, which provides stampede protection via probabilistic early expiration and should become the preferred way to use a cache
* throw `LogicException` when `CacheItem::tag()` is called on an item coming from a non tag-aware pool * throw `LogicException` when `CacheItem::tag()` is called on an item coming from a non tag-aware pool
* deprecated `CacheItem::getPreviousTags()`, use `CacheItem::getMetadata()` instead * deprecated `CacheItem::getPreviousTags()`, use `CacheItem::getMetadata()` instead
* deprecated the `AbstractAdapter::createSystemCache()` method
3.4.0 3.4.0
----- -----

View File

@ -36,7 +36,6 @@ class PhpArrayCache implements CacheInterface, PruneableInterface, ResettableInt
{ {
$this->file = $file; $this->file = $file;
$this->pool = $fallbackPool; $this->pool = $fallbackPool;
$this->zendDetectUnicode = ini_get('zend.detect_unicode');
} }
/** /**

View File

@ -24,14 +24,11 @@ class PhpFilesCache extends AbstractCache implements PruneableInterface
*/ */
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null) public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null)
{ {
if (!static::isSupported()) { self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time();
throw new CacheException('OPcache is not enabled');
}
parent::__construct('', $defaultLifetime); parent::__construct('', $defaultLifetime);
$this->init($namespace, $directory); $this->init($namespace, $directory);
$e = new \Exception(); $e = new \Exception();
$this->includeHandler = function () use ($e) { throw $e; }; $this->includeHandler = function () use ($e) { throw $e; };
$this->zendDetectUnicode = ini_get('zend.detect_unicode');
} }
} }

View File

@ -26,7 +26,7 @@ class MaxIdLengthAdapterTest extends TestCase
$cache->expects($this->exactly(2)) $cache->expects($this->exactly(2))
->method('doHave') ->method('doHave')
->withConsecutive( ->withConsecutive(
array($this->equalTo('----------:0GTYWa9n4ed8vqNlOT2iEr:')), array($this->equalTo('----------:nWfzGiCgLczv3SSUzXL3kg:')),
array($this->equalTo('----------:---------------------------------------')) array($this->equalTo('----------:---------------------------------------'))
); );

View File

@ -25,10 +25,6 @@ class PhpFilesAdapterTest extends AdapterTestCase
public function createCachePool() public function createCachePool()
{ {
if (!PhpFilesAdapter::isSupported()) {
$this->markTestSkipped('OPcache extension is not enabled.');
}
return new PhpFilesAdapter('sf-cache'); return new PhpFilesAdapter('sf-cache');
} }

View File

@ -25,10 +25,6 @@ class PhpFilesCacheTest extends CacheTestCase
public function createSimpleCache() public function createSimpleCache()
{ {
if (!PhpFilesCache::isSupported()) {
$this->markTestSkipped('OPcache extension is not enabled.');
}
return new PhpFilesCache('sf-cache'); return new PhpFilesCache('sf-cache');
} }

View File

@ -27,6 +27,7 @@ trait AbstractTrait
private $namespaceVersion = ''; private $namespaceVersion = '';
private $versioningIsEnabled = false; private $versioningIsEnabled = false;
private $deferred = array(); private $deferred = array();
private $ids = array();
/** /**
* @var int|null The maximum length to enforce for identifiers or null when no limit applies * @var int|null The maximum length to enforce for identifiers or null when no limit applies
@ -198,6 +199,7 @@ trait AbstractTrait
$this->commit(); $this->commit();
} }
$this->namespaceVersion = ''; $this->namespaceVersion = '';
$this->ids = array();
} }
/** /**
@ -229,8 +231,6 @@ trait AbstractTrait
private function getId($key) private function getId($key)
{ {
CacheItem::validateKey($key);
if ($this->versioningIsEnabled && '' === $this->namespaceVersion) { if ($this->versioningIsEnabled && '' === $this->namespaceVersion) {
$this->namespaceVersion = '1:'; $this->namespaceVersion = '1:';
foreach ($this->doFetch(array('@'.$this->namespace)) as $v) { foreach ($this->doFetch(array('@'.$this->namespace)) as $v) {
@ -238,11 +238,19 @@ trait AbstractTrait
} }
} }
if (\is_string($key) && isset($this->ids[$key])) {
return $this->namespace.$this->namespaceVersion.$this->ids[$key];
}
CacheItem::validateKey($key);
$this->ids[$key] = $key;
if (null === $this->maxIdLength) { if (null === $this->maxIdLength) {
return $this->namespace.$this->namespaceVersion.$key; return $this->namespace.$this->namespaceVersion.$key;
} }
if (\strlen($id = $this->namespace.$this->namespaceVersion.$key) > $this->maxIdLength) { if (\strlen($id = $this->namespace.$this->namespaceVersion.$key) > $this->maxIdLength) {
$id = $this->namespace.$this->namespaceVersion.substr_replace(base64_encode(hash('sha256', $key, true)), ':', -22); // Use MD5 to favor speed over security, which is not an issue here
$this->ids[$key] = $id = substr_replace(base64_encode(hash('md5', $key, true)), ':', -2);
$id = $this->namespace.$this->namespaceVersion.$id;
} }
return $id; return $id;

View File

@ -56,7 +56,7 @@ trait FilesystemCommonTrait
$ok = true; $ok = true;
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS)) as $file) { foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS)) as $file) {
$ok = ($file->isDir() || @unlink($file) || !file_exists($file)) && $ok; $ok = ($file->isDir() || $this->doUnlink($file) || !file_exists($file)) && $ok;
} }
return $ok; return $ok;
@ -71,12 +71,17 @@ trait FilesystemCommonTrait
foreach ($ids as $id) { foreach ($ids as $id) {
$file = $this->getFile($id); $file = $this->getFile($id);
$ok = (!file_exists($file) || @unlink($file) || !file_exists($file)) && $ok; $ok = (!file_exists($file) || $this->doUnlink($file) || !file_exists($file)) && $ok;
} }
return $ok; return $ok;
} }
protected function doUnlink($file)
{
return @unlink($file);
}
private function write($file, $data, $expiresAt = null) private function write($file, $data, $expiresAt = null)
{ {
set_error_handler(__CLASS__.'::throwError'); set_error_handler(__CLASS__.'::throwError');
@ -98,7 +103,8 @@ trait FilesystemCommonTrait
private function getFile($id, $mkdir = false) private function getFile($id, $mkdir = false)
{ {
$hash = str_replace('/', '-', base64_encode(hash('sha256', static::class.$id, true))); // Use MD5 to favor speed over security, which is not an issue here
$hash = str_replace('/', '-', base64_encode(hash('md5', static::class.$id, true)));
$dir = $this->directory.strtoupper($hash[0].DIRECTORY_SEPARATOR.$hash[1].DIRECTORY_SEPARATOR); $dir = $this->directory.strtoupper($hash[0].DIRECTORY_SEPARATOR.$hash[1].DIRECTORY_SEPARATOR);
if ($mkdir && !file_exists($dir)) { if ($mkdir && !file_exists($dir)) {

View File

@ -26,7 +26,6 @@ trait PhpArrayTrait
private $file; private $file;
private $values; private $values;
private $zendDetectUnicode;
/** /**
* Store an array of cached values. * Store an array of cached values.
@ -98,7 +97,6 @@ EOF;
} }
$dump .= "\n);\n"; $dump .= "\n);\n";
$dump = str_replace("' . \"\\0\" . '", "\0", $dump);
$tmpFile = uniqid($this->file, true); $tmpFile = uniqid($this->file, true);
@ -128,15 +126,6 @@ EOF;
*/ */
private function initialize() private function initialize()
{ {
if ($this->zendDetectUnicode) { $this->values = file_exists($this->file) ? (include $this->file ?: array()) : array();
$zmb = ini_set('zend.detect_unicode', 0);
}
try {
$this->values = file_exists($this->file) ? (include $this->file ?: array()) : array();
} finally {
if ($this->zendDetectUnicode) {
ini_set('zend.detect_unicode', $zmb);
}
}
} }
} }

View File

@ -26,11 +26,14 @@ trait PhpFilesTrait
use FilesystemCommonTrait; use FilesystemCommonTrait;
private $includeHandler; private $includeHandler;
private $zendDetectUnicode;
private static $startTime;
public static function isSupported() public static function isSupported()
{ {
return function_exists('opcache_invalidate') && ini_get('opcache.enable'); self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time();
return \function_exists('opcache_invalidate') && ini_get('opcache.enable') && ('cli' !== \PHP_SAPI || ini_get('opcache.enable_cli'));
} }
/** /**
@ -40,7 +43,6 @@ trait PhpFilesTrait
{ {
$time = time(); $time = time();
$pruned = true; $pruned = true;
$allowCompile = 'cli' !== PHP_SAPI || ini_get('opcache.enable_cli');
set_error_handler($this->includeHandler); set_error_handler($this->includeHandler);
try { try {
@ -48,11 +50,7 @@ trait PhpFilesTrait
list($expiresAt) = include $file; list($expiresAt) = include $file;
if ($time >= $expiresAt) { if ($time >= $expiresAt) {
$pruned = @unlink($file) && !file_exists($file) && $pruned; $pruned = $this->doUnlink($file) && !file_exists($file) && $pruned;
if ($allowCompile) {
@opcache_invalidate($file, true);
}
} }
} }
} finally { } finally {
@ -70,9 +68,6 @@ trait PhpFilesTrait
$values = array(); $values = array();
$now = time(); $now = time();
if ($this->zendDetectUnicode) {
$zmb = ini_set('zend.detect_unicode', 0);
}
set_error_handler($this->includeHandler); set_error_handler($this->includeHandler);
try { try {
foreach ($ids as $id) { foreach ($ids as $id) {
@ -88,9 +83,6 @@ trait PhpFilesTrait
} }
} finally { } finally {
restore_error_handler(); restore_error_handler();
if ($this->zendDetectUnicode) {
ini_set('zend.detect_unicode', $zmb);
}
} }
foreach ($values as $id => $value) { foreach ($values as $id => $value) {
@ -119,7 +111,7 @@ trait PhpFilesTrait
{ {
$ok = true; $ok = true;
$data = array($lifetime ? time() + $lifetime : PHP_INT_MAX, ''); $data = array($lifetime ? time() + $lifetime : PHP_INT_MAX, '');
$allowCompile = 'cli' !== PHP_SAPI || ini_get('opcache.enable_cli'); $allowCompile = self::isSupported();
foreach ($values as $key => $value) { foreach ($values as $key => $value) {
if (null === $value || \is_object($value)) { if (null === $value || \is_object($value)) {
@ -142,7 +134,8 @@ trait PhpFilesTrait
$data[1] = $value; $data[1] = $value;
$file = $this->getFile($key, true); $file = $this->getFile($key, true);
$ok = $this->write($file, '<?php return '.var_export($data, true).';') && $ok; // Since OPcache only compiles files older than the script execution start, set the file's mtime in the past
$ok = $this->write($file, '<?php return '.var_export($data, true).';', self::$startTime - 10) && $ok;
if ($allowCompile) { if ($allowCompile) {
@opcache_invalidate($file, true); @opcache_invalidate($file, true);
@ -155,4 +148,13 @@ trait PhpFilesTrait
return $ok; return $ok;
} }
protected function doUnlink($file)
{
if (self::isSupported()) {
@opcache_invalidate($file, true);
}
return @unlink($file);
}
} }