Issue #1792536 by chx, Berdir, sun, David_Rothstein: Remove the install backend and stop catching exceptions in the default database cache backend.

8.0.x
catch 2013-01-10 12:08:06 +00:00
parent 14e0c88dad
commit f5d69718ee
8 changed files with 221 additions and 513 deletions

View File

@ -342,17 +342,8 @@ function install_begin_request(&$install_state) {
module_list(NULL, $module_list);
drupal_load('module', 'system');
// Load the cache infrastructure using a "fake" cache implementation that
// does not attempt to write to the database. We need this during the initial
// part of the installer because the database is not available yet. We
// continue to use it even when the database does become available, in order
// to preserve consistency between interactive and command-line installations
// (the latter complete in one page request and therefore are forced to
// continue using the cache implementation they started with) and also
// because any data put in the cache during the installer is inherently
// suspect, due to the fact that Drupal is not fully set up yet.
require_once DRUPAL_ROOT . '/core/includes/cache.inc';
$conf['cache_classes'] = array('cache' => 'Drupal\Core\Cache\InstallBackend');
$conf['cache_classes'] = array('cache' => 'Drupal\Core\Cache\MemoryBackend');
// The install process cannot use the database lock backend since the database
// is not fully up, so we use a null backend implementation during the
@ -1496,6 +1487,11 @@ function install_load_profile(&$install_state) {
* An array of information about the current installation state.
*/
function install_bootstrap_full(&$install_state) {
// The early stages of the installer override the cache backend since Drupal
// isn't fully set up yet. Here the override is removed so that the standard
// cache backend will be used again.
unset($GLOBALS['conf']['cache_classes']['cache']);
drupal_static_reset('cache');
// Clear the module list that was overriden earlier in the process.
// This will allow all freshly installed modules to be loaded.
module_list_reset();

View File

@ -135,7 +135,10 @@ function update_prepare_d8_bootstrap() {
drupal_install_config_directories();
}
// Bootstrap the database.
// Bootstrap the database. During this, the DRUPAL_BOOTSTRAP_PAGE_CACHE will
// try to read the cache but the cache tables might not be Drupal 8
// compatible yet. Use the null backend by default to avoid exceptions.
$GLOBALS['conf']['cache_classes'] = array('cache' => 'Drupal\Core\Cache\NullBackend');
drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE);
// If the site has not updated to Drupal 8 yet, check to make sure that it is
@ -195,6 +198,116 @@ function update_prepare_d8_bootstrap() {
);
db_create_table('key_value', $specs);
}
if (!db_table_exists('cache_tags')) {
$table = array(
'description' => 'Cache table for tracking cache tags related to the cache bin.',
'fields' => array(
'tag' => array(
'description' => 'Namespace-prefixed tag string.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'invalidations' => array(
'description' => 'Number incremented when the tag is invalidated.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'deletions' => array(
'description' => 'Number incremented when the tag is deleted.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
),
'primary key' => array('tag'),
);
db_create_table('cache_tags', $table);
}
if (!db_table_exists('cache_config')) {
$spec = array(
'description' => 'Cache table for configuration data.',
'fields' => array(
'cid' => array(
'description' => 'Primary Key: Unique cache ID.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'data' => array(
'description' => 'A collection of data to cache.',
'type' => 'blob',
'not null' => FALSE,
'size' => 'big',
),
'expire' => array(
'description' => 'A Unix timestamp indicating when the cache entry should expire, or 0 for never.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'created' => array(
'description' => 'A Unix timestamp indicating when the cache entry was created.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'serialized' => array(
'description' => 'A flag to indicate whether content is serialized (1) or not (0).',
'type' => 'int',
'size' => 'small',
'not null' => TRUE,
'default' => 0,
),
'tags' => array(
'description' => 'Space-separated list of cache tags for this entry.',
'type' => 'text',
'size' => 'big',
'not null' => FALSE,
),
'checksum_invalidations' => array(
'description' => 'The tag invalidation sum when this entry was saved.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'checksum_deletions' => array(
'description' => 'The tag deletion sum when this entry was saved.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
),
'indexes' => array(
'expire' => array('expire'),
),
'primary key' => array('cid'),
);
db_create_table('cache_config', $spec);
}
require_once DRUPAL_ROOT . '/core/modules/system/system.install';
$tables = array(
'cache',
'cache_bootstrap',
'cache_block',
'cache_field',
'cache_filter',
'cache_form',
'cache_image',
'cache_menu',
'cache_page',
'cache_path',
'cache_update',
);
foreach ($tables as $table) {
update_add_cache_columns($table);
}
// Bootstrap variables so we can update theme while preparing the update
// process.
drupal_bootstrap(DRUPAL_BOOTSTRAP_VARIABLES);
@ -299,6 +412,9 @@ function update_prepare_d8_bootstrap() {
}
}
}
// Now remove the cache override.
unset($GLOBALS['conf']['cache_classes']['cache']);
drupal_static_reset('cache');
}
/**
@ -1333,3 +1449,33 @@ function update_add_uuids(&$sandbox, $table, $primary_key, $values) {
$sandbox['last'] = $value;
}
}
/**
* Adds tags, checksum_invalidations, checksum_deletions to a cache table.
*
* @param string $table
* Name of the cache table.
*/
function update_add_cache_columns($table) {
if (db_table_exists($table) && !db_field_exists($table, 'tags')) {
db_add_field($table, 'tags', array(
'description' => 'Space-separated list of cache tags for this entry.',
'type' => 'text',
'size' => 'big',
'not null' => FALSE,
));
db_add_field($table, 'checksum_invalidations', array(
'description' => 'The tag invalidation sum when this entry was saved.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
));
db_add_field($table, 'checksum_deletions', array(
'description' => 'The tag deletion sum when this entry was saved.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
));
}
}

View File

@ -8,8 +8,7 @@
namespace Drupal\Core\Cache;
use Drupal\Core\Database\Database;
use Exception;
use PDO;
use Drupal\Core\Database\DatabaseException;
/**
* Defines a default cache implementation.
@ -52,7 +51,6 @@ class DatabaseBackend implements CacheBackendInterface {
* Implements Drupal\Core\Cache\CacheBackendInterface::getMultiple().
*/
public function getMultiple(&$cids, $allow_invalid = FALSE) {
try {
// 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,12 +69,6 @@ class DatabaseBackend implements CacheBackendInterface {
$cids = array_diff($cids, array_keys($cache));
return $cache;
}
catch (Exception $e) {
// If the database is never going to be available, cache requests should
// return FALSE in order to allow exception handling to occur.
return array();
}
}
/**
* Prepares a cached item.
@ -131,7 +123,6 @@ class DatabaseBackend implements CacheBackendInterface {
* Implements Drupal\Core\Cache\CacheBackendInterface::set().
*/
public function set($cid, $data, $expire = CacheBackendInterface::CACHE_PERMANENT, array $tags = array()) {
try {
$flat_tags = $this->flattenTags($tags);
$checksum = $this->checksumTags($flat_tags);
$fields = array(
@ -156,10 +147,6 @@ class DatabaseBackend implements CacheBackendInterface {
->fields($fields)
->execute();
}
catch (Exception $e) {
// The database may not be available, so we'll ignore cache_set requests.
}
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::delete().
@ -316,7 +303,7 @@ class DatabaseBackend implements CacheBackendInterface {
$query_tags = array_diff($flat_tags, array_keys($tag_cache));
if ($query_tags) {
$db_tags = Database::getConnection()->query('SELECT tag, invalidations, deletions FROM {cache_tags} WHERE tag IN (:tags)', array(':tags' => $query_tags))->fetchAllAssoc('tag', PDO::FETCH_ASSOC);
$db_tags = Database::getConnection()->query('SELECT tag, invalidations, deletions FROM {cache_tags} WHERE tag IN (:tags)', array(':tags' => $query_tags))->fetchAllAssoc('tag', \PDO::FETCH_ASSOC);
$tag_cache += $db_tags;
// Fill static cache with empty objects for tags not found in the database.

View File

@ -1,187 +0,0 @@
<?php
/**
* @file
* Definition of Drupal\Core\Cache\InstallBackend.
*/
namespace Drupal\Core\Cache;
use Exception;
/**
* Defines a stub cache implementation to be used during installation.
*
* The stub implementation is needed when database access is not yet available.
* Because Drupal's caching system never requires that cached data be present,
* these stub functions can short-circuit the process and sidestep the need for
* any persistent storage. Obviously, using this cache implementation during
* normal operations would have a negative impact on performance.
*
* If there is a database cache, this backend will attempt to clear it whenever
* possible. The reason for doing this is that the database cache can accumulate
* data during installation due to any full bootstraps that may occur at the
* same time (for example, Ajax requests triggered by the installer). If we
* didn't try to clear it whenever one of the delete function are called, the
* data in the cache would become stale; for example, the installer sometimes
* calls variable_set(), which updates the {variable} table and then clears the
* cache to make sure that the next page request picks up the new value.
* Not actually clearing the cache here therefore leads old variables to be
* loaded on the first page requests after installation, which can cause
* subtle bugs, some of which would not be fixed unless the site
* administrator cleared the cache manually.
*/
class InstallBackend extends DatabaseBackend {
/**
* Overrides Drupal\Core\Cache\DatabaseBackend::get().
*/
public function get($cid, $allow_invalid = FALSE) {
return FALSE;
}
/**
* Overrides Drupal\Core\Cache\DatabaseBackend::getMultiple().
*/
public function getMultiple(&$cids, $allow_invalid = FALSE) {
return array();
}
/**
* Overrides Drupal\Core\Cache\DatabaseBackend::set().
*/
public function set($cid, $data, $expire = CacheBackendInterface::CACHE_PERMANENT, array $tags = array()) {}
/**
* Overrides Drupal\Core\Cache\DatabaseBackend::delete().
*/
public function delete($cid) {
try {
if (class_exists('Drupal\Core\Database\Database')) {
parent::delete($cid);
}
}
catch (Exception $e) {}
}
/**
* Overrides Drupal\Core\Cache\DatabaseBackend::deleteMultiple().
*/
public function deleteMultiple(array $cids) {
try {
if (class_exists('Drupal\Core\Database\Database')) {
parent::deleteMultiple($cids);
}
}
catch (Exception $e) {}
}
/**
* Overrides Drupal\Core\Cache\DatabaseBackend::deleteAll().
*/
public function deleteAll() {
try {
if (class_exists('Drupal\Core\Database\Database')) {
parent::deleteAll();
}
}
catch (Exception $e) {}
}
/**
* Overrides Drupal\Core\Cache\DatabaseBackend::deleteExpired().
*/
public function deleteExpired() {
try {
if (class_exists('Drupal\Core\Database\Database')) {
parent::deleteExpired();
}
}
catch (Exception $e) {}
}
/**
* Overrides Drupal\Core\Cache\DatabaseBackend::deleteTags().
*/
public function deleteTags(array $tags) {
try {
if (class_exists('Drupal\Core\Database\Database')) {
parent::deleteTags($tags);
}
}
catch (Exception $e) {}
}
/**
* Overrides Drupal\Core\Cache\DatabaseBackend::invalidate().
*/
public function invalidate($cid) {
try {
if (class_exists('Drupal\Core\Database\Database')) {
parent::invalidate($cid);
}
}
catch (Exception $e) {}
}
/**
* Overrides Drupal\Core\Cache\DatabaseBackend::invalidateMultiple().
*/
public function invalidateMultiple(array $cids) {
try {
if (class_exists('Drupal\Core\Database\Database')) {
parent::invalidateMultiple($cids);
}
}
catch (Exception $e) {}
}
/**
* Overrides Drupal\Core\Cache\DatabaseBackend::invalidateTags().
*/
public function invalidateTags(array $tags) {
try {
if (class_exists('Drupal\Core\Database\Database')) {
parent::invalidateTags($tags);
}
}
catch (Exception $e) {}
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::invalidateAll().
*/
public function invalidateAll() {
try {
if (class_exists('Drupal\Core\Database\Database')) {
parent::invalidateAll($tags);
}
}
catch (Exception $e) {}
}
/**
* Overrides Drupal\Core\Cache\DatabaseBackend::garbageCollection().
*/
public function garbageCollection() {
try {
if (class_exists('Drupal\Core\Database\Database')) {
parent::garbageCollection();
}
}
catch (Exception $e) {}
}
/**
* Overrides Drupal\Core\Cache\DatabaseBackend::isEmpty().
*/
public function isEmpty() {
try {
if (class_exists('Drupal\Core\Database\Database')) {
return parent::isEmpty();
}
}
catch (Exception $e) {}
return TRUE;
}
}

View File

@ -1,119 +0,0 @@
<?php
/**
* @file
* Definition of Drupal\system\Tests\Cache\InstallTest.
*/
namespace Drupal\system\Tests\Cache;
use Drupal\Core\Cache\DatabaseBackend;
use Drupal\Core\Cache\InstallBackend;
use Exception;
/**
* Tests the behavior of the cache backend used for installing Drupal.
*/
class InstallTest extends CacheTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('cache_test');
protected $profile = 'testing';
public static function getInfo() {
return array(
'name' => 'Cache install test',
'description' => 'Confirm that the cache backend used for installing Drupal works correctly.',
'group' => 'Cache',
);
}
/**
* Tests the behavior of the cache backend used for installing Drupal.
*
* While Drupal is being installed, the cache system must deal with the fact
* that the database is not initially available, and, after it is available,
* the fact that other requests that take place while Drupal is being
* installed (for example, Ajax requests triggered via the installer's user
* interface) may cache data in the database, which needs to be cleared when
* the installer makes changes that would result in it becoming stale.
*
* We cannot test this process directly, so instead we test it by switching
* between the normal database cache (Drupal\Core\Cache\DatabaseBackend) and
* the installer cache (Drupal\Core\Cache\InstallBackend) while setting and
* clearing various items in the cache.
*/
function testCacheInstall() {
$database_cache = new DatabaseBackend('test');
$install_cache = new InstallBackend('test');
// Store an item in the database cache, and confirm that the installer's
// cache backend recognizes that the cache is not empty.
$database_cache->set('cache_one', 'One');
$this->assertFalse($install_cache->isEmpty());
$database_cache->delete('cache_one');
$this->assertTrue($install_cache->isEmpty());
// Store an item in the database cache, then use the installer's cache
// backend to delete it. Afterwards, confirm that it is no longer in the
// database cache.
$database_cache->set('cache_one', 'One');
$this->assertEqual($database_cache->get('cache_one')->data, 'One');
$install_cache->delete('cache_one');
$this->assertFalse($database_cache->get('cache_one'));
// Store multiple items in the database cache, then use the installer's
// cache backend to delete them. Afterwards, confirm that they are no
// longer in the database cache.
$database_cache->set('cache_one', 'One');
$database_cache->set('cache_two', 'Two');
$this->assertEqual($database_cache->get('cache_one')->data, 'One');
$this->assertEqual($database_cache->get('cache_two')->data, 'Two');
$install_cache->deleteMultiple(array('cache_one', 'cache_two'));
$this->assertFalse($database_cache->get('cache_one'));
$this->assertFalse($database_cache->get('cache_two'));
// Store multiple items in the database cache, then use the installer's
// cache backend to flush the cache. Afterwards, confirm that they are no
// longer in the database cache.
$database_cache->set('cache_one', 'One');
$database_cache->set('cache_two', 'Two');
$this->assertEqual($database_cache->get('cache_one')->data, 'One');
$this->assertEqual($database_cache->get('cache_two')->data, 'Two');
$install_cache->deleteAll();
$this->assertFalse($database_cache->get('cache_one'));
$this->assertFalse($database_cache->get('cache_two'));
// Invalidate a tag using the installer cache, then check that the
// invalidation was recorded correctly in the database.
$install_cache->invalidateTags(array('tag'));
$invalidations = db_query("SELECT invalidations FROM {cache_tags} WHERE tag = 'tag'")->fetchField();
$this->assertEqual($invalidations, 1);
// For each cache clearing event that we tried above, try it again after
// dropping the {cache_test} table. This simulates the early stages of the
// installer (when the database cache tables won't be available yet) and
// thereby confirms that the installer's cache backend does not produce
// errors if the installer ever calls any code early on that tries to clear
// items from the cache.
db_drop_table('cache_test');
try {
$install_cache->isEmpty();
$install_cache->delete('cache_one');
$install_cache->deleteMultiple(array('cache_one', 'cache_two'));
$install_cache->deleteAll();
$install_cache->deleteExpired();
$install_cache->garbageCollection();
$install_cache->invalidateTags(array('tag'));
$this->pass("The installer's cache backend can be used even when the cache database tables are unavailable.");
}
catch (Exception $e) {
$this->fail("The installer's cache backend can be used even when the cache database tables are unavailable.");
}
}
}

View File

@ -26,11 +26,8 @@ class DrupalKernelTest extends UnitTestBase {
);
}
/**
* Tests DIC compilation.
*/
function testCompileDIC() {
$classloader = drupal_classloader();
function setUp() {
parent::setUp();
global $conf;
$conf['php_storage']['service_container']= array(
'bin' => 'service_container',
@ -38,6 +35,15 @@ class DrupalKernelTest extends UnitTestBase {
'directory' => DRUPAL_ROOT . '/' . variable_get('file_public_path', conf_path() . '/files') . '/php',
'secret' => $GLOBALS['drupal_hash_salt'],
);
// Use a non-persistent cache to avoid queries to non-existing tables.
$conf['cache_classes'] = array('cache' => 'Drupal\Core\Cache\MemoryBackend');
}
/**
* Tests DIC compilation.
*/
function testCompileDIC() {
$classloader = drupal_classloader();
// @todo: write a memory based storage backend for testing.
$module_enabled = array(
'system' => 'system',
@ -60,6 +66,7 @@ class DrupalKernelTest extends UnitTestBase {
// Now use the read-only storage implementation, simulating a "production"
// environment.
global $conf;
$conf['php_storage']['service_container']['class'] = 'Drupal\Component\PhpStorage\FileReadOnlyStorage';
$kernel = new DrupalKernel('testing', FALSE, $classloader);
$kernel->updateModules($module_enabled);

View File

@ -1519,66 +1519,7 @@ function system_update_8002() {
* Creates {cache_config} cache table for the new configuration system.
*/
function system_update_8003() {
$spec = array(
'description' => 'Cache table for configuration data.',
'fields' => array(
'cid' => array(
'description' => 'Primary Key: Unique cache ID.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'data' => array(
'description' => 'A collection of data to cache.',
'type' => 'blob',
'not null' => FALSE,
'size' => 'big',
),
'expire' => array(
'description' => 'A Unix timestamp indicating when the cache entry should expire, or 0 for never.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'created' => array(
'description' => 'A Unix timestamp indicating when the cache entry was created.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'serialized' => array(
'description' => 'A flag to indicate whether content is serialized (1) or not (0).',
'type' => 'int',
'size' => 'small',
'not null' => TRUE,
'default' => 0,
),
'tags' => array(
'description' => 'Space-separated list of cache tags for this entry.',
'type' => 'text',
'size' => 'big',
'not null' => FALSE,
),
'checksum_invalidations' => array(
'description' => 'The tag invalidation sum when this entry was saved.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'checksum_deletions' => array(
'description' => 'The tag deletion sum when this entry was saved.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
),
'indexes' => array(
'expire' => array('expire'),
),
'primary key' => array('cid'),
);
db_create_table('cache_config', $spec);
// Moved to update_prepare_d8_bootstrap.
}
/**
@ -1626,84 +1567,14 @@ function system_update_8005() {
* Adds the {cache_tags} table.
*/
function system_update_8006() {
$table = array(
'description' => 'Cache table for tracking cache tags related to the cache bin.',
'fields' => array(
'tag' => array(
'description' => 'Namespace-prefixed tag string.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'invalidations' => array(
'description' => 'Number incremented when the tag is invalidated.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'deletions' => array(
'description' => 'Number incremented when the tag is deleted.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
),
'primary key' => array('tag'),
);
db_create_table('cache_tags', $table);
}
/**
* Adds tags, checksum_invalidations, checksum_deletions to a cache table.
*
* @param string $table
* Name of the cache table.
*/
function system_update_8007_add_tag_column($table) {
if (db_table_exists($table)) {
db_add_field($table, 'tags', array(
'description' => 'Space-separated list of cache tags for this entry.',
'type' => 'text',
'size' => 'big',
'not null' => FALSE,
));
db_add_field($table, 'checksum_invalidations', array(
'description' => 'The tag invalidation sum when this entry was saved.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
));
db_add_field($table, 'checksum_deletions', array(
'description' => 'The tag deletion sum when this entry was saved.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
));
}
// Moved to update_prepare_d8_bootstrap.
}
/**
* Modifies existing cache tables, adding support for cache tags.
*/
function system_update_8007() {
$tables = array(
'cache',
'cache_bootstrap',
'cache_form',
'cache_page',
'cache_menu',
'cache_path',
'cache_filter',
'cache_image',
'cache_update',
'cache_block',
'cache_field',
);
foreach ($tables as $table) {
system_update_8007_add_tag_column($table);
}
// Moved to update_prepare_d8_bootstrap.
}
/**

View File

@ -207,6 +207,7 @@ class ViewsDataCache {
* Destructs the ViewDataCache object.
*/
public function __destruct() {
try {
if ($this->rebuildCache && !empty($this->storage)) {
// Keep a record with all data.
$this->set($this->baseCid, $this->storage);
@ -217,5 +218,11 @@ class ViewsDataCache {
}
}
}
catch (\Exception $e) {
// During testing the table is gone before this fires.
// @todo Use terminate() instead of __destruct(), see
// http://drupal.org/node/512026.
}
}
}