Issue #2224847 by pwolanin, damiankloip, danblack, Berdir: Fixed Automatically shorten cid's in Cache\DatabaseBackend and PhpBackend.

8.0.x
Nathaniel Catchpole 2014-07-02 20:42:16 +01:00
parent 8ca26dcf34
commit 4fb1180802
3 changed files with 119 additions and 27 deletions

View File

@ -7,6 +7,7 @@
namespace Drupal\Core\Cache;
use Drupal\Component\Utility\Crypt;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\SchemaObjectExistsException;
@ -62,6 +63,10 @@ class DatabaseBackend implements CacheBackendInterface {
* Implements Drupal\Core\Cache\CacheBackendInterface::getMultiple().
*/
public function getMultiple(&$cids, $allow_invalid = FALSE) {
$cid_mapping = array();
foreach ($cids as $cid) {
$cid_mapping[$this->normalizeCid($cid)] = $cid;
}
// When serving cached pages, the overhead of using ::select() was found
// to add around 30% overhead to the request. Since $this->bin is a
// variable, this means the call to ::query() here uses a concatenated
@ -71,13 +76,15 @@ class DatabaseBackend implements CacheBackendInterface {
// ::select() is a much smaller proportion of the request.
$result = array();
try {
$result = $this->connection->query('SELECT cid, data, created, expire, serialized, tags, checksum_invalidations, checksum_deletions FROM {' . $this->connection->escapeTable($this->bin) . '} WHERE cid IN (:cids)', array(':cids' => $cids));
$result = $this->connection->query('SELECT cid, data, created, expire, serialized, tags, checksum_invalidations, checksum_deletions FROM {' . $this->connection->escapeTable($this->bin) . '} WHERE cid IN (:cids)', array(':cids' => array_keys($cid_mapping)));
}
catch (\Exception $e) {
// Nothing to do.
}
$cache = array();
foreach ($result as $item) {
// Map the cache ID back to the original.
$item->cid = $cid_mapping[$item->cid];
$item = $this->prepareItem($item, $allow_invalid);
if ($item) {
$cache[$item->cid] = $item;
@ -196,7 +203,7 @@ class DatabaseBackend implements CacheBackendInterface {
}
$this->connection->merge($this->bin)
->key('cid', $cid)
->key('cid', $this->normalizeCid($cid))
->fields($fields)
->execute();
}
@ -284,14 +291,14 @@ class DatabaseBackend implements CacheBackendInterface {
* Implements Drupal\Core\Cache\CacheBackendInterface::deleteMultiple().
*/
public function deleteMultiple(array $cids) {
$cids = array_values(array_map(array($this, 'normalizeCid'), $cids));
try {
// Delete in chunks when a large array is passed.
do {
foreach (array_chunk($cids, 1000) as $cids_chunk) {
$this->connection->delete($this->bin)
->condition('cid', array_splice($cids, 0, 1000), 'IN')
->condition('cid', $cids_chunk, 'IN')
->execute();
}
while (count($cids));
}
catch (\Exception $e) {
// Create the cache table, which will be empty. This fixes cases during
@ -357,15 +364,15 @@ class DatabaseBackend implements CacheBackendInterface {
* Implements Drupal\Core\Cache\CacheBackendInterface::invalideMultiple().
*/
public function invalidateMultiple(array $cids) {
$cids = array_values(array_map(array($this, 'normalizeCid'), $cids));
try {
// Update in chunks when a large array is passed.
do {
foreach (array_chunk($cids, 1000) as $cids_chunk) {
$this->connection->update($this->bin)
->fields(array('expire' => REQUEST_TIME - 1))
->condition('cid', array_splice($cids, 0, 1000), 'IN')
->condition('cid', $cids_chunk, 'IN')
->execute();
}
while (count($cids));
}
catch (\Exception $e) {
$this->catchException($e);
@ -548,6 +555,26 @@ class DatabaseBackend implements CacheBackendInterface {
}
}
/**
* Ensures that cache IDs have a maximum length of 255 characters.
*
* @param string $cid
* The passed in cache ID.
*
* @return string
* A cache ID that is at most 255 characters long.
*/
protected function normalizeCid($cid) {
// Nothing to do if the ID length is 255 characters or less.
if (strlen($cid) <= 255) {
return $cid;
}
// Return a string that uses as much as possible of the original cache ID
// with the hash appended.
$hash = Crypt::hashBase64($cid);
return substr($cid, 0, 255 - strlen($hash)) . $hash;
}
/**
* Defines the schema for the cache bin and cache_tags table.
*/

View File

@ -8,7 +8,7 @@
namespace Drupal\Core\Cache;
use Drupal\Core\PhpStorage\PhpStorageFactory;
use Drupal\Component\Utility\Variable;
use Drupal\Component\Utility\Crypt;
/**
* Defines a PHP cache implementation.
@ -49,7 +49,22 @@ class PhpBackend implements CacheBackendInterface {
* {@inheritdoc}
*/
public function get($cid, $allow_invalid = FALSE) {
if ($file = $this->storage()->getFullPath($cid)) {
return $this->getByHash($this->normalizeCid($cid), $allow_invalid);
}
/**
* Fetch a cache item using a hashed cache ID.
*
* @param string $cidhash
* The hashed version of the original cache ID after being normalized.
* @param bool $allow_invalid
* (optional) If TRUE, a cache item may be returned even if it is expired or
* has been invalidated.
*
* @return bool|mixed
*/
protected function getByHash($cidhash, $allow_invalid = FALSE) {
if ($file = $this->storage()->getFullPath($cidhash)) {
$cache = @include $file;
}
if (isset($cache)) {
@ -125,14 +140,14 @@ class PhpBackend implements CacheBackendInterface {
'expire' => $expire,
);
$item->tags = $this->flattenTags($tags);
$this->writeItem($cid, $item);
$this->writeItem($this->normalizeCid($cid), $item);
}
/**
* {@inheritdoc}
*/
public function delete($cid) {
$this->storage()->delete($cid);
$this->storage()->delete($this->normalizeCid($cid));
}
/**
@ -149,10 +164,10 @@ class PhpBackend implements CacheBackendInterface {
*/
public function deleteTags(array $tags) {
$flat_tags = $this->flattenTags($tags);
foreach ($this->storage()->listAll() as $cid) {
$item = $this->get($cid);
foreach ($this->storage()->listAll() as $cidhash) {
$item = $this->getByHash($cidhash);
if (is_object($item) && array_intersect($flat_tags, $item->tags)) {
$this->delete($cid);
$this->delete($item->cid);
}
}
}
@ -168,9 +183,19 @@ class PhpBackend implements CacheBackendInterface {
* {@inheritdoc}
*/
public function invalidate($cid) {
if ($item = $this->get($cid)) {
$this->invalidatebyHash($this->normalizeCid($cid));
}
/**
* Invalidate one cache item.
*
* @param string $cidhash
* The hashed version of the original cache ID after being normalized.
*/
protected function invalidatebyHash($cidhash) {
if ($item = $this->getByHash($cidhash)) {
$item->expire = REQUEST_TIME - 1;
$this->writeItem($cid, $item);
$this->writeItem($cidhash, $item);
}
}
@ -188,10 +213,10 @@ class PhpBackend implements CacheBackendInterface {
*/
public function invalidateTags(array $tags) {
$flat_tags = $this->flattenTags($tags);
foreach ($this->storage()->listAll() as $cid) {
$item = $this->get($cid);
foreach ($this->storage()->listAll() as $cidhash) {
$item = $this->getByHash($cidhash);
if ($item && array_intersect($flat_tags, $item->tags)) {
$this->invalidate($cid);
$this->invalidate($item->cid);
}
}
}
@ -200,7 +225,9 @@ class PhpBackend implements CacheBackendInterface {
* {@inheritdoc}
*/
public function invalidateAll() {
$this->invalidateMultiple($this->storage()->listAll());
foreach($this->storage()->listAll() as $cidhash) {
$this->invalidatebyHash($cidhash);
}
}
/**
@ -248,20 +275,20 @@ class PhpBackend implements CacheBackendInterface {
/**
* Writes a cache item to PhpStorage.
*
* @param string $cid
* The cache ID of the data to store.
* @param string $cidhash
* The hashed version of the original cache ID after being normalized.
* @param \stdClass $item
* The cache item to store.
*/
protected function writeItem($cid, \stdClass $item) {
protected function writeItem($cidhash, \stdClass $item) {
$content = '<?php return unserialize(' . var_export(serialize($item), TRUE) . ');';
$this->storage()->save($cid, $content);
$this->storage()->save($cidhash, $content);
}
/**
* Gets the PHP code storage object to use.
*
* @return \Drupal\Core\PhpStorage\PhpStorageInterface
* @return \Drupal\Component\PhpStorage\PhpStorageInterface
*/
protected function storage() {
if (!isset($this->storage)) {
@ -270,4 +297,17 @@ class PhpBackend implements CacheBackendInterface {
return $this->storage;
}
/**
* Ensures a normalized cache ID.
*
* @param string $cid
* The passed in cache ID.
*
* @return string
* A normalized cache ID.
*/
protected function normalizeCid($cid) {
return Crypt::hashBase64($cid);
}
}

View File

@ -2,7 +2,7 @@
/**
* @file
* Definition of Drupal\system\Tests\Cache\GenericCacheBackendUnitTestBase.
* Contains \Drupal\system\Tests\Cache\GenericCacheBackendUnitTestBase.
*/
namespace Drupal\system\Tests\Cache;
@ -207,6 +207,11 @@ abstract class GenericCacheBackendUnitTestBase extends DrupalUnitTestBase {
$cached->data->this_should_not_be_in_the_cache = TRUE;
$fresh_cached = $backend->get('test7');
$this->assertFalse(isset($fresh_cached->data->this_should_not_be_in_the_cache));
// Check with a long key.
$cid = str_repeat('a', 300);
$backend->set($cid, 'test');
$this->assertEqual('test', $backend->get($cid)->data);
}
/**
@ -230,6 +235,11 @@ abstract class GenericCacheBackendUnitTestBase extends DrupalUnitTestBase {
$backend->delete('test2');
$this->assertIdentical(FALSE, $backend->get('test2'), "Backend does not contain data for cache id test2 after deletion.");
$long_cid = str_repeat('a', 300);
$backend->set($long_cid, 'test');
$backend->delete($long_cid);
$this->assertIdentical(FALSE, $backend->get($long_cid), "Backend does not contain data for long cache id after deletion.");
}
/**
@ -267,6 +277,7 @@ abstract class GenericCacheBackendUnitTestBase extends DrupalUnitTestBase {
$backend = $this->getCacheBackend();
// Set numerous testing keys.
$long_cid = str_repeat('a', 300);
$backend->set('test1', 1);
$backend->set('test2', 3);
$backend->set('test3', 5);
@ -274,6 +285,7 @@ abstract class GenericCacheBackendUnitTestBase extends DrupalUnitTestBase {
$backend->set('test5', 11);
$backend->set('test6', 13);
$backend->set('test7', 17);
$backend->set($long_cid, 300);
// Mismatch order for harder testing.
$reference = array(
@ -341,6 +353,12 @@ abstract class GenericCacheBackendUnitTestBase extends DrupalUnitTestBase {
$this->assertFalse(in_array('test2', $cids), "Existing cache id test2 is not in cids array.");
$this->assertFalse(in_array('test7', $cids), "Existing cache id test7 is not in cids array.");
$this->assertFalse(in_array('test19', $cids), "Added cache id test19 is not in cids array.");
// Test with a long $cid and non-numeric array key.
$cids = array('key:key' => $long_cid);
$return = $backend->getMultiple($cids);
$this->assertEqual(300, $return[$long_cid]->data);
$this->assertTrue(empty($cids));
}
/**
@ -420,6 +438,9 @@ abstract class GenericCacheBackendUnitTestBase extends DrupalUnitTestBase {
// Test if that expected keys do not exist.
$this->assertIdentical(FALSE, $backend->get('test19'), "Cache id test19 does not exist.");
$this->assertIdentical(FALSE, $backend->get('test21'), "Cache id test21 does not exist.");
// Calling deleteMultiple() with an empty array should not cause an error.
$this->assertFalse($backend->deleteMultiple(array()));
}
/**
@ -528,6 +549,10 @@ abstract class GenericCacheBackendUnitTestBase extends DrupalUnitTestBase {
$cids = $reference;
$ret = $backend->getMultiple($cids, TRUE);
$this->assertEqual(count($ret), 4, 'Four items returned.');
// Calling invalidateMultiple() with an empty array should not cause an
// error.
$this->assertFalse($backend->invalidateMultiple(array()));
}
/**