Problem/Motivation

We were investigating enabling Redis caching for Drupal 8 out of the box on Platform.sh. However, when I tried to install Drupal with the Redis module already configured the first load of the actual site offers a fatal error with the generic "The website encountered an unexpected error. Please try again later."

Rerunning the installer with debug output set to verbose offers this error output:

The website encountered an unexpected error. Please try again later.

InvalidArgumentException: No check has been registered for access_check.permission in Drupal\Core\Access\CheckProvider->loadCheck() (line 97 of core/lib/Drupal/Core/Access/CheckProvider.php).
Drupal\Core\Access\AccessManager->performCheck('access_check.permission', Object) (Line: 135)
Drupal\Core\Access\AccessManager->check(Object, Object, Object, 1) (Line: 112)
Drupal\Core\Access\AccessManager->checkRequest(Object, Object, 1) (Line: 107)
Drupal\Core\Routing\AccessAwareRouter->checkAccess(Object) (Line: 92)
Drupal\Core\Routing\AccessAwareRouter->matchRequest(Object) (Line: 154)
Symfony\Component\HttpKernel\EventListener\RouterListener->onKernelRequest(Object, 'kernel.request', Object) (Line: 111)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch('kernel.request', Object) (Line: 125)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 64)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 57)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 47)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 47)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 50)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 656)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)

Oddly, the first time (without verbose debugging) a page reload worked and gave me an installed site. With verbose debugging enabled a reload just gives me the same error message. If I run "drush cr" manually then the site comes up and appears to function correctly thereafter.

The install process itself seemed to have no error either way. It's just after redirecting to the actual site that the error appears.

Since a force cache rebuild fixes it, my suspicion is that it's caused by a cache not being "Ready" in time to be populated and the particular access check it fails is simply the first of a certain cache lookup rather than itself the buggy component.

Proposed resolution

¯\_(ツ)_/¯

Comments

Crell created an issue. See original summary.

Crell’s picture

Version: 8.x-1.x-dev » 8.x-1.0-beta1
berdir’s picture

Hm, not sure.

I just put a !drupal_installation_attempted() check in settings.platformsh.php, that works well for us. Not the same, but with #2488350: Switch to a memory cache backend during installation, core will actually not use redis even if you tell it to.

Crell’s picture

That's an option I suppose, but would that cause the cache tables to be created and populated in the DB? Ideally we'd avoid that.

Also, it's a bit curious that causing the redis integration to NOT be used during the installer (and thus use the DB cache)... resolves a cache consistency/staleness issue. I'm honestly confused by that.

berdir’s picture

Not combined with the core issue to use the memory backend, just verifed that locally as I started to use that core patch for an install profile we're working on. The only cache table I get then are cachetags.

memtkmcc’s picture

This happens also with Drupal 7, especially when trying to install Commerce Kickstart. Some contrib module is probably causing this.

We couldn't figure out what happens exactly, so we simply turn-off Redis config on the fly on initial site installation in Aegir.

But with Drupal 8 it is complicated even more, because the Redis integration module will do nothing until it is installed (enabled), but you can't install it before the site is installed -- and this may cause unexpected issues, if the site config prepared/created during installation already expects the Redis backend to be available, and you don't have a fallback to standard cache backend (SQL) in place.

With Drupal 7/6 there is no requirement to enable this module, of course, so the WSOD on site install with some D7 distributions is probably a different issue.

Not sure if there is a workaround or if the only solution is just to turn-off Redis integration/configuration during initial site installation.

memtkmcc’s picture

It is not a good idea to not get SQL cache tables created at all. You may still need them if there will be any intermittent issue with Redis availability, and normally you should use Redis backend only if Redis server responds and otherwise fallback to SQL cache.

berdir’s picture

Drupal 8 creates cache tables on the fly, when they are needed, deleting or at least truncating them on production is strongly recommended, otherwise you might copy them or suddenly work against very old cache data, which can actualy mess up a site.

Also, when using the documented platform.sh documentation which explicitly adds the services.yml files and configured the classloader, the redis module does not need to be installed although I'd recommend doing that. See https://docs.platform.sh/frameworks/drupal8/redis.html. That also works elsewhere, you just need to change the dynamic parts.

Crell’s picture

Berdir: Hm. I tried modifying the code block as you suggest, with !drupal_installation_attempted(), and I still get the same failure condition.

if (!empty($_ENV['PLATFORM_RELATIONSHIPS']) && !drupal_installation_attempted()) {
  // ...
}
memtkmcc’s picture

@Berdir Interesting, if we can avoid installing Redis module, it will save us a lot of unnecessary dance with switching config on the fly to avoid this chicken/egg situation. Thanks for the hint!

memtkmcc’s picture

@Berdir Yes, SQL cache tables need to be managed regularly and truncated before running backups/clone/migrate etc and before re-using them again. We automate this in BOA/Aegir, so the fallback from Redis, if needed, just works, but it can't be recommended as a general solution, I agree.

badjava’s picture

I think this issue was created before installing Drupal with sync configuration was added to core. If I try to install Drupal with Redis enabled and properly configured, I get an error like:

drush site-install -y
You are about to DROP all tables in your 'docker_bpd' database. Do you want to continue? (y/n): y
Starting Drupal installation. This takes a while. Consider using the --notify global option.                     [ok]
Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException: You have requested a non-existent      [error]
service "cache.backend.redis"

I comment out the Redis configuration in settings to get it to install and after the site-install is complete, add the Redis configuration back and it works.

I was able to solve this with:

if (!drupal_installation_attempted() && extension_loaded('redis')) {

  // Redis configuration here.

}

Perhaps we can update the documentation?

Also a separate issue probably but it would be nice to document how to remove Redis as a module dependency altogether using $class_loader->addPsr4('Drupal\\redis\\', 'modules/contrib/redis/src');.

Crell’s picture

I don't know what's different, but the !drupal_installation_attempted() trick is still not working for me with Drupal 8.7.7. I still get the same "encountered an error" after installation completes.

What's different here?

Crell’s picture

Version: 8.x-1.0-beta1 » 8.x-1.1
berdir’s picture

What error exactly? You'll either need to make sure the module is installed or set up the necessary service definition and possibly autoloader configuration yourself.

Crell’s picture

The same error as in the OP.

The settings.php file contains (via an include):

if ($platformsh->hasRelationship('rediscache') && !drupal_installation_attempted() && extension_loaded('redis')) {
  $redis = $platformsh->credentials('rediscache');

  // Set a cache prefix so not all sites go into the same cache pool.
  $settings['cache_prefix'] = $platformsh_subsite_id . '_';

  // Set Redis as the default backend for any cache bin not otherwise specified.
  $settings['cache']['default'] = 'cache.backend.redis';
  $settings['redis.connection']['host'] = $redis['host'];
  $settings['redis.connection']['port'] = $redis['port'];

  // Apply changes to the container configuration to better leverage Redis.
  // This includes using Redis for the lock and flood control systems, as well
  // as the cache tag checksum. Alternatively, copy the contents of that file
  // to your project-specific services.yml file, modify as appropriate, and
  // remove this line.
  $settings['container_yamls'][] = 'modules/contrib/redis/example.services.yml';

  // Allow the services to work before the Redis module itself is enabled.
  $settings['container_yamls'][] = 'modules/contrib/redis/redis.services.yml';

  // Manually add the classloader path, this is required for the container cache bin definition below
  // and allows to use it without the redis module being enabled.
  $class_loader->addPsr4('Drupal\\redis\\', 'modules/contrib/redis/src');

  // Use redis for container cache.
  // The container cache is used to load the container definition itself, and
  // thus any configuration stored in the container itself is not available
  // yet. These lines force the container cache to use Redis rather than the
  // default SQL cache.
  $settings['bootstrap_container_definition'] = [
    'parameters' => [],
    'services' => [
      'redis.factory' => [
        'class' => 'Drupal\redis\ClientFactory',
      ],
      'cache.backend.redis' => [
        'class' => 'Drupal\redis\Cache\CacheBackendFactory',
        'arguments' => ['@redis.factory', '@cache_tags_provider.container', '@serialization.phpserialize'],
      ],
      'cache.container' => [
        'class' => '\Drupal\redis\Cache\PhpRedis',
        'factory' => ['@cache.backend.redis', 'get'],
        'arguments' => ['container'],
      ],
      'cache_tags_provider.container' => [
        'class' => 'Drupal\redis\Cache\RedisCacheTagsChecksum',
        'arguments' => ['@redis.factory'],
      ],
      'serialization.phpserialize' => [
        'class' => 'Drupal\Component\Serialization\PhpSerialize',
      ],
    ],
  ];
}

Is it an older config style, perhaps?

I have also intermittently started getting this error post-install. I've not narrowed down how/why yet:

Fatal error: Redis::multi(): Can't activate pipeline in multi mode! in /app/web/modules/contrib/redis/src/Cache/PhpRedis.php on line 96

rodrigoaguilera’s picture

Can the latest fatal error be related with #3068810: Not compatible with php-redis 5 ?

berdir’s picture

Yes, because platform.sh upgraded to php-redis 5 on php 7.3

Crell’s picture

Ugh. Yeah, I'll bet that's it. I'll drop this site back to 7.2 for now. Still not sure about the installer issue.

(What is the fix for the php-redis 5 update?)

bob.hinrichs’s picture

Same problem Drupal 8.9.1, #12 states a good theory, and that code fixes the Drupal install for me. FYI we are installing form config, and using config_split.

I also share the concern that configuration install cannot run with Redis, which means there is stuff going on with mysql tables during install that will be cruft once the problem stage is over, no?

mxr576’s picture

Found another possible scenario when using Redis as a cache backend does not work and you can get an error like #12 when you use Redis with an approach like #16 (with uninstalled Redis module).

tondeuse’s picture

The following solution works for me on Drupal 10.4. Thanks for all for the ideas and tips, this is team works, as usual.

In this format, I may use redis only for local usage, and the performance benefits it provides. The files, variables and module may be absent on hosted site instance without failure.

My dependency to drupal/redis is declared as a DEV dependency in my composer file, in this case.

In my Dockerfile, for all cases

FROM php:8.3-apache

ARG REDIS_PORT
ARG REDIS_HOST
ARG REDIS_PASSWORD
ARG ENABLE_REDIS_CACHE
ARG LOCAL_DEV

# Install Redis PHP extension
RUN pecl install redis \
    && docker-php-ext-enable redis

In my Dockerfile, when running in local dev mode :

An enterprise service inject these files into our hosted containers, this is a solution for local dev that also works for hosted site instances.

# Config for local dev
RUN \
if [ "$LOCAL_DEV" = "TRUE" ]; \

[...]

if [ "$ENABLE_REDIS_CACHE" = "TRUE" ]; \
      then mkdir -p /var/run/secrets/vdmtl/redis; \
      echo "Redis configuration"; \
      echo "$REDIS_PASSWORD" > /var/run/secrets/vdmtl/redis/password; \
      echo "$REDIS_HOST" > /var/run/secrets/vdmtl/redis/host; \
      echo "$REDIS_PORT" > /var/run/secrets/vdmtl/redis/port; \
 fi; \
[...]

fi

In my docker-compose file :

web:
    container_name: web-${APP_NAME}
    links:
      - database:localhost
    build:
      context: .
      dockerfile: Dockerfile
      args:
        - LOCAL_DEV=${LOCAL_DEV}
        - REDIS_PORT=${REDIS_PORT}
        - REDIS_HOST=${REDIS_HOST}
        - REDIS_PASSWORD=${REDIS_PASSWORD}
        - ENABLE_REDIS_CACHE=${ENABLE_REDIS_CACHE}
    environment:
      ENV: ${ENV}
      LOCAL_DEV: ${LOCAL_DEV}
      ENABLE_REDIS_CACHE: ${ENABLE_REDIS_CACHE}
      

  redis:
    container_name: redis-${APP_NAME}
    image: redis:${REDIS_VERSION}
    restart: always
    ports:
      - ${REDIS_PORT}:6379
    command: --port ${REDIS_PORT}
    expose:
      - ${REDIS_PORT}
    volumes:
      - redis-cache:/data:delegated

In my local .env file :

LOCAL_DEV=TRUE

# Redis
REDIS_PORT=6382
REDIS_VERSION=6.2.7
REDIS_HOST='redis'
REDIS_PASSWORD=
ENABLE_REDIS_CACHE=TRUE

In web/sites/default/settings.php

/**
 * Redis config -- import as needed, check if extension is installed first
 */

$redis = getenv('ENABLE_REDIS_CACHE') === 'TRUE' ? true : false;
$redis_extension_installed = file_exists($app_root . '/modules/contrib/redis/redis.info.yml');
if (file_exists($app_root . '/' . $site_path . '/settings.redis.php') && $redis  && $redis_extension_installed) {
  include($app_root . '/' . $site_path . '/settings.redis.php');
}

In web/sites/default/settings.redis.php

<?php
/**
 * Redis settings file
 * @see https://git.drupalcode.org/project/redis/-/blob/8.x-1.x/settings.redis.example.php?ref_type=heads
 *
 * Leverages the php redis ressources installed from dockerfile
 * Will set the redis config before drupal bootstrap, even if the Redis module
 * is not yet installed
 */


use Drupal\Core\Installer\InstallerKernel;
if (!InstallerKernel::installationAttempted() && extension_loaded('redis')) {

  // Set Redis as the default backend for any cache bin not otherwise specified.
  $settings['state_cache'] = TRUE;
  $redis_host = trim(file_get_contents('/var/run/secrets/vdmtl/redis/host'), "\r\n");
  $redis_password = trim(file_get_contents('/var/run/secrets/vdmtl/redis/password'), "\r\n");
  $redis_port = trim(file_get_contents('/var/run/secrets/vdmtl/redis/port'), "\r\n");
  $redis_password = $redis_password === '' ? NULL : $redis_password;

  // Apply changes to the container configuration to better leverage Redis.
  // This includes using Redis for the lock and flood control systems, as well
  // as the cache tag checksum. Alternatively, copy the contents of that file
  // to your project-specific services.yml file, modify as appropriate, and
  // remove this line.
  $settings['cache']['default'] = 'cache.backend.redis';
  $settings['redis_compress_length'] = 100;
  $settings['container_yamls'][] = 'modules/contrib/redis/redis.services.yml';
  $class_loader->addPsr4('Drupal\\redis\\', 'modules/contrib/redis/src');
  $settings['bootstrap_container_definition'] = [
    'parameters' => [],
    'services' => [
      'redis.factory' => [
        'class' => 'Drupal\redis\ClientFactory',
      ],
      'cache.backend.redis' => [
        'class' => 'Drupal\redis\Cache\CacheBackendFactory',
        'arguments' => ['@redis.factory', '@cache_tags_provider.container', '@serialization.phpserialize'],
      ],
      'cache.container' => [
        'class' => '\Drupal\redis\Cache\PhpRedis',
        'factory' => ['@cache.backend.redis', 'get'],
        'arguments' => ['container'],
      ],
      'cache_tags_provider.container' => [
        'class' => 'Drupal\redis\Cache\RedisCacheTagsChecksum',
        'arguments' => ['@redis.factory'],
      ],
      'serialization.phpserialize' => [
        'class' => 'Drupal\Component\Serialization\PhpSerialize',
      ],
    ],
  ];

  try {
    $redis = new Redis();
    $redis->connect($redis_host, $redis_port);
    if ($redis->IsConnected()) {
      $redis->auth($redis_password);
      $response = $redis->ping();
      if ($response) {
        # Redis cache configuration
        $settings['cache_prefix'] = 'dm_';
        $settings['redis.connection']['host'] = $redis_host;
        $settings['redis.connection']['password'] = $redis_password;
        $settings['redis.connection']['port'] = $redis_port;
        $settings['redis.connection']['instance'] = 'cache';
        $settings['redis.connection']['interface'] = 'PhpRedis';
        $settings['cache']['default'] = 'cache.backend.redis';
        $settings['container_yamls'][] = $app_root . '/modules/contrib/redis/example.services.yml';
        $settings['redis_compress_length'] = 100;
        $settings['redis_compress_level'] = 3;
        $conf['redis_perm_ttl'] = 2592000;
        $conf['redis_flush_mode'] = 1;
      }
    }
  } catch (Exception $e) {
  }
}