Skip to content

Race condition that makes a page cacheable in varnish with without X-Magento-Tags when use_stale_cache is enabled #40281

@alinalexandru

Description

@alinalexandru

Preconditions and environment

This race condition is hard to reproduse every time, but doing a code analysis will confirm the bug.
Method \Magento\PageCache\Model\Layout\LayoutPlugin::afterGenerateElements is called before \Magento\PageCache\Model\Layout\LayoutPlugin::afterGetOutput meaning that other actions can happen mean time.

With the code first hits \Magento\PageCache\Model\Layout\LayoutPlugin::afterGenerateElements, the FPC could be evaluated as activ and public headers are set to response.

Then imagine that we have some code that calls \Magento\Framework\Cache\Backend\RemoteSynchronizedCache::load and we have the following scenario

public function load($id, $doNotTestCacheValidity = false)
{
    $localData = $this->local->load($id);

    if ($localData !== false) {
        if ($this->getDataVersion($localData) === $this->loadRemoteDataVersion($id)) {
            return $localData;
        }
    }

    $remoteData = $this->remote->load($id);
    if ($remoteData !== false) {
        $this->local->save($remoteData, $id);

        return $remoteData;
    } elseif ($localData && $this->_options['use_stale_cache']) {
        if ($this->lock($id)) {
            return false;
        } else {
            $this->notifyStaleCache();
            return $localData;
        }
    }

    return false;
}

The following thinks needs to happen:

  1. data should exists in local storage ($localData) but not in redis ($remoteData)
  2. The lock should not succeed
  3. The code calls $this->notifyStaleCache();, which then will mark FPC as disabled (\Magento\Framework\Cache\CompositeStaleCacheNotifier::cacheLoaderIsUsingStaleCache)
  4. The the function \Magento\PageCache\Model\Layout\LayoutPlugin::afterGetOutput will not set any headers because FPC is now disabled.

Steps to reproduce

  1. Have magento with stale_cache_enabled
  2. Have varnish configured and enabled
  3. Have cache warmed up. To have data in local cache and Redis
  4. Put a breakpoint at \Magento\PageCache\Model\Layout\LayoutPlugin::afterGenerateElements and \Magento\PageCache\Model\Layout\LayoutPlugin::afterGetOutput in order to be able to simulate the next steps
  5. Edit \Magento\Framework\Cache\Backend\RemoteSynchronizedCache::lock to return false in order to simulate a lock failure in Redis
  6. Flush Redis cache - simulate a flush cache
  7. Have code that reads from cache, and that cache exists in local cache
  8. Run the code
  9. When the code arrives in \Magento\PageCache\Model\Layout\LayoutPlugin::afterGetOutput, you will that the $this->config->isEnabled() will return false and X-Magento-Tags headers will no be set

It's very important that during manual testing to have step 7 executed

Expected result

The page should have private content headers in order to not be cached in Varnish.

Actual result

The page is generated with cacheable HTTP headers but without X-Magento-Tags.
Those pages remains with old contend (old prices, old stock) which causes customer issues.
Those pages can not be deleted from Varnish using admin or cli commands. Varnish needs to be restarted or wait until those pages are automatically deleted (depending of the TTL set)

Additional information

No response

Release note

No response

Triage and priority

  • Severity: S0 - Affects critical data or functionality and leaves users without workaround.
  • Severity: S1 - Affects critical data or functionality and forces users to employ a workaround.
  • Severity: S2 - Affects non-critical data or functionality and forces users to employ a workaround.
  • Severity: S3 - Affects non-critical data or functionality and does not force users to employ a workaround.
  • Severity: S4 - Affects aesthetics, professional look and feel, “quality” or “usability”.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    Status

    Ready for Confirmation

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions