diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index 5223cc5..ee944d1 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -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,8 @@ function install_load_profile(&$install_state) { * An array of information about the current installation state. */ function install_bootstrap_full(&$install_state) { + 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(); diff --git a/core/includes/update.inc b/core/includes/update.inc index b8e8ee6..4261a9b 100644 --- a/core/includes/update.inc +++ b/core/includes/update.inc @@ -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, + )); + } +} diff --git a/core/lib/Drupal/Core/Cache/DatabaseBackend.php b/core/lib/Drupal/Core/Cache/DatabaseBackend.php index 8a73a20..d1e0060 100644 --- a/core/lib/Drupal/Core/Cache/DatabaseBackend.php +++ b/core/lib/Drupal/Core/Cache/DatabaseBackend.php @@ -8,6 +8,7 @@ namespace Drupal\Core\Cache; use Drupal\Core\Database\Database; +use Drupal\Core\Database\DatabaseExceptionWrapper; use Exception; use PDO; @@ -52,30 +53,23 @@ public function get($cid, $allow_invalid = FALSE) { * 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 - // string. This is highly discouraged under any other circumstances, and - // is used here only due to the performance overhead we would incur - // otherwise. When serving an uncached page, the overhead of using - // ::select() is a much smaller proportion of the request. - $result = Database::getConnection()->query('SELECT cid, data, created, expire, serialized, tags, checksum_invalidations, checksum_deletions FROM {' . Database::getConnection()->escapeTable($this->bin) . '} WHERE cid IN (:cids)', array(':cids' => $cids)); - $cache = array(); - foreach ($result as $item) { - $item = $this->prepareItem($item, $allow_invalid); - if ($item) { - $cache[$item->cid] = $item; - } + // 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 + // string. This is highly discouraged under any other circumstances, and + // is used here only due to the performance overhead we would incur + // otherwise. When serving an uncached page, the overhead of using + // ::select() is a much smaller proportion of the request. + $result = Database::getConnection()->query('SELECT cid, data, created, expire, serialized, tags, checksum_invalidations, checksum_deletions FROM {' . Database::getConnection()->escapeTable($this->bin) . '} WHERE cid IN (:cids)', array(':cids' => $cids)); + $cache = array(); + foreach ($result as $item) { + $item = $this->prepareItem($item, $allow_invalid); + if ($item) { + $cache[$item->cid] = $item; } - $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(); } + $cids = array_diff($cids, array_keys($cache)); + return $cache; } /** @@ -131,33 +125,35 @@ protected function prepareItem($cache, $allow_invalid) { * 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( - 'serialized' => 0, - 'created' => REQUEST_TIME, - 'expire' => $expire, - 'tags' => implode(' ', $flat_tags), - 'checksum_invalidations' => $checksum['invalidations'], - 'checksum_deletions' => $checksum['deletions'], - ); - if (!is_string($data)) { - $fields['data'] = serialize($data); - $fields['serialized'] = 1; - } - else { - $fields['data'] = $data; - $fields['serialized'] = 0; - } + $flat_tags = $this->flattenTags($tags); + $checksum = $this->checksumTags($flat_tags); + $fields = array( + 'serialized' => 0, + 'created' => REQUEST_TIME, + 'expire' => $expire, + 'tags' => implode(' ', $flat_tags), + 'checksum_invalidations' => $checksum['invalidations'], + 'checksum_deletions' => $checksum['deletions'], + ); + if (!is_string($data)) { + $fields['data'] = serialize($data); + $fields['serialized'] = 1; + } + else { + $fields['data'] = $data; + $fields['serialized'] = 0; + } + try { Database::getConnection()->merge($this->bin) ->key(array('cid' => $cid)) ->fields($fields) ->execute(); } - catch (Exception $e) { - // The database may not be available, so we'll ignore cache_set requests. + catch (DatabaseExceptionWrapper $e) { + // If set() failed for whatever reason, then try to delete() to avoid a + // stale cache. + $this->delete($cid); } } diff --git a/core/lib/Drupal/Core/Cache/InstallBackend.php b/core/lib/Drupal/Core/Cache/InstallBackend.php deleted file mode 100644 index 91d0249..0000000 --- a/core/lib/Drupal/Core/Cache/InstallBackend.php +++ /dev/null @@ -1,187 +0,0 @@ - '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."); - } - } -} diff --git a/core/modules/system/lib/Drupal/system/Tests/DrupalKernel/DrupalKernelTest.php b/core/modules/system/lib/Drupal/system/Tests/DrupalKernel/DrupalKernelTest.php index cc60abd..cde0aeb 100644 --- a/core/modules/system/lib/Drupal/system/Tests/DrupalKernel/DrupalKernelTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/DrupalKernel/DrupalKernelTest.php @@ -26,11 +26,8 @@ public static function getInfo() { ); } - /** - * 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 @@ function testCompileDIC() { '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 @@ function testCompileDIC() { // 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); diff --git a/core/modules/system/system.install b/core/modules/system/system.install index b632a2f..ca5d6de 100644 --- a/core/modules/system/system.install +++ b/core/modules/system/system.install @@ -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. } /** diff --git a/core/modules/views/lib/Drupal/views/ViewsDataCache.php b/core/modules/views/lib/Drupal/views/ViewsDataCache.php index 2f38051..8aec87e 100644 --- a/core/modules/views/lib/Drupal/views/ViewsDataCache.php +++ b/core/modules/views/lib/Drupal/views/ViewsDataCache.php @@ -207,15 +207,22 @@ protected function processEntityTypes(array &$data) { * Destructs the ViewDataCache object. */ public function __destruct() { - if ($this->rebuildCache && !empty($this->storage)) { - // Keep a record with all data. - $this->set($this->baseCid, $this->storage); - // Save data in seperate cache entries. - foreach ($this->storage as $table => $data) { - $cid = $this->baseCid . ':' . $table; - $this->set($cid, $data); + try { + if ($this->rebuildCache && !empty($this->storage)) { + // Keep a record with all data. + $this->set($this->baseCid, $this->storage); + // Save data in seperate cache entries. + foreach ($this->storage as $table => $data) { + $cid = $this->baseCid . ':' . $table; + $this->set($cid, $data); + } } } + catch (\Exception $e) { + // During testing the table is gone before this fires. + // @todo Use terminate() instead of __destruct(), see + // http://drupal.org/node/512026. + } } }