Skip to content
Snippets Groups Projects
Commit 8794105d authored by Alberto Paderno's avatar Alberto Paderno
Browse files

Issue #3468201: Clearing cache items whose IDs start with a given string is not possible

parent 1cf410e6
No related branches found
No related tags found
1 merge request!63Issue #3468201: Clearing cache items whose IDs start with a given string is not possible
Pipeline #255255 passed
......@@ -28,5 +28,5 @@ include:
# OPT_IN_TEST_NEXT_MAJOR: '1'
# _CURL_TEMPLATES_REF: 'main'
variables:
_PHPUNIT_CONCURRENT: 1
_CONCURRENCY_THREADS: 4
_PHPUNIT_CONCURRENT: '1'
_CONCURRENCY_THREADS: '6'
......@@ -15,7 +15,7 @@
*
* @param array $patterns
* An associative array of patterns and their corresponding old patterns. Old
* patterns are replaced by the new patterns, in the persistent variable.
* patterns are replaced by the new patterns.
*/
function _apc_delete_apcu_keys($patterns) {
$apcu_enabled = extension_loaded('apcu') && apcu_enabled();
......@@ -125,10 +125,10 @@ function apc_update_7101() {
}
/**
* Delete the APCu keys starting with apc_cache_ as they are no longer used.
* Delete the APCu keys no longer used for the cache.
*/
function apc_update_7102() {
_apc_delete_apcu_keys(array('apc_cache_' => 'apc_cache_'));
_apc_delete_apcu_keys(array('apc_cache_' => ''));
}
/**
......@@ -139,17 +139,24 @@ function apc_update_7103() {
}
/**
* Delete the APCu keys starting with apc_cache:: to create the new keys.
* Delete the APCu keys previously used for the cache.
*/
function apc_update_7104() {
_apc_delete_apcu_keys(array('apc_cache::' => ''));
}
/**
* Delete the APCu keys starting with apc_cache:: to create the new keys.
* Delete the APCu keys previously used for the cache.
*/
function apc_update_7105() {
_apc_delete_apcu_keys('apc_cache::[a-zA-Z0-9-_]{20,}::[a-zA-Z0-9-_]{20,}');
_apc_delete_apcu_keys(array('apc_cache::[a-zA-Z0-9-_]{20,}::[a-zA-Z0-9-_]{20,}' => ''));
}
/**
* Delete the cache items stored on APCu to avoid duplicate items.
*/
function apc_update_7106() {
_apc_delete_apcu_keys(array('apc_cache::' => 'apc_cache::[a-zA-Z0-9-_]{20,}::[a-zA-Z0-9-_]{20,}'));
}
/**
......@@ -159,8 +166,10 @@ function apc_uninstall() {
$apcu_enabled = extension_loaded('apcu') && apcu_enabled();
if ($apcu_enabled && class_exists('APCUIterator')) {
$iterator = new APCUIterator('/^apc_cache::[a-zA-Z0-9-_]{40,46}::[a-zA-Z0-9-_]{40,46}/', APC_ITER_KEY);
$iterator = new APCUIterator('/^apc_cache::[a-zA-Z0-9-_]{40,46}::[0-9a-f]{2,}/', APC_ITER_KEY);
apcu_delete($iterator);
$iterator = new APCUIterator('/^apc_cache::()/', APC_ITER_KEY);
}
// Deleted all the persistent variables used by the module.
......
......@@ -851,13 +851,13 @@ class ApcCacheClearTestCase extends ApcCacheBaseTestCase {
if ($this->assertTrue(!empty($stored), $message)) {
$cleared_cid = array_pop($stored);
$options = array('message' => '@bin/@cache_id has been removed by cache_clear_all().');
$options = array('message' => '@bin/@cache_id was removed by cache_clear_all().');
cache_clear_all($cleared_cid, $bin);
$this->assertNoCacheItem($bin, $cleared_cid, $options);
$options = array('message' => '@bin/@cache_id has been not been removed by cache_clear_all().');
$options = array('message' => '@bin/@cache_id was not removed by cache_clear_all().');
foreach ($stored as $cid) {
$this->assertCacheItem($bin, $cid, $options);
......@@ -891,25 +891,23 @@ class ApcCacheClearTestCase extends ApcCacheBaseTestCase {
$message = 'New items have been stored in the cache.';
if ($this->assertTrue(!empty($stored), $message)) {
$options = array('message' => '@bin/@cache_id has been not been removed by cache_clear_all().');
$options = array('message' => '@bin/@cache_id was not removed by cache_clear_all().');
cache_clear_all('*', $bin);
// Since cache_clear_all() has not been called setting its last
// parameter to TRUE, '*' is the literal value for the cache ID. No
// cache item has been stored with that cache ID, so they all must still
// exist.
// Since cache_clear_all() was not called with TRUE as last parameter,
// '*' is the literal value for the cache ID. No cache item is stored
// with that cache ID, so all the cache items still exist.
foreach ($stored as $cid) {
$this->assertCacheItem($bin, $cid, $options);
}
cache_clear_all('*', $bin, TRUE);
$options = array('message' => '@bin/@cache_id has been removed by cache_clear_all().');
$options = array('message' => '@bin/@cache_id was removed by cache_clear_all().');
// Since cache_clear_all() has been called setting its last
// parameter to TRUE, '*' matches all the cache IDs stored for the bin.
// All the existing cache IDs are cleared.
// Since cache_clear_all() was called with TRUE as last parameter, all
// the cache items were removed.
foreach ($stored as $cid) {
$this->assertNoCacheItem($bin, $cid, $options);
}
......@@ -921,58 +919,52 @@ class ApcCacheClearTestCase extends ApcCacheBaseTestCase {
* Tests cache_clear_all() passing TRUE as last parameter.
*/
public function testClearWildcard() {
$cache_id_prefixes = array('clear_first_time_by_wildcard_', 'clear_second_time_by_wildcard_');
$stored = array();
if ($this->skipTest) {
return;
}
// This test is temporarily disabled until an issue in the cache back-end
// class has not been fixed.
if (!$this->pass('This test is temporarily disabled.')) {
if ($this->assertApcuEmpty() && $this->assertCacheBinsOnApcu()) {
$bin = $this->getCacheBins()[0];
if ($this->assertApcuEmpty() && $this->assertCacheBinsOnApcu()) {
$bin = $this->getCacheBins()[0];
foreach (array('clear_by_wildcard_', 'no_clear_by_wildcard_') as $prefix) {
foreach ($this->storageData() as $id => $data) {
$cid = "$prefix$id";
foreach ($cache_id_prefixes as $prefix) {
foreach ($this->storageData() as $id => $data) {
$cid = "$prefix$id";
cache_set($cid, $data['value'], $bin);
cache_set($cid, $data['value'], $bin);
if ($this->assertCacheItem($bin, $cid, array('value' => $data['value']))) {
$stored[] = $cid;
}
if ($this->assertCacheItem($bin, $cid, array('value' => $data['value']))) {
$stored[] = $cid;
}
}
}
$message = 'New items have been stored in the cache.';
$message = 'New items were stored in the cache.';
if ($this->assertTrue(!empty($stored), $message)) {
cache_clear_all('clear_by_wildcard_', $bin);
if ($this->assertTrue(!empty($stored), $message)) {
cache_clear_all($cache_id_prefixes[0], $bin, TRUE);
foreach ($stored as $cid) {
// Check the cache ID starts with 'clear_by_wildcard_' and has been
// removed.
if (strpos($cid, 'clear_by_wildcard_') === 0) {
$options = array('message' => '@bin/@cache_id has been removed by cache_clear_all().');
foreach ($stored as $cid) {
if (strpos($cid, $cache_id_prefixes[0]) === 0) {
$options = array('message' => '@bin/@cache_id was removed by cache_clear_all().');
$this->assertNoCacheItem($bin, $cid, $options);
}
else {
$options = array('message' => '@bin/@cache_id has not been removed by cache_clear_all().');
$this->assertNoCacheItem($bin, $cid, $options);
}
else {
$options = array('message' => '@bin/@cache_id was not removed by cache_clear_all().');
$this->assertCacheItem($bin, $cid, $options);
}
$this->assertCacheItem($bin, $cid, $options);
}
}
$options = array('message' => '@bin/@cache_id has been removed by cache_clear_all().');
$options = array('message' => '@bin/@cache_id was removed by cache_clear_all().');
// Since cache_clear_all() has been called setting its last
// parameter to TRUE, '*' matches all the cache IDs stored for the
// bin. All the existing cache IDs are cleared.
foreach ($stored as $cid) {
$this->assertNoCacheItem($bin, $cid, $options);
}
cache_clear_all($cache_id_prefixes[1], $bin, TRUE);
foreach ($stored as $cid) {
$this->assertNoCacheItem($bin, $cid, $options);
}
}
}
......
......@@ -51,48 +51,20 @@ class DrupalApcCache implements DrupalCacheInterface {
protected $modulePrefix = 'apc_cache';
/**
* Converts binary data to a hexadecimal representation.
* Converts a binary string to a hexadecimal representation.
*
* @param mixed $data
* The binary data to convert. If it is an empty string, a random 4-byte
* string is generated.
* @param string $data
* The binary string to convert.
*
* @return string
* The hexadecimal representation of the binary data.
* The hexadecimal representation of the binary string.
*/
protected function binaryToHex($data) {
if (is_array($data)) {
if (empty($data)) {
return unpack("H*", "\2\5\6")[1];
}
return unpack("H*", "\0\4\6" . serialize($data))[1];
}
elseif (is_bool($data)) {
return $data ? "\3\0\5\1" : "\3\1\5\0";
}
elseif (is_string($data)) {
if ($data === '') {
// If an empty string is an acceptable value for the caller, return an
// empty string.
return '';
}
return unpack("H*", "\0\0\4$data")[1];
}
elseif (is_object($data) && get_class($data) === 'stdClass') {
return unpack("H*", "\0\4\3" . var_export((object) (array) $data, TRUE))[1];
}
elseif (is_object($data)) {
return unpack("H*", "\0\4\2" . var_export($data, TRUE))[1];
}
else {
return unpack("H*", "\0\0\1" . var_export($data, TRUE))[1];
}
return unpack('H*', $data)[1];
}
/**
* Sets the prefix to use for the cache bin.
* Sets the one of the strings used for the APCu storage key.
*/
protected function setBinPrefix() {
$default_empty_prefix = '';
......@@ -105,7 +77,7 @@ protected function setBinPrefix() {
if (!apcu_store('apcu_cache::default_prefix', $default_empty_prefix)) {
// Since it was not possible to store the value to use instead of the
// empty string, set $default_empty_prefix to an empty string.
// empty string, set $default_empty_prefix back to an empty string.
$default_empty_prefix = '';
}
}
......@@ -150,24 +122,11 @@ protected function setBinPrefix() {
}
/**
* Adds the database prefix to the bin prefix.
*
* This method checks if the $prefix property is empty and if the global
* $databases variable is set and is an array. If these conditions are met,
* it generates a prefix based on the database connection information.
*
* The generated prefix is an SHA-256 hash of the concatenated values of the
* 'host', 'database', and 'prefix' keys from the 'default' array in the
* 'default' array of the $databases variable.
*
* If the generated prefix is not empty and does not end with '::', the
* '::' string is appended to the prefix.
*
* If the site is in testing mode, the generated prefix is prepended with the
* value returned by drupal_valid_test_ua().
* Sets two of the strings used for the APCu storage key.
*/
protected function addDatabasePrefix() {
protected function setDatabasePrefix() {
global $databases;
$default_empty_prefix = '';
if (extension_loaded('apcu') && apcu_enabled()) {
......@@ -177,33 +136,31 @@ protected function addDatabasePrefix() {
$default_empty_prefix = drupal_random_bytes(8);
if (!apcu_store('apcu_cache::default_prefix', $default_empty_prefix)) {
// Since it was not possible to store the value to use instead of the
// empty string, set $default_empty_prefix back to an empty string.
$default_empty_prefix = '';
}
}
}
if (isset($databases) && is_array($databases)) {
$data = '';
$data = array();
if (isset($databases['default']['default']) && is_array($databases['default']['default'])) {
if (isset($databases['default']['default']['host'])) {
$data .= $this->binaryToHex($databases['default']['default']['host']);
$data['host'] = $databases['default']['default']['host'];
}
if (isset($databases['default']['default']['database'])) {
$data .= $this->binaryToHex($databases['default']['default']['database']);
$data['database'] = $databases['default']['default']['database'];
}
if (isset($databases['default']['default']['prefix'])) {
$data .= $this->binaryToHex($databases['default']['default']['prefix']);
$data['prefix'] = $databases['default']['default']['prefix'];
}
}
if (empty($data)) {
$data = $this->binaryToHex($default_empty_prefix);
}
$this->prefixes[2] = $data;
$this->prefixes[2] = $this->binaryToHex(serialize($data));
}
if ($test_prefix = drupal_valid_test_ua()) {
......@@ -218,10 +175,11 @@ public function __construct($bin) {
$this->bin = $bin;
$this->setBinPrefix();
$this->addDatabasePrefix();
$this->setDatabasePrefix();
$data = $this->prefixes[0] . $this->bin;
$this->binPrefix = drupal_base64_encode(hash('sha256', $data, TRUE));
$data = $this->prefixes[0] . $this->bin . $this->prefixes[1];
$data = $this->prefixes[2] . hash('sha256', $data, TRUE);
$this->binPrefix = drupal_base64_encode($data);
}
/**
......@@ -237,10 +195,8 @@ public function __construct($bin) {
protected function keyName($cid = NULL) {
$key_name = $this->modulePrefix . '::' . $this->binPrefix . '::';
if (!is_null($cid)) {
$data = $this->prefixes[1] . $this->bin . $this->prefixes[2] . $cid;
$data = drupal_base64_encode(hash('sha256', $data, TRUE));
$key_name .= $data;
if (!is_null($cid) && $cid != '') {
$key_name .= $this->binaryToHex($cid);
}
return $key_name;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment