Following several hours of discussion between myself, Wim Leers, beejeebus and Alex Pott at DevDays, we have a potential solution for all of the following issues:

#2114319: Lots of cache requests from plugin discovery
#1880766: Preload configuration objects
#2223143: Consolidate library extension caches into a single cache entry

Problem

There are in the region of 20-30 cache gets per page from configuration on a standard install, this will be higher with more contributed modules.

Additionally plugin discovery accounts for a further 5-10.

Libraries 3-5.

Proposed solution

Alex Pott is writing a PhpStorage-based cache backend. We would then use that (by default in prod mode) for the following cache bins:

cache_config (for all configuration objects)
cache_discovery (new cache bin, for all plugins and other static discovery stuff)

That should remove 30-40 database hits, per page, replaced with include_once on PHP.

CommentFileSizeAuthor
#58 2228261-58.patch3.01 KBAnonymous (not verified)
#53 2228261-52.patch2.08 KBAnonymous (not verified)
#52 2228261-52-test-only.patch1.3 KBAnonymous (not verified)
#46 interdiff.txt766 byteskim.pepper
#46 2228261-php-cache-46.patch14.95 KBkim.pepper
#44 interdiff.44.txt4.22 KBAnonymous (not verified)
#44 2228261-44.patch14.53 KBAnonymous (not verified)
#42 interdiff.42.txt1.04 KBAnonymous (not verified)
#42 2228261-42.patch14.01 KBAnonymous (not verified)
#40 interdiff.txt2.34 KBwim leers
#40 2228261-40.patch14.13 KBwim leers
#38 2228261-38.patch16.35 KBAnonymous (not verified)
#36 interdiff.php_.txt2.82 KBAnonymous (not verified)
#36 2228261-36.patch14.79 KBAnonymous (not verified)
#33 2228261-33.patch14.75 KBAnonymous (not verified)
#31 2228261-31.patch14.53 KBAnonymous (not verified)
#26 interdiff.txt2.66 KBAnonymous (not verified)
#26 2228261-26.patch32.59 KBAnonymous (not verified)
#13 top_level.png78.01 KBalexpott
#13 frontpage_standard.png132 KBalexpott
#13 2-13-interdiff.txt9.19 KBalexpott
#13 2228261.13.patch13.68 KBalexpott
#2 2228261.2.patch12.05 KBalexpott

Comments

damiankloip’s picture

alexpott’s picture

Status: Active » Needs review
StatusFileSize
new12.05 KB

The patch attached adds a PhpBackend that stores cache keys in a PHP file. Currently this creates one file per bin - a standard install creates a php file of about 21k.

Status: Needs review » Needs work

The last submitted patch, 2: 2228261.2.patch, failed testing.

catch’s picture

catch’s picture

Status: Postponed » Needs work
berdir’s picture

  1. +++ b/core/lib/Drupal/Core/Cache/CacheFactory.php
    @@ -57,7 +57,13 @@ public function get($bin) {
         }
         else {
    -      $service_name = 'cache.backend.database';
    +      // @todo do something more sensible.
    +      if ($bin == 'config') {
    +        $service_name = 'cache.backend.php';
    +      }
    +      else {
    +        $service_name = 'cache.backend.database';
    +      }
         }
         return $this->container->get($service_name)->get($bin);
       }
    

    This is essentially the same problem as the cache backend chain would have if we'd ever use it. We currently don't have a good way to provide configuration for this from outside CacheFactory (this would be fine within, and could simply add configuration for those bin by default to $cache_settings.

  2. +++ b/core/lib/Drupal/Core/Cache/PhpBackend.php
    @@ -0,0 +1,301 @@
    +<?php
    ...
    +
    +  /**
    +   * Writes the cache to PHP storage.
    +   */
    +  protected function writeCache() {
    +    $storage = $this->storage();
    +    $data = Variable::export($this->cache);
    +    $content =<<<EOF
    +<?php
    +\$cache = $data;
    +EOF;
    +    $storage->save($this->bin, $content);
    +  }
    

    We're going to have pretty serious concurrency issues when writing out to a single file. This will need a similarly complex approach as CacheCollector with locking and partial updates and all that stuff.

    It also might not scale for the cache_config use case, because you probably don't want to every single config entity in one include file.

    I guess a file per bin is the saner approach, as suggested in the issue summary. Should still be fairly fast if you don't check the timestamp on every request (opcache in 5.5. is much more flexible than apc I think to configure that)

  3. +++ b/core/lib/Drupal/Core/Cache/PhpBackend.php
    @@ -0,0 +1,301 @@
    + * Definition of Drupal\Core\Cache\PhpBackend.
    

    Contains ;) (SCNR)

  4. +++ b/core/lib/Drupal/Core/Cache/PhpBackend.php
    @@ -0,0 +1,301 @@
    +
    +    // Whether or not Drupal\Component\PhpStorage\PhpStorageInterface::getPath()
    +    // checks for the existence of the file is an implementation of the storage.
    +    if ($filename = $storage->getPath($this->bin)) {
    +      // If this file exists it'll set a variable called $cache.
    +      @include_once $filename;
    +    }
    

    Why not use $storage->load() ? See DrupalKernel::initializeContainer().

    Also not sure if include*_once* is what we want here. Especially in tests, we will write and load that file many times in the same request?

sun’s picture

Did we consider the option to append new and updated entries to an existing file per bin + only flush that out entirely upon dfac()?

I.e., like this:

$cache['foo'] = 'foo';
$cache['bar'] = 'bar';
# cache->set('foo', ...
$cache['foo'] = 'baz';
catch’s picture

Alex has a patch in progress for file per cid instead of file per bin. File per bin isn't going to work for the reasons berdir outlines (except perhaps for a read-only situation).

@sun on read say you have the same cache item appended 50 times, you'd need to build each of these then throw away again. It'd also increase the opcode size considerable.

wim leers’s picture

Issue tags: +Performance, +D8 cacheability

#7: very interesting thought :) But do we know that appends will be significantly faster? And what if the file size grows too large before dfac() is called? Finally: it'll leave less space in the opcode cache for other things.

It feels like that could be an optimization. Let's do the simplest possible thing now, which will already be a significant performance boost.

berdir’s picture

It feels like that could be an optimization. Let's do the simplest possible thing now, which will already be a significant performance boost.

In a way, it is funny that we're considering to inverse the current situation (yml files + database cache to config in db + caches in files) :)

Obviously, for actual sites this will be a nice performance improvements, as this should be very fast for reads. Tests do an insane amount of cache writes, so I'm not so sure there...

The test run above took 36min on #1733, another patch that I checked 30m. Maybe the single file approach will be faster, I'm not sure.

pounard’s picture

Simple note, the PHP code in the file can return array(); this actually allows code such as if ($data = @include $file) {}

Note that include_once might prevent us from re-loading the file content later in the same request if needed while include won't cause any trouble (for exemple if I drop the cache object and create a new instance).

Also the $data = include allows the array inside the file to be anonymous (unnamned) and thus decouples the array name from the file and unties the file from the backend.

Also, may be the keys should be subjected to a consistent hashing mecanism when wrote, so we could dispatch entries in more than one file, but I'm not sure if this actually worthes anything. Considering that those kind of PHP cache should be only used for performance critical path, it should not contain thousands of entries and trying to avoid scaling problems over this seems like premature and useless optimisation.

To avoid concurency problem, a lock wait should be enough, but when the lock switches to wait mode then acquires, the thread should clear its own loaded entries reload the file (hence the include instead of include_once) to be up to date before writing its own local modifications into it so we don't loose the previous thread writes. Note that I didn't looked at how the storage actually works, but I think that it doesn't do such things, so the cache file storage should probably use its own implementation.

In order to gain a few I/O the file could be wrote at PHP shutdown time (such as some caches are actually doing it). This needs to historize all modifications being done during the thread run in order to be able to refresh from the file (if changed by another thread) before writing.

catch’s picture

The installer is currently using the database cache backend as soon as it can. While it's good we have a full environment for most of the installer, all the writing and blowing away of caches there is not doing us any good, so we could go back to the null cache backend for that. Should help both actual installer performance and testing, and likely faster compared to the current situation as well.

alexpott’s picture

Status: Needs work » Needs review
StatusFileSize
new13.68 KB
new9.19 KB
new132 KB
new78.01 KB

The attached patch uses a file per key and var_export. If the $data provided to a cache set is an object it must implement __set_state or an exception is thrown. This patch also suggests that we really ought to have a CacheItem object instead of just returning a \stdClass since then this could implement __set_state and we would not need to futz with array to object conversions.

This patch reduces the number of queries from 105 to 78 and reduces the memory usage due to not using unserialize for each cache item.

CachedStorage::readMultiple

Run 1: Without patch
Run 2: With patch

Top level summary

pounard’s picture

I am not sure this is a really good idea to have this amount of files, you are going to do an awful lot of file I/O operations. Using the checkFile() function as defined, doing file_exists() calls will bypass the OPCode cache and can make it very slow depending on the underlaying FS.

The checkFile() should not even exist, if (false !== ($data = @include $file)) {} would probably do a lot less I/O. Using one file per cache entry is probably shooting yourself in the foot. Storing eveything in one single file was not a bad idea for a first pass. Using a grosser approach for dividing it later if necessary shounds a better idea (per language, using an hashing algorithm, or whatever else...).

My opinion is but I might be wrong is that the concurency problem should be dealt with a custom approach: tracking locally modified keys, put a file lock then reload the file before write, then rewrite it modifying only the locally modified keys sounds like a finer and safe approach.

Anonymous’s picture

#14 is right, and will get much, much worse on a shared filesystem. having said that, 15% is a good result.

i raised this at Szeged, but for the record, i don't like this approach for shared filesystems, but only as a local cache that falls through to a central cache. races between webserver procs doing reads and writes of php code to a shared filesystem... yeah, nope.

i'm going to work on a generic local --> central cache fall through.

+    if (is_object($data) && !method_exists($data, '__set_state')) {
+      throw new \InvalidArgumentException(

i raised that saving arbitrary php values would be hard at Szeged, and now i see that we're not going to support data that can be safely cached now. requiring __set_state is a regression - as of now, you can cache anything that can be safely serialized.

catch’s picture

Using the checkFile() function as defined, doing file_exists() calls will bypass the OPCode cache and can make it very slow depending on the underlaying FS.

You can avoid that completely by using the read-only backend. It means you also need to lock down your site against configuration writes on production, but it's a valid approach.

As beejeebus said we can do something like APCu + memcache with a single consistency (timestamp or similar) read from memcache each request. I'd be very happy to have for example something extending CacheChain that does this in core, I also wouldn't mind an APCu cache backend as long as we ensure tests for it can run on the bots.

However we can't default to APCu + memcache in core, or even APCu + database cache in core. Since this issue is only 1. providing a cache backend 2. changing the default, it's considerably less intrusive than twig or container PHP writing.

All we need to do in this issue is ensure that multiple web-head sites are able to get similar performance with a similar approach (so that we can mark the issues postponed on this one as duplicate), and APCu + memcache should cover that well.

raised that saving arbitrary php values would be hard at Szeged, and now i see that we're not going to support data that can be safely cached now. requiring __set_state is a regression - as of now, you can cache anything that can be safely serialized.

If we only wire this up for the configuration and discovery bins, then the only regression would be that you can't write arbitrary cache items into those bins. It's not such a big regression really. We should probably do a comparison vs. serialize() though to see how much we actually gain from that restriction though.

msonnabaum’s picture

The more I think about this, the more I agree with #15. Apologies for restating some of the previous arguments.

We cannot assume the filesystem is fast on any multi-web setups, so this approach is only helping single server deployments, which is hardly worth doing. We have to assume 1 stat call per file, which is not cheap on shared filesystems. Relying on settings like apc.stat and opcache.revalidate_freq is not a reasonable solution.

Also, I don't think we can say that read-only is a valid approach without having experienced any real-world D8 usage. I suspect this will rarely be acceptable.

If we're going to do this, we should treat the files as a local, inconsistent cache bin, the same way we'd treat apcu. At the start of the request, get a set of timestamps from the consistent cache, use that to know whether or not its safe to use the local cache. If we make the assumption from the start that the files are local, then we can be reasonably confident that this is a performance improvement.

So, I like this issue if it's about adding support for an inconsistent cache API, but I'm not convinced that a PhpStorage-based cache backend alone is worth our time.

catch’s picture

We cannot assume the filesystem is fast on any multi-web setups, so this approach is only helping single server deployments, which is hardly worth doing.

No that's completely worth doing. My local dev is a 'single server deployment', and when I profile on there, the noise from configuration gets makes it harder to see what else is wrong without constantly having to account for them. There's also plenty of sites out there running as single server deployments (or one web head + database + failover) and it's valid for those too.

We know that there's a valid approach to enable local + shared caching for multiple web heads (albeit it's been discussed for years without implementation that I know of *cough* *splutter* *empty sandbox* https://drupal.org/sandbox/catch/1153584), and that solving this is a much more self-contained problem than trying to make things work with pre-loading.

It'd be great to have support for the consistency check in core though, so I've opened #2231595: Add a cache backend that checks an inconsistent cache, then falls back to a consistent cache backend.

I do think there will be sites out there that want a strict staging -> production workflow with production locked down, that's one of the things that people complain about now, and projects like http://drupal.org/project/strongarm partially hacked into place.

msonnabaum’s picture

All I ask is that the multi-web scenario isn't an afterthought, which it was when we decided to compile to container to php storage. That's the hard problem. Solving this for single server isn't.

wim leers’s picture

Issue tags: +Fast By Default

We cannot assume the filesystem is fast on any multi-web setups, so this approach is only helping single server deployments, which is hardly worth doing.

I couldn't disagree more. Many — if not most — Drupal sites run on a single server. We develop on a single server. Let's not delay a vast improvement for many Drupal sites.

AFAICT Drupal core cannot solve the multi-web scenario anyway: it depends on the particular multi-web scenario what the best solution is, and especially on the available infrastructure (memcached, redis…).

In principle I agree with "let's not make multi-web an afterthought". But in practice, that pretty much implies we cannot make progress for single-web either. Let us at least move forward with single-web, so that we can profile more effectively in the single-web case, and thus improve performance for both single- and multi-web cases.


This issue is incredibly important for a "fast by default" Drupal 8. If you run a multi-web website, then you're an expert already, since you choose the components in your multi-web infrastructure, therefore you're also in the best position to judge how to make it work performantly. In the single-web case, Drupal can and should make things fast out of the box for you.

catch’s picture

Yeah also the idea that this isn't a hard problem to solve for single servers seems a bit optimistic, given that I opened #1187726: Add caching for configuration / rework config object loading (Was: Memory usage and i/o from config objects) about i/o issues with CMI in 2011 before the original CMI patch was committed, and we still have exactly the same issues as we did then.

It also took several iterations and approaches to pre-loading before we realised (especially in a discussion with Wim and beejeebus last week), that pre-loading is a double edged sword:

- if you have opt-in pre-loading, it's going to require careful analysis on a per-module/install profile/site basis to get it right.

- if you preload by route/roles or similar with a cache of config IDs on the page + multiple load, that cache item will be populated on cache misses. Since we have better high-level (i.e. render) caching in 8.x, when all those caches are warm, most of the CMI objects that were pre-loaded (field instances, views etc.) won't be needed, so the page may actually end up slower rather than faster. This also applies to a lesser extent to opt-in.

Realising that led to abandoning pre-loading for configuration objects at least for now, and opening this issue instead. The issue that's being fixed here is not the local cache backend, but more or less interminable config/plugin caching i/o.

I opened #2231595: Add a cache backend that checks an inconsistent cache, then falls back to a consistent cache backend but have no intention of blocking this issue on that one - the advantage of getting this in sooner rather than later is we have a better default situation from which to assess the other (very, very many) performance issues in core at the moment.

Anonymous’s picture

just to be clear about preloading - the thing that catch convinced me of was that opt-in would lead to many sites just not really doing preloading.

if you know what you're doing, an opt-in preloading approach will provide big wins, and we should make sure it's easy to do in contrib.

also - what about APC/APCu for the fast cache option?

moshe weitzman’s picture

also - what about APC/APCu for the fast cache option?

Right. This solution has a limited audience IMO. Anyone who has APC on 5.4 can just use that as cache backend and it will be as fast as this, I think. Same for anyone with APCu on 5.5. And isn't APCu already nearly a necessity for good performance considering thats where settings.php suggests you put your class_loader.

I'm OK with going ahead here, but its a small win AFAICT.

catch’s picture

And isn't APCu already nearly a necessity for good performance considering thats where settings.php suggests you put your class_loader.

It is at the moment, but that's a crappy requirement considering how horrible the default classloader is, and how few people bother with APCu (or APC user caching) normally. I don't think I've ever seen a 7.x site running it. That's likely to change for 8.x, but it's not a good thing being in that position - this is just the least-worst we can do.

For the classloader there is #2023325: Add interface for classloader so it can be cleanly swapped open already. For a while I've been considering a CacheCollector-alike version of the classloader, which could then write to either a single APCu entry or a single PhpStorage file - would be marginally faster than the individual apc_get() I think and more flexible than the compiled classloader which is what Composer recommends.

If we can only do that option for APCu, then it leaves us with crappy default performance in core. If we can ship with a universally available fast local cache as well as supporting APCu in other cases, then it makes it a lot easier to fix the actual caching issues in core too, in a way that works equally well for single server (which as Wim points out is a vast number of cases), and multiple server (which will only have to change a cache backend, not re-implement entire core subsystems) configurations.

When talking about a small win, please read the profiling very carefully, then consider whether you're able to account for that significant and unnecessary overhead every time you run xhprof against 8.x. There is a ridiculous amount of profiling noise in core due to systemic issues like this, as well as myriad silly ones, and they mask each other in each direction.

Anonymous’s picture

started playing with a 'php cache' for site config with this dumb little rig:

https://gist.github.com/beejeebus/10007540

to read all of the config on a default standard install of drupal, i see output like this:

Array
(
    [serialize] => 3.6227703094482
    [var_export] => 1.8866062164307
    [factory] => 43.782234191895
    [apc] => 3.4973621368408
)
Anonymous’s picture

StatusFileSize
new32.59 KB
new2.66 KB

ok, here's a version that uses serialize() instead of var_export().

this keeps the current behaviour, and means we can remove the requirement to implement __set_state().

Status: Needs review » Needs work

The last submitted patch, 26: 2228261-26.patch, failed testing.

sun’s picture

Just for completeness: Drupal core happens to ship with some prior art from Doctrine already:

PhpFileCache
FilesystemCache

pounard’s picture

Nice catch, this might worth the shot to use, depending on how well it fits to the current API.

Anonymous’s picture

Anonymous’s picture

Status: Needs work » Needs review
StatusFileSize
new14.53 KB

reroll, the PhpStorageFactory moved. also make sure we have an object in deleteTags().

Status: Needs review » Needs work

The last submitted patch, 31: 2228261-31.patch, failed testing.

Anonymous’s picture

StatusFileSize
new14.75 KB

add a setMultiple() implementations.

Anonymous’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 33: 2228261-33.patch, failed testing.

Anonymous’s picture

Status: Needs work » Needs review
StatusFileSize
new14.79 KB
new2.82 KB

fixed signature on setMultiple(), a bit of cleanup.

catch’s picture

+++ b/core/lib/Drupal/Core/Cache/CacheFactory.php
@@ -60,7 +60,13 @@ public function get($bin) {
+      // @todo do something more sensible.

I think we should remove this from the patch and wire this up separately in another issue. Also it should be used for cache_discovery too now.

The tag deletion looks pretty inefficient. Apart from storing tag deletions in another PHP file, I don't see much of an alternative though. There's three ways I see this being used in production:

1. Read only - won't delete tags.

2. Chained backend - deleting tags will mean all local items are invalidated anyway

3. Single server/not chained, so tags would be useful there.

I wonder whether shouldn't just clear the directory and not bother with tags? Would be a lot better for the #2 case, although worse for #3. Could just be a follow-up though anyway.

Not much to complain about otherwise.

Anonymous’s picture

StatusFileSize
new16.35 KB

discussed this with catch in IRC.

- lets put in a correct but slow tags implementation and iterate on it

- lets punt on the cache factory changes to here: #2248767: Use fast, local cache back-end (APCu, if available) for low-write caches (bootstrap, discovery, and config)

only changes from #36 are to take out the CacheFactory bits:

diff --git a/core/lib/Drupal/Core/Cache/CacheFactory.php b/core/lib/Drupal/Core/Cache/CacheFactory.php
index 8026c84..c20979f 100644
--- a/core/lib/Drupal/Core/Cache/CacheFactory.php
+++ b/core/lib/Drupal/Core/Cache/CacheFactory.php
@@ -60,13 +60,7 @@ public function get($bin) {
       $service_name = $cache_settings['default'];
     }
     else {
-      // @todo do something more sensible.
-      if ($bin == 'config') {
-        $service_name = 'cache.backend.php';
-      }
-      else {
-        $service_name = 'cache.backend.database';
-      }
+      $service_name = 'cache.backend.database';
     }
     return $this->container->get($service_name)->get($bin);
   }

Status: Needs review » Needs work

The last submitted patch, 38: 2228261-38.patch, failed testing.

wim leers’s picture

Status: Needs work » Needs review
Issue tags: +sprint
StatusFileSize
new14.13 KB
new2.34 KB

The test failure was legitimate, core/tests/Drupal/Tests/Core/Cache/InconsistentBackendTest.php was accidentally included from the #2231595: Add a cache backend that checks an inconsistent cache, then falls back to a consistent cache backend patch.

Straight reroll that simply removes that accidentally added file.

(That also explains why #38 was larger than #36 despite only removing code.)

damiankloip’s picture

  1. +++ b/core/lib/Drupal/Component/PhpStorage/FileReadOnlyStorage.php
    @@ -83,4 +83,30 @@ function writeable() {
    +  public function listAll() {
    
    +++ b/core/lib/Drupal/Component/PhpStorage/FileStorage.php
    @@ -248,4 +255,23 @@ protected function unlink($path) {
    +  public function listAll() {
    

    It's a shame we have to duplicate this code.

  2. +++ b/core/lib/Drupal/Core/Cache/PhpBackend.php
    @@ -0,0 +1,284 @@
    +   * Constructs a MemoryBackend object.
    

    PhpBackend

  3. +++ b/core/lib/Drupal/Core/Cache/PhpBackend.php
    @@ -0,0 +1,284 @@
    +      if ($this->bin != 'cache') {
    +        $store = 'cache_' . $this->bin;
    +      }
    +      else {
    +        $store = 'cache';
    +      }
    

    We don't need this logic anymore, now we don't have a 'cache' bin.

Anonymous’s picture

StatusFileSize
new14.01 KB
new1.04 KB

addressed the points in #40 accept for the angst in 1. ;-)

wim leers’s picture

Status: Needs review » Needs work

Trying to find all nitpicks so we can finally RTBC this! :)

  1. +++ b/core/lib/Drupal/Component/PhpStorage/PhpStorageInterface.php
    @@ -78,4 +78,25 @@ public function delete($name);
    +   *   The full file path for the provided name. Return FALSE if the
    +   *   implementation needs to prevent access to the file.
    +   */
    +  public function getPath($name);
    

    Wouldn't getAbsolutePath() be a better name? Or getFullPath()?

  2. +++ b/core/lib/Drupal/Core/Cache/PhpBackend.php
    @@ -0,0 +1,278 @@
    + * Definition of Drupal\Core\Cache\PhpBackend.
    

    s/Definition of Drupal/Contains \Drupal/

  3. +++ b/core/lib/Drupal/Core/Cache/PhpBackend.php
    @@ -0,0 +1,278 @@
    + * Defines a PHP cache implementation.
    

    Maybe mention OpCache?

  4. +++ b/core/lib/Drupal/Core/Cache/PhpBackend.php
    @@ -0,0 +1,278 @@
    +  protected function prepareItem($cache, $allow_invalid) {
    

    Docs for $allow_invalid are missing.

  5. +++ b/core/modules/system/lib/Drupal/system/Tests/Cache/GenericCacheBackendUnitTestBase.php
    @@ -136,10 +136,11 @@ public function testSetGet() {
    -    $backend->set('test1', 7);
    +    $with_backslash = array('foo' => '\Drupal\foo\Bar');
    +    $backend->set('test1', $with_backslash);
    ...
    -    $this->assertIdentical(7, $cached->data);
    +    $this->assertIdentical($with_backslash, $cached->data);
    

    Sounds like these tests are added specifically for the PHPStorage backend. Let's not put them in the generic cache backend test?

  6. +++ b/core/modules/system/lib/Drupal/system/Tests/Cache/PhpBackendUnitTest.php
    @@ -0,0 +1,35 @@
    + * Definition of Drupal\system\Tests\Cache\PhpBackendUnitTest.
    

    s/Definition Drupal/Contains \Drupal/

Anonymous’s picture

Status: Needs work » Needs review
StatusFileSize
new14.53 KB
new4.22 KB

attached patch address everything in #43, except the test changes.

i don't see any reason to not make sure that all backends work with backslashes.

Status: Needs review » Needs work

The last submitted patch, 44: 2228261-44.patch, failed testing.

kim.pepper’s picture

Status: Needs work » Needs review
StatusFileSize
new14.95 KB
new766 bytes

Fixes incorrect 'protected' access on a public interface method.

wim leers’s picture

Status: Needs review » Reviewed & tested by the community

#44: fair.

I can't find any other things to nitpick.

  • Commit 2ea43de on 8.x by catch:
    Issue #2228261 by beejeebus, kim.pepper, alexpott, Wim Leers: Add a...
catch’s picture

Status: Reviewed & tested by the community » Fixed

OK this looks great. The cache deletion implementation we might want to look at in time, but what we have here passes tests, and this is only supposed to be used for high-read/low-write bins anyway.

Committed/pushed to 8.x, thanks!

chx’s picture

Status: Fixed » Active

php -r 'print serialize("\nEOF\nyou are screwed");'

s:20:"
EOF
you are screwed"

Escape, my friends, escape.

Edit: str_replace('\\', '\\\\', serialize($item)); this is not helping; serialize doesnt put a literal \n in there.

Anonymous’s picture

chx - can you help me write a test case that illustrates the problem? and write some comments explaining it better?

Anonymous’s picture

Status: Active » Needs review
StatusFileSize
new1.3 KB

thanks chx! ok, here's a patch with a test that illustrates the problem.

Anonymous’s picture

StatusFileSize
new2.08 KB

and here's a patch with the test and fix.

The last submitted patch, 52: 2228261-52-test-only.patch, failed testing.

chx’s picture

OK, so the reason for not using an escape and instead finding a usable heredoc delimiter is because we can't unescape upon reading. I understand now (although a comment would be helpful), and I am fine with the idea but the random and the guard is weird. Why not:

    $suffix = '';
    do {
      $EOF = 'EOF' . $suffix++;
    } while (preg_match('/^' . $EOF . ';?$/m', $data));

There are a couple bugs caught by the preg_match: EOF and EOF; both are deadly. You don't reaaaaally know the line endings (Mac, Windows, Unix, fun!) so let PCRE deal with that crap. This loop will produce EOF, EOF1, EOF2 etc and surely will terminate (as long as $data is finite but it is).

Anonymous’s picture

Status: Needs review » Needs work

ok, i don't have strong opinion about #53 vs #55.

i'll reroll with #55 and some somments.

damiankloip’s picture

This already seems like it should be a follow up in a separate issue tbh.

Anonymous’s picture

Status: Needs work » Needs review
StatusFileSize
new3.01 KB

here we go.

tstoeckler’s picture

+++ b/core/lib/Drupal/Core/Cache/PhpBackend.php
@@ -263,11 +263,22 @@ public function removeBin() {
+    $suffix = '';
...
+    } while ($suffix++ < 1000 && ($unsafe_eof_in_data = preg_match('/^' . $EOF . ';?$/m', $data)));

It might be me, but ''++ freaks me out. Is there a way around that? I.e. can we initialize $suffix as 0 or something?

chx’s picture

We could but then the default EOF wouldn't be EOF but EOF0. This is easy and safe. I know my way around PHP ++ . This is not the character increment which leads to $a='2d9';$a++;$a++ which is my all time favorite PHP WTF. This is simply casting the empty string to an int which is a 0 then adding 1.

tstoeckler’s picture

OK. I personally would favor having EOF0 then, but I don't really mind either. Just wanted that reassurance. :-) Thanks!

wim leers’s picture

Status: Needs review » Reviewed & tested by the community
catch’s picture

Status: Reviewed & tested by the community » Needs work

Sorry just committed #2256877: Remove the API function to check if a cache bin is empty which was also critical then realised this will need a quick re-roll to remove the isEmpty() method.

wim leers’s picture

Status: Needs work » Reviewed & tested by the community

#63: the patch in #58 applies cleanly on HEAD?

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 58: 2228261-58.patch, failed testing.

  • Commit f9f3695 on 8.x by catch:
    Issue #2228261 by beejeebus, kim.pepper, alexpott, Wim Leers | catch:...
catch’s picture

Status: Needs work » Fixed

Arggh I'd forgotten this was just a follow-up.

Committed/pushed to 8.x, thanks!

wim leers’s picture

Issue tags: -sprint
olli’s picture

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.