Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

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

[Cache] Cannot warm or clear APCu cache from CLI with apc.enable_cli set to false #53962

Closed
GuySartorelli opened this issue Feb 15, 2024 · 4 comments

Comments

@GuySartorelli
Copy link

GuySartorelli commented Feb 15, 2024

Symfony version(s) affected

>= 3.4

Description

If apc.enable_cli is not set to true, the ChainAdapter will skip the ApcuAdapter even if ApcuAdapter::isSupported() returns true.
This change was introduced in #36555

This means that CLI can't be used to warm APCu cache even when that is actually supported.

How to reproduce

<?php

use Symfony\Component\Cache\Adapter\ApcuAdapter;
use Symfony\Component\Cache\Adapter\ChainAdapter;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;

require_once __DIR__ . '/vendor/autoload.php';

$cacheNamespace = 'MyCacheExample';
$cacheLifetime = 300;
$isCli = 'cli' === \PHP_SAPI && !ini_get('apc.enable_cli');

$adapters = [
    new FilesystemAdapter($cacheNamespace, $cacheLifetime, __DIR__ . '/' . $cacheNamespace)
];

if (ApcuAdapter::isSupported()) {
    $apcuAdapter = new ApcuAdapter($cacheNamespace, $cacheLifetime);
    // Note the order doesn't REALLY matter for this demonstration, but we should still ideally have the cache we *know* we can read from first.
    if ($isCli) {
        // Add it second so we're pulling from the filesystem, since APCu isn't available for reading
        $adapters[] = $apcuAdapter;
    } else {
        // Add it first so we're pulling from memory where possible
        array_unshift($adapters, $apcuAdapter);
    }
}

$cache = new ChainAdapter($adapters, $cacheLifetime);

if ($isCli) {
    echo "CLI" . PHP_EOL;
    $cache->clear();
} else {
    echo "Not CLI" . PHP_EOL;
}
$cache->get('my-cache-item', function () use ($isCli) {
    $value =  $isCli ? 'CLI value' : 'not-CLI value';
    return $value . time();
});
$cache->commit();

echo $cache->getItem('my-cache-item')->get();

I think the order you need to run this is:

  1. from a non-CLI context (or with apc.enable_cli set to true)
  2. from a CLI context with apc.enable_cli set to false (or maybe not set at all?)
  3. from a non-CLI context (or with apc.enable_cli set to true) again.

The first run will set the cache in APCu.
The second run will clear the cache - but since it's skipping APCu it only clears the (already empty) filesystem cache. It then sets a value into the filesystem cache.
The third run will get a cache hit from APCu - it completely ignores the fact that the CLI run was meant to clear it and set a new value.

This should get you into a state where the CLI output is always different from the non-CLI output (at least until the APCu cache times out, at which point you might get lucky and they'll sync up)

Possible Solution

Ideally, that continue in ChainAdapter's constructor would just be outright removed.

If we want to be really careful, then if apc.enable_cli is not set to true the chain adapter could skip it anywhere that reads the adapter's cache, but still use it to set cache so it can warm that cache up and clear it.
I don't fully understand how the internals of this component work so I can't speak to the feasibility of that.

Additional Context

Note that in #25080 the suggestion to check apc.enable_cli to determine whether APCu was rejected specifically because it would prevent this use case - so obviously this at least used to be an intended use case.

@GuySartorelli
Copy link
Author

GuySartorelli commented Feb 15, 2024

(I think I've got that code example right....)
Update: No, now I've got it right 😅

@GuySartorelli
Copy link
Author

GuySartorelli commented Feb 16, 2024

Hmm I had assumed that this continue was the only thing stopping this from working, but it seems that even when that's removed, the APCu cache doesn't get cleared correctly, so the non-CLI context still gets a cache hit from the old value even after the CLI context tried to clear it and set a new one. I would have expected at least for it to be cleared, even if the new CLI value doesn't get stored.

I've updated the code example to include a timestamp in the value so it's clearer when there's a hit vs a miss.

@GuySartorelli GuySartorelli changed the title [Cache] Cannot warm APCu cache from CLI with apc.enable_cli set to false [Cache] Cannot warm or clear APCu cache from CLI with apc.enable_cli set to false Feb 16, 2024
@GuySartorelli
Copy link
Author

More on this - looks like even if I enable apc.enable_cli and comment out the && !ini_get('apc.enable_cli') in the above script, I still can't clear the APCu cache (from the CLI) that ends up getting used for the browser context.

So... maybe that's just explicitly not supported by APCu?

@mikocevar
Copy link
Contributor

@GuySartorelli

APCu cache is not the kind of cache you may be used to everywhere else. This is a shared cache. It works for PHP-FPM (child processes accessing cache from parent) and mod_php.

While it works for CLI too it does not make any sense to use it, since each CLI process dies at the end of execution and thus loses all cached information. CLI processes do not have access to cache created from web requests (php-fpm, mod_php), this is why there are community made "monitoring scripts" for APCu. I used such a script too:

https://github.com/nvthaovn/APCu-Gui-Admin

I could clear the cache from this admin panel or by restarting the php-fpm service. The latter was just a consequence if we had to restart the service for other reasons though.

More on APCu and shared cache:

https://stackoverflow.com/questions/34533695/is-the-new-apcu-apc-user-cache-shared-between-processes
https://stackoverflow.com/questions/60243055/are-php-workers-the-same-as-child-processes

In conclusion if you want to warmup cache from the CLI for the web too, I would suggest you choose Redis or perhaps memcached.

If you insist on using APCu and the CLI, there are possible "hacky" solutions I would personally come up with. You can create a special route which is secured and visited from the CLI, this will actually be a web request (but triggered from the CLI) which warms up your cache.

Feel free to correct me, if I'm wrong, but this is my experience with APCu and CLI.

@symfony symfony locked and limited conversation to collaborators Feb 26, 2024
@nicolas-grekas nicolas-grekas converted this issue into discussion #54066 Feb 26, 2024

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Projects
None yet
Development

No branches or pull requests

3 participants