Remove all todos that point to this issue.


Problem/Motivation

Drupal uses function_exists('apc_fetch') to detect the APCu backend.

There're two problems with that:

  1. The detection doesn't verify the ini settings like apc.enabled. In some environments this leads to an usage of APCu even if it's disabled which might lead to any kind of problems. => critical
  2. Unfortunately this also uses APC on a pure APC backend, which has a track record of not working well, because the user cache is combined with the opcode cache and such leads to strong fragmentation.
    There is two things to discuss here:
    • Pro - enabling also for APC itself (non APCu)
      • apc_fetch is still faster than the DB
      • The chained fast backend can be used
    • Contra
      • Strong fragmentation influences the opcode cache
      • That can lead to reduced file and especially class loading times

Proposed resolution

  • Use extension_loaded('apcu') to detect the APC backend only for APCu. (Likely need a helper for this somewhere, which can be configured or overridden.)
  • Verify the ini settings (see comment #15)

Remaining tasks

User interface changes

API changes

Original report

Follow up to #2395143: YAML parsing is very slow, cache it with FileCache

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Fabianx’s picture

Category: Task » Bug report
Issue summary: View changes
Parent issue: » #2395143: YAML parsing is very slow, cache it with FileCache
anavarre’s picture

Issue tags: +Performance
Fabianx’s picture

Status: Active » Postponed
Fabianx’s picture

Status: Postponed » Active

This can be active.

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Fabianx’s picture

Title: Use extension_loaded('apcu') for non-testbot » Use extension_loaded('apcu') instead of apc
Issue summary: View changes
Issue tags: -Performance +Novice, +php-novice

apcu is the extension and PHP 5.4 is no longer supported.

Lets change all 'apc' to 'apcu' in the code and get rid of the emulation layer.

fgm’s picture

Well, since no one did it in over 1 year, here is a first patch.

Fabianx’s picture

Status: Needs review » Needs work

Nice start,

There is also checks for apc_fetch and apc_clear, which need to be converted.

fgm’s picture

I didn't find any in 8.1 core. Where do you see them ? The only apc_* calls I get on a fresh install are below vendor/.

Fabianx’s picture

Status: Needs work » Reviewed & tested by the community

Indeed, we did the majority of this already in #2617568: Rename apc_* functions with apcu_*. I forgot about that.

Lets get the remainder in.

fgm’s picture

Issue tags: +DevDaysMilan
Fabianx’s picture

Status: Reviewed & tested by the community » Needs work

This needs work, because this issue is still referenced by other @todo's as dawehner found out in #2473179: Use FileCache for config storage.

Lets remove them all.

chrisfromredfin’s picture

FileSize
6.52 KB

These are the instances of relevance that I'm finding that mention apc without mentioning apcu:

core/modules/file/file.install:107:    elseif ($implementation == 'apc') {
core/modules/file/file.module:932:    elseif (extension_loaded('apc') && ini_get('apc.rfc1867')) {
core/modules/file/file.module:933:      $implementation = 'apc';
core/modules/file/src/Controller/FileWidgetAjaxController.php:35:    elseif ($implementation == 'apc') {
core/modules/file/src/Element/ManagedFile.php:284:      elseif ($implementation == 'apc') {
core/tests/Drupal/KernelTests/Core/Cache/ApcuBackendTest.php:32:      if (PHP_SAPI === 'cli' && !ini_get('apc.enable_cli')) {

However, I don't know enough about this to know if this is anything that needs to change or not, but hopefully this helps.

I've also attached the full grep list:

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

mkalkbrenner’s picture

Title: Use extension_loaded('apcu') instead of apc » Use extension_loaded('apcu') instead of function_exists('apcu_fetch')
Priority: Normal » Critical
Issue tags: -Novice, -php-novice, -DevDaysMilan
Related issues: +#1942432: Use extension_loaded instead of function_exists to detect PHP extensions

Today I ran into a really strange issue. While debugging I recognized that the ApcClassLoader is used in an environment where apc is disabled!
The problem is caused by the way Drupal detects the availability of APCu. At the moment it's done like this:

if (function_exists('apcu_fetch')) {
  $loader = new ApcClassLoader($prefix, $this->classLoader);
}

I guess we use function_exists here as a workaround for the transition from APC to APCu. But even if we use extension_loaded('apcu') instead the implementation isn't reliable!

The APCu extension could be loaded but be disabled! In this case function_exists('apcu_fetch') and extension_loaded('apcu') will nevertheless both return TRUE.
It's important to check the ini settings as well. That's especially important if an extension is statically compiled into PHP.

For example this is a valid situation you may face:

var_dump(extension_loaded('apcu')); // boolean true
var_dump(function_exists('apcu_fetch')); // boolean true
var_dump(ini_get('apc.enabled')); // string '0'

So the correct detection should look like this:

if (extension_loaded('apcu') && ini_get('apc.enabled') && (ini_get('apc.enable_cli') || !drupal_is_cli())) {
  $loader = new ApcClassLoader($prefix, $this->classLoader);
}

I consider this a critical bug because Drupal must not ignore the configuration of it's environment (even if PHP behaves really stupid here).
Especially in a shared environment where APCu is available in the webserver as module but disabled for some applications due to memory limits or whatever.

tstoeckler’s picture

I think http://php.net/manual/de/apcu.configuration.php#ini.apcu.enabled documents quite clearly that #15 is correct:

apc.enabled can be set to 0 to disable APC. This is primarily useful when APC is statically compiled into PHP, since there is no other way to disable it [...].

mkalkbrenner’s picture

BTW symfony has the same issue :-(

Composer does it better but still not perfect:

    public function setApcuPrefix($apcuPrefix)
    {
        $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null;
    }
mkalkbrenner’s picture

Here's a patch against 8.4.x first.

Status: Needs review » Needs work

The last submitted patch, 18: 2447753-check_apc_enabled-18.patch, failed testing.

mkalkbrenner’s picture

Status: Needs work » Needs review
mkalkbrenner’s picture

Title: Use extension_loaded('apcu') instead of function_exists('apcu_fetch') » APCu detection erroneously succeeds if apc.enabled is "0"
Issue summary: View changes
mkalkbrenner’s picture

Issue summary: View changes
mkalkbrenner’s picture

From IRC:
alexpott: well 8.2.x has had it's last release unless there is a super ciritical
mkalkbrenner: I assume it only hits a few people. But when it hits it's critical. Due to the fact that it already exists since 8.0 I think 8.3 is ok.
alexpott: that is likely to be the consensus

alexpott’s picture

The detection doesn't verify the ini settings like apc.enabled. In some environments this leads to an usage of APCu even if it's disabled which might lead to any kind of problems

What are the problems? - "might" does not lead to this being critical. The issue summary needs to be updated to describe any real issues faced.

+++ b/core/modules/file/file.module
@@ -929,7 +929,7 @@ function file_progress_implementation() {
-    elseif (version_compare(PHP_VERSION, '7', '<') && extension_loaded('apc') && ini_get('apc.rfc1867')) {
+    elseif (extension_loaded('apcu') && ini_get('apc.enabled') && (ini_get('apc.enable_cli') || 'cli' !== php_sapi_name()) && ini_get('apc.rfc1867')) {

This change is not right - the issue that change this needs to be read and completely understood before altering this.

alexpott’s picture

Also there have been a couple of changes to how the APC autoloader works in 8.2.6. See #2843828: \Drupal\Core\DrupalKernel::initializeSettings() can result in moving the autoloader position and #2840596: Update Symfony components to ~2.8.16. @mkalkbrenner can you confirm whether or not the problems were experienced on the latest stable version of D8 and also it would be great to know exactly what you experienced.

mkalkbrenner’s picture

Priority: Critical » Major

I need to to some more research which exact combination of environment setting and core version caused our trouble.
Due to the fact that the ApcClassloader is supposed to fall back to the standard class loader I decrease the priority.

Nevertheless I think that wrongly detecting the availability of APCu and relying on fallbacks in the class loader and the chained cache backend isn't the best architecture if the detection could be implemented correctly.
I assume the additional check of ini settings causes less overhead than a decorator that calls apc_fetch and then uses a fall back ;-)

jibran’s picture

Just uploading the patch for 8.2.5 core.

cilefen’s picture

Status: Needs review » Postponed (maintainer needs more info)

@alexpott, @catch, @cottser, @xjm and I considered this issue at a recent triage meeting and decided to postpone it based on #26 as more information is needed on the impact. If it can be shown to be reproducible in a realistic scenario, it will be triaged major priority.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

gease’s picture

Tried to reroll patch from #18 against 8.9.x. Since it was posted, the relevant codevae has changed though. #3054315: Sort out ApcuBackendTest substantially reworked ApcuBackendTest, removing getRequirements alltogether. Though some assumptions of the current patch may be cut out, but I left it as it is, not reverting (at least partially) commit from #3054315: Sort out ApcuBackendTest. Another relevant issue is #3053363: Remove support for PHP 5 in Drupal 8.8, where apc support was removed from file_progress implementation. But as the current patch should support PHP 7 as well, I restored it the reroll.

Fabianx’s picture

Status: Postponed (maintainer needs more info) » Closed (works as designed)

Thanks for the patch!

IIRC apcu_fetch will silently fail if the backend is disabled.

That is no real problem (and would even lead to bugs in chained fast if changed) as it’s acting as a NULL backend.

ChainedFast would have a bug if this was changed due to it needing the same configuration for cli and web - if the fast backend was only found for web, but not for cli a drush command would fail to clear the cache (as it would not update the timestamp in the DB),

As no one demonstrated a real bug with APCu, I will close this as works as designed.

Thanks again for your patch that brought that issue up again on my radar.

Fabianx’s picture

Issue summary: View changes
Status: Closed (works as designed) » Active

Actually back to active to remove the commented lines.

mkalkbrenner’s picture

As no one demonstrated a real bug with APCu, I will close this as works as designed.

I described the "problem" in #15:

If you want to turn off APCu by setting apc.enabled = 0, you can't! That's a bug.

Fabianx’s picture

As soon as you disable it, it should act as a NULL cache backend.

If there is a bug, we need tests / steps to reproduce it.

mkalkbrenner’s picture

The APCu extension could be loaded but be disabled! In this case function_exists('apcu_fetch') and extension_loaded('apcu') will nevertheless both return TRUE.

Can you point us to the code where this combination results in a NULL cache backend?

Fabianx’s picture

Correct me if I am wrong, but if apcu.enable == 0, then as far as I know the following should happen:

apcu_fetch($cid) == FALSE
apcu_store($cid, $data) == FALSE (IIRC)

It behaves like an always empty cache.

So what side effect / bug does that behavior cause? Trying to understand.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

andypost’s picture

andypost’s picture

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

yonailo’s picture

I have tried to do this in settings.php :

ini_set('apc.enabled', 0);
$aux = apcu_store('hola', 'mundo');
$aux = apcu_fetch('hola');

And it still saves the values into the cache !, so I guess it is impossible to disable APCu using "ini_set" when using the non-static PHP extension ??

I don't really understand why ini_set does not work, any help would be appreciated :)

cilefen’s picture

You cannot disable APCu with ini_set because the "enabled" setting is a PHP_INI_SYSTEM value.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.