8000 Try to use the APCIterator to clear the APC cache by guillaumelecerf · Pull Request #24008 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

Try to use the APCIterator to clear the APC cache #24008

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed

Try to use the APCIterator to clear the APC cache #24008

wants to merge 1 commit into from

Conversation

guillaumelecerf
Copy link
@guillaumelecerf guillaumelecerf commented Aug 28, 2017

Follow up here : #24011

Q A
Branch? 3.3
Bug fix? yes
New feature? no
BC breaks? no
Deprecations? no
Tests pass? yes
License MIT

TL;DR: when APCuIterator is not available (i.e. PHP 5.6 + APCu 4.0.7), the APC cache handling is broken.

When the app initializes one APC cache, it tries to retrieve its namespace flag key.
If the app can't retrieve its flag key, it clears its namespaced cache and adds its flag key back.
But when APCuIterator is not usable, the app flushes the whole cache each time, preventing the others APC caches to retrieve their flag key, resulting in a regular full APC cache flush.

Deeper explanation:

During the cache warmup, \Symfony\Component\Cache\Adapter\AbstractAdapter::createSystemCache() is called multiple times with different namespaces, i.e:

protected function getCache_SystemService()
{
    return $this->services['cache.system'] = \Symfony\Component\Cache\Adapter\AbstractAdapter::createSystemCache('42DFrsyAcq', 0, 'i6ox8pAXPv2yMq+00ATexQ', (__DIR__.'/pools'), ${($_ = isset($this->services['monolog.logger.cache']) ? $this->services['monolog.logger.cache'] : $this->get('monolog.logger.cache', ContainerInterface::NULL_ON_INVALID_REFERENCE)) && false ?: '_'});
}
protected function getCache_AnnotationsService($lazyLoad = true)
{
    return $this->services['cache.annotations'] = \Symfony\Component\Cache\Adapter\AbstractAdapter::createSystemCache('ljDfqzQGel', 0, 'i6ox8pAXPv2yMq+00ATexQ', (__DIR__.'/pools'), ${($_ = isset($this->services['monolog.logger.cache']) ? $this->services['monolog.logger.cache'] : $this->get('monolog.logger.cache', ContainerInterface::NULL_ON_INVALID_REFERENCE)) && false ?: '_'});
}

but when the APCuIterator is not usable, AbstractAdapter does init the APC cache with this code:

private function init($namespace, $defaultLifetime, $version)
<snip>
            if (!apcu_exists($version.'@'.$namespace)) {
                $this->doClear($namespace);
                apcu_add($version.'@'.$namespace, null);
            }
<snip>
}

protected function doClear($namespace)
{
    return isset($namespace[0]) && class_exists('APCuIterator', false) && ('cli' !== PHP_SAPI || ini_get('apc.enable_cli'))
        ? apcu_delete(new \APCuIterator(sprintf('/^%s/', preg_quote($namespace, '/')), APC_ITER_KEY))
        : apcu_clear_cache();
}

Here is the callstack:

getCache_SystemService()
- AbstractAdapter::createSystemCache('42DFrsyAcq', 0, 'i6ox8pAXPv2yMq+00ATexQ')
-- !apcu_exists('i6ox8pAXPv2yMq+00ATexQ@42DFrsyAcq')
--- apcu_clear_cache()
--- apcu_add('i6ox8pAXPv2yMq+00ATexQ@42DFrsyAcq', null);

APC cache contains :
'i6ox8pAXPv2yMq+00ATexQ@42DFrsyAcq' => null

getCache_AnnotationsService()
- AbstractAdapter::createSystemCache('ljDfqzQGel', 0, 'i6ox8pAXPv2yMq+00ATexQ')
-- !apcu_exists('i6ox8pAXPv2yMq+00ATexQ@ljDfqzQGel')
--- apcu_clear_cache()
--- apcu_add('i6ox8pAXPv2yMq+00ATexQ@ljDfqzQGel', null);

APC cache contains :
'i6ox8pAXPv2yMq+00ATexQ@ljDfqzQGel' => null

Resulting in a complete cache reset between the 2 calls, making every subsequent call to apcu_exists($version.'@'.$namespace) false, and so making the whole APC cache completely useless.

@guillaumelecerf guillaumelecerf changed the title WIP: Limit APC cache clearing to the provided namespace Do not wipe the whole APC cache when we only want to clear a specific namespace Aug 28, 2017
@nicolas-grekas
Copy link
Member

when APCuIterator doesn't exist, does APCIterator exist? can't we use it instead?

return apcu_delete($keysToDelete);
}

return apcu_clear_cache();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be great if this return could be first, so that we can remove one level of indentation above

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@guillaumelecerf
Copy link
Author

Added a case for APCIterator, will need some testing though.

$keysToDelete = new \APCIterator('user', sprintf('/^%s/', preg_quote($namespace, '/')), APC_ITER_KEY);
} else {
$keysToDelete = array();
$cacheInfo = apcu_cache_info();
Copy link
Member
@nicolas-grekas nicolas-grekas Aug 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given that apcu_cache_info exists since v4, but APCIterator since v3.1.1, I think we just don't need this "else" case (and consider APCIterator in the "else" instead, without any "if")

@@ -71,9 +71,26 @@ protected function doHave($id)
*/
protected function doClear($namespace)
{
return isset($namespace[0]) && class_exists('APCuIterator', false) && ('cli' !== PHP_SAPI || ini_get('apc.enable_cli'))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should just remove the false from the class_exists check, to allow using the polyfill implementation of APCuIterator

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The polyfill doesn't use autoload to declare the class, so false or true wouldn't change anything.
Which means installing the polyfill is another way to fix this issue immediately yes.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #24011

@guillaumelecerf
Copy link
Author
guillaumelecerf commented Aug 28, 2017

On tomorrow, I will try this change : https://github.com/guillaumelecerf/symfony/commit/434fdba469340728b696778fd9bb89db99ea1229
and @stof suggestion.

@guillaumelecerf guillaumelecerf changed the title Do not wipe the whole APC cache when we only want to clear a specific namespace Try to use the APCIterator to clear the APC cache Aug 28, 2017
This has the side effect to fix a bug resulting in a non-working APC cache:
When the app initializes one APC cache, it tries to retrieve its namespace
flag key.
If the app can't retrieve its flag key, it clears its namespaced cache and
adds its flag key back.
But when APCuIterator is not usable, the app flushes the whole cache each time,
preventing the others APC caches to retrieve their flag key, resulting in a
regular full APC cache flush.
@guillaumelecerf
Copy link
Author

Follow up here : #24011

@guillaumelecerf guillaumelecerf deleted the fix_ApcuTrait_doClear branch August 29, 2017 08:53
fabpot added a commit that referenced this pull request Aug 29, 2017
…CuIterator everywhere (guillaumelecerf)

This PR was merged into the 3.3 branch.

Discussion
----------

[Cache] Always require symfony/polyfill-apcu to provide APCuIterator everywhere

| Q             | A
| ------------- | ---
| Branch?       | 3.3
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| License       | MIT

**TL;DR: when APCuIterator is not available (i.e. PHP 5.6 + APCu 4.0.7), the APC cache handling is broken.**

When [the app initializes](https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml#L31) one of [the APC caches](https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml#L14-L28), it tries to [retrieve its namespace flag key](https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Cache/Traits/ApcuTrait.php#L42). If it can't, [it clears its namespaced cache](https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Cache/Traits/ApcuTrait.php#L43) and [adds its flag key back](https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Cache/Traits/ApcuTrait.php#L44).
But [when APCuIterator is not usable](https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Cache/Traits/ApcuTrait.php#L74), **the app [flushes the whole cache each time](https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Cache/Traits/ApcuTrait.php#L76)**, preventing all the APC caches to retrieve their own flag key, resulting in a **perpetual full APC cache flush**.

TODO:
- [x] add test => https://travis-ci.org/symfony/symfony/jobs/269383629#L3334

See #24008

Commits
-------

9d44442 Always require symfony/polyfill-apcu to provide APCuIterator everywhere
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants
0