Problem/Motivation

We create the key_value table on the installation of the system module.
Let's make that lazy like for example the cache system.

Proposed resolution

The patch from #67 was committed (5571193) and then reverted (eaaea5d for two reasons:

  1. This breaks saving config entities without UUIDs. See #73
  2. There are test failures with PHP7 and some database drivers. See #74

The first problem is addressed in #77 and more recent patches. Some patches (up to #94) addressed the second problem, but not in a satisfactory way, so that problem was split off into #2847855: Incorrect statement class caused by race condition when closing and opening the connection and this issue was postponed on that, #107.

In order to help reviews and future work, when this was no longer postponed, a new patch was made based on #77 which included typo fixes and documentation changes from #94. When that was complete the issue was postponed in #113 until the follow 2 questions were answered.

1. What really confuses me is why this starts to fail as part of this patch. I mean it used to work fine without the patch, I guess. See #80
The answer. The Connection class has the wrong statementClass, leading to an exception. The patches on this issue change the exception handling in KeyValueStore\DatabaseStorage::getMultiple(). Previously, all exceptions were ignored. Earlier patches on this issue propagated the exception unless it seemed to be caused by a missing (to-be-lazy-loaded) database table. Different implementations of tableExists() give different results, which explains why the test fails only for PostgresQL. See #116

2. It's good that the workaround works, but do we understand what the race condition is? And is it reproducible enough to post a PHP/PDO bug report for? See #96
The answer. TBD

This was unblocked in #116, where all changes to getMultiple() were removed. That resulted in passing tests for MySQL, PostgreSQL and SQLite. This was followed by several rounds of code cleanup.

When this was picked up again it was time to reroll against 9.0.x which removed uuid changes since they were part of a BC layer. The result is a cleanup patch which passes tests in #141.

Then deprecation was added because now that the tables are lazy loaded contrib test could fails.

Remaining tasks

Review the patch

Answer this, if not already answered.
It's good that the workaround works, but do we understand what the race condition is? And is it reproducible enough to post a PHP/PDO bug report for? See #96
The answer. TBD

Make a followup for, if not already made. #116, 3rd item. Different implementations of tableExists() give different results, which explains why the test fails only for PostgresQL.

User interface changes

API changes

Data model changes

CommentFileSizeAuthor
#159 2664322-159.patch48.31 KBdaffie
#159 interdiff-2664322-158-159.txt1.36 KBdaffie
#158 2664322-158.patch48.41 KBdaffie
#158 interdiff-2664322-156-158.txt1.88 KBdaffie
#156 2664322-156.patch48.39 KBdaffie
#147 2664322-147.patch47.8 KBdaffie
#147 interdiff-2664322-144-147.txt2.51 KBdaffie
#144 2664322-144.patch47.79 KBdaffie
#144 interdiff-2664322-141-144.txt2.1 KBdaffie
#141 2664322-141.patch45.45 KBdaffie
#140 2664322-140-combined-patch-2850292-16-and-2847855-40.patch57.35 KBdaffie
#140 2664322-140-for-review-only.patch48.39 KBdaffie
#138 2664322-138-combined-patch-2850292-16-and-2847855-40.patch56.96 KBdaffie
#138 2664322-138-for-review-only.patch45.81 KBdaffie
#127 interdiff-125-127.txt692 bytesbenjifisher
#127 2664322-127.patch40.48 KBbenjifisher
#125 2664322-125.patch40.49 KBbenjifisher
#122 interdiff-2664322-121-122.txt7.48 KBbenjifisher
#122 2664322-122.patch40.42 KBbenjifisher
#121 interdiff-2664322-117-121.txt5.15 KBbenjifisher
#121 2664322-121.patch40.82 KBbenjifisher
#118 interdiff-2664322-117-118.txt5.16 KBbenjifisher
#118 2664322-118.patch40.82 KBbenjifisher
#117 interdiff-2664322-116-117.txt4.21 KBbenjifisher
#117 2664322-117.patch73.12 KBbenjifisher
#116 interdiff-2664322-109-116.txt1.57 KBbenjifisher
#116 2664322-116.patch40.92 KBbenjifisher
#114 interdiff-2664322-111-114.txt906 bytesdaffie
#114 2664322-114.patch41.77 KBdaffie
#113 interdiff-109-111.txt1.56 KBbenjifisher
#111 2664322-111.patch40.93 KBbenjifisher
#109 2664322-109.patch42.49 KBbenjifisher
#108 interdiff-77-94.txt2.18 KBbenjifisher
#108 interdiff-67-77.txt1.91 KBbenjifisher
#94 interdiff.txt2.2 KBalmaudoh
#94 2664322-94.patch44.43 KBalmaudoh
#84 statement-class-test.patch1.93 KBmradcliffe
#84 interdiff-2664322-77-84.txt966 bytesmradcliffe
#84 2664322-84.patch44.43 KBmradcliffe
#81 interdiff-2664322-77-81.txt760 bytesmradcliffe
#81 2664322-81.patch48.56 KBmradcliffe
#77 2664322-77.patch43.35 KBdawehner
#75 interdiff.txt2.18 KBdawehner
#75 2664322-74.patch43.71 KBdawehner
#67 2664322-67.patch42.75 KBalmaudoh
#65 interdiff.txt590 bytesdawehner
#65 2664322-65.patch42.48 KBdawehner
#56 2664322-56.patch43.33 KBdawehner
#54 2664322-54.patch43.06 KBdawehner
#52 interdiff.txt991 bytesdawehner
#52 2664322-52.patch43.06 KBdawehner
#50 2664322-50.patch42.36 KBdawehner
#50 interdiff.txt4.47 KBdawehner
#48 2664322-41-do-not-commit.patch35.59 KBcatch
#44 bar.log_.txt765 bytesdawehner
#41 2664322-41.patch37.09 KBalmaudoh
#41 interdiff-2.txt2.05 KBalmaudoh
#40 2664322-40.patch37.07 KBalmaudoh
#40 interdiff.txt6.78 KBalmaudoh
#39 2664322-39.patch43.16 KBalmaudoh
#35 2664322-35.patch43.16 KBbenjifisher
#33 interdiff-2664322-33.txt697 bytesbenjifisher
#33 2664322-33-a.patch94.52 KBbenjifisher
#33 2664322-33.patch42.48 KBbenjifisher
#31 interdiff.txt8.03 KBdawehner
#31 2664322-31.patch42.34 KBdawehner
#28 interdiff-24-28.txt2.72 KBbenjifisher
#28 2664322-28.patch34.31 KBbenjifisher
#24 interdiff-22-24.txt1.25 KBbenjifisher
#24 2664322-24.patch33.1 KBbenjifisher
#22 interdiff-14-22.txt4.08 KBbenjifisher
#22 2664322-22.patch26.31 KBbenjifisher
#20 interdiff-14-20.txt4.2 KBbenjifisher
#20 2664322-20.patch26.31 KBbenjifisher
#15 interdiff.txt745 bytesdawehner
#15 2664322-14.patch26.54 KBdawehner
#12 2664322-12.patch25.82 KBdawehner
#10 2664322-10.patch16.73 KBdawehner
#9 2664322-9.patch16.8 KBdawehner
#8 interdiff.txt2.27 KBdawehner
#8 2664322-8.patch16.72 KBdawehner
#6 interdiff.txt3.66 KBdawehner
#6 2664322-6.patch14.44 KBdawehner
#4 interdiff.txt3.8 KBdawehner
#4 2664322-4.patch12.81 KBdawehner
#2 2664322-2.patch9.61 KBdawehner
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

dawehner created an issue. See original summary.

dawehner’s picture

Status: Needs review » Needs work

The last submitted patch, 2: 2664322-2.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
12.81 KB
3.8 KB

This should fix a good bunch of the failures.

Status: Needs review » Needs work

The last submitted patch, 4: 2664322-4.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
14.44 KB
3.66 KB

There we go.

I would have not expected that we catch exceptions silently.

Status: Needs review » Needs work

The last submitted patch, 6: 2664322-6.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
16.72 KB
2.27 KB

Let's fix the remaining failures.

dawehner’s picture

Component: batch system » base system
FileSize
16.8 KB

Rerolled

dawehner’s picture

Rebased

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.0-beta1 was released on March 2, 2016, which means new developments and disruptive changes should now be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

dawehner’s picture

Now this patch also patches the expireable key value store.

Status: Needs review » Needs work

The last submitted patch, 12: 2664322-12.patch, failed testing.

almaudoh’s picture

+++ b/core/modules/system/system.install
@@ -855,71 +855,6 @@ function system_install() {
diff --git a/core/modules/system/tests/fixtures/HtaccessTest/access_test.module.orig b/core/modules/system/tests/fixtures/HtaccessTest/access_test.module.orig

index e69de29..0000000
diff --git a/core/modules/system/tests/fixtures/HtaccessTest/access_test.php.orig b/core/modules/system/tests/fixtures/HtaccessTest/access_test.php.orig

These look unrelated...

dawehner’s picture

Status: Needs work » Needs review
FileSize
26.54 KB
745 bytes

There we go.

Status: Needs review » Needs work

The last submitted patch, 15: 2664322-14.patch, failed testing.

benjifisher’s picture

I see a few oddities. For example,

+    if ($uuid = $this->uuid()) {
+      $matching_entities = $storage->getQuery()
+        ->condition('uuid', $this->uuid())
+        ->execute();
+      $matched_entity = reset($matching_entities);
+      if (!empty($matched_entity) && ($matched_entity != $this->id()) && $matched_entity != $this->getOriginalId()) {
+        throw new ConfigDuplicateUUIDException("Attempt to save a configuration entity '{$this->id()}' with UUID '{$this->uuid()}' when this UUID is already used for '$matched_entity'");
+      }
     }

I guess the idea of setting $uuid is that you planned to use the value later instead of making another call to the uuid() method.

This pattern is used twice:

+    $try_again = FALSE;
+    try { ...
+    }
+    catch (\Exception $e) {
+      // If there was an exception, try to create the table.
+      if (!$try_again = $this->ensureTableExists()) {
+        // If the exception happened for other reason than the missing bin
+        // table, propagate the exception.
+        throw $e;
+      }
+    }
+    // Now that the bin has been created, try again if necessary.
+    if ($try_again) {
+      $this->set($key, $value);
+    }

I think you can eliminate the $try_again variable completely by moving $this->set($key, $value); inside the catch block. IMO this makes the control flow easier to follow, which is a good thing to worry about when a function calls itself from a catch block!

    try {
// ...
    }
    catch (\Exception $e) {
      // If there was an exception, then try to create the table.
      if ($this->ensureTableExists()) {
        // Now that the bin has been created, try again.
        $this->set($key, $value);
      }
      else {
        // If the exception happened for other reason than the missing bin
        // table, then propagate the exception.
        throw $e;
      }
    }

I will look at it some more tomorrow.

benjifisher’s picture

I forgot to say: I was looking at KeyValueStore/DatabaseStorage.php in my previous comment. Here is another little complaint:

        $schema_definition = $this->schemaDefinition();
        $database_schema->createTable($this->table, $schema_definition);

You can combine those into one line and still stay under 80 characters.

Funny indentation:

      // If another process has already created the batch table, attempting to
      // recreate it will throw an exception. In this case just catch the
      // exception and do nothing.
    catch (SchemaObjectExistsException $e) { // ...
    }

Comparing schemaDefinition() in KeyValueStore/DatabaseStorage.php and KeyValueStore/DatabaseStorageExpirable.php, I see

        'name' => [
          'description' => 'The key of the key-value pair. As KEY is a SQL reserved keyword, name was chosen instead.',

in one and

        'name' => [
          // KEY is an SQL reserved word, so use 'name' as the key's field name.
          'description' => 'The key of the key/value pair.',

in the other. I would rewrite DatabaseStorageExpirable/schemaDefinition() as

  public function schemaDefinition() {
    $schema = parent::schemaDefinition();

    $schema['description'] = '...';
    $schema['expire'] = [
      // ...
    ];
    $schema['indexes'] = [
      // ...
    ];

    return $schema;
  }

This would fix the inconsistencies that are already there, and it would prevent future inconsistencies.

This looks wrong: don't you want use Drupal\Core\KeyValueStore\DatabaseStorage;?

--- a/core/modules/system/src/Tests/Path/UrlAliasFixtures.php
+++ b/core/modules/system/src/Tests/Path/UrlAliasFixtures.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\system\Tests\Path;
 
+use Drupal\Core\Config\DatabaseStorage;
 use Drupal\Core\Database\Connection;
 use Drupal\Core\Path\AliasStorage;
 
@@ -89,7 +90,7 @@ public function tableDefinition() {
     $schema = system_schema();
 
     $tables['url_alias'] = AliasStorage::schemaDefinition();
-    $tables['key_value'] = $schema['key_value'];
+    $tables['key_value'] = DatabaseStorage::schemaDefinition();

Do we really need to keep multi-line array syntax here?

--- a/core/modules/system/src/Tests/Update/DbDumpTest.php
+++ b/core/modules/system/src/Tests/Update/DbDumpTest.php
@@ -84,7 +84,6 @@ protected function setUp() {
 
     // Create some schemas so our export contains tables.
     $this->installSchema('system', [
-      'key_value_expire',
       'sessions',
     ]);
     $this->installSchema('dblog', ['watchdog']);
dawehner’s picture

Great comments!

benjifisher’s picture

Status: Needs work » Needs review
FileSize
26.31 KB
4.2 KB

The attached patch does a few things:

  • Restore the removed files as pointed out in #14.
  • Implement the cosmetic changes from my comments #17 and #18, plus one or two other changes suggested by CodeSniffer.
  • Change the use directive in UrlAliasFixtures.php as I suggested in #18.

The removed files seem to cause one of the test failures for the patch in #14. I am getting additional failures when I run that test locally, so I am curious to see what the testbot does now.

@dawehner points out that some of my other suggestions affect code already in core, so I will open a couple of follow-up issues to suggest those changes.

Status: Needs review » Needs work

The last submitted patch, 20: 2664322-20.patch, failed testing.

benjifisher’s picture

Status: Needs work » Needs review
FileSize
26.31 KB
4.08 KB

It already needs a re-roll!

Status: Needs review » Needs work

The last submitted patch, 22: 2664322-22.patch, failed testing.

benjifisher’s picture

Status: Needs work » Needs review
FileSize
33.1 KB
1.25 KB

Failing two tests. I think one is easy to fix. I am attaching an interdiff with my previous patch.

Status: Needs review » Needs work

The last submitted patch, 24: 2664322-24.patch, failed testing.

benjifisher’s picture

I was right: we are down to one failing test.

After adding some extra lines to the failing test, I get the following message when running it locally:

Drupal\Core\Database\DatabaseExceptionWrapper: SQLSTATE[42S02]: Base table or view not found: 1146 Table 'drupal.simpletest974603key_value_expire' doesn't exist: DELETE FROM {key_value_expire} WHERE (expire < :db_condition_placeholder_0); Array ( [:db_condition_placeholder_0] => 1462765698 ) in system_cron() (line 1285 of /var/www/drupalvm/drupal/core/modules/system/system.module).

This is the extra dblog message that makes the test fail. The offending line in system.module is

    \Drupal::service('keyvalue.expirable.database')->garbageCollection();

So I think the problem is that KeyValueStore\KeyValueDatabaseExpirableFactory::garbageCollection() is trying to delete entries from a table that does not exist.

That is as far as I plan to go with this.

dawehner’s picture

@benjifisher
Right, so yeah we need this exception wrapping here as well.

benjifisher’s picture

Status: Needs work » Needs review
FileSize
34.31 KB
2.72 KB

@dawehner: Yes, and I decided to give it a try myself.

Since the DatabaseStorage::catchException() method is neither public nor static, I copied it (replacing $this->table with 'key_value_expire') to KeyValueDatabaseExpirableFactory and used it in the catch block. The DbLog test now passes locally; let's see if the testbot gets the same result.

While working on this, I noticed that some of the code comments had been copied from the BatchStorage class. Since that is part of this issue, I updated the comments. This is a signal that we should formalize this pattern for lazy table creation.

Another thing I noticed is that the table name 'key_value_expire' appears in two places: the constructor for DatabaseStorageExpirable and in KeyValueDatabaseExpirableFactory::garbageCollection(). (After this patch, it appears in another method in that class.) I think that string should be used in just the factory class, passed to the DatabaseStorageExpirable constructor. In fact, the only reason that constructor exists seems to be to override the default value of $table from the DatabaseStorage constructor, so if we pass 'key_value_expire', then we can eliminate the child constructor. Unless someone tells me I am missing something, I will open an issue to fix this.

almaudoh’s picture

This is a signal that we should formalize this pattern for lazy table creation.

There's a really old issue where that was being done - #2371709: Move the on-demand-table creation into the database API

benjifisher’s picture

Status: Needs review » Needs work

@almaudoh: Thanks for the reference.

I am moving this back to NW even though it is passing tests. I searched for key_value and key_value_expire in the code base. I found a few places, mostly in tests, where there are direct database queries. For example, (drupal-8.views_entity_reference_plugins-2429191.php):

$installed = $connection->select('key_value')
  ->fields('key_value', ['value'])
  ->condition('collection', 'entity.definitions.installed')
  ->condition('name', 'node.field_storage_definitions')
  ->execute()
  ->fetchField();

Probably these should be instantiating the appropriate DatabaseStorage class instead. Maybe in a follow-up issue? Or maybe they really should talk to the database directly?

In MigrationCreationTrait::getLegacyDrupalVersion() there is something similar, and I think this one should stay as is.

There are several tests that do something like this in their setUp() methods (DbLogFormInjectionTest.php):

    $this->installSchema('system', ['key_value_expire', 'sequences']);

I think the right thing to do is remove 'key_value_expire' from the array, and I think it should be done as part of this issue. But maybe some or all of these should be creating the tables indirectly by instantiating the appropriate class.

Also, there are several gzipped files that directly reference these database tables: core/modules/system/tests/fixtures/update/*.php.gz.

dawehner’s picture

Status: Needs work » Needs review
FileSize
42.34 KB
8.03 KB

@benjifisher
Great feedback!

I think the right thing to do is remove 'key_value_expire' from the array, and I think it should be done as part of this issue

I totally agree. Good point! I went through all instances of it.

I am moving this back to NW even though it is passing tests. I searched for key_value and key_value_expire in the code base. I found a few places, mostly in tests, where there are direct database queries. For example, (drupal-8.views_entity_reference_plugins-2429191.php):

Those places are fine. These are one of implementations for update path tests.

In MigrationCreationTrait::getLegacyDrupalVersion() there is something similar, and I think this one should stay as is.

Let's not touch that here either ... its wrapped in a $connection->schema()->tableExists('key_value') call.

Status: Needs review » Needs work

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

benjifisher’s picture

Status: Needs work » Needs review
FileSize
42.48 KB
94.52 KB
697 bytes

The patch from #31 ran afoul of #2687897: Convert system module's kernel tests to NG, which moved the tests to a new location. Don't take my word for it, ask git:

$ git show --stat=200 20a0802
commit 20a080288f7342d6fecff46bf7c1228675ea84c6
Author: Nathaniel Catchpole <catch@35733.no-reply.drupal.org>
Date:   Mon May 23 12:37:36 2016 +0100

    Issue #2687897 by dawehner, jhedstrom, catch, jibran, Berdir, klausi: Convert system module's kernel tests to NG
...
 core/modules/system/{src/Tests => tests/src/Kernel}/Block/SystemMenuBlockTest.php                                    |   4 +-
...
 core/{modules/system/src/Tests/Common => tests/Drupal/KernelTests/Core/Asset}/AttachedAssetsTest.php |   9 +-

That makes it easy to re-roll:

$ git co -b foo 20a0802~1
$ git apply -v --index 2664322-31.patch
$ git commit
$ git merge 8.2.x

The resulting patch (attached) applies to the current head of 8.2.x or 8.3.x. I do not think an interdiff is appropriate in this case.

I checked the interdiff attached to #31. It does affect only tests, and it does remove 'key_value_expire' from the array of modules to be enabled for the test, as advertised. In appropriate places, the setUp() method does not do anything else, so it is removed completely.

I again searched the code base for key_value and key_value_expire. I found one new test that installs the module in its setUp() method. I removed that: a second patch and an interdiff are attached. Let's see what the testbot has to say!

almaudoh’s picture

The 33-a.patch is so much bigger...

benjifisher’s picture

FileSize
43.16 KB

@almaudoh: Thanks for catching that. After making sure that my patch applied against 8.3.x, I made a diff against 8.2.x, so I accidentally included the differences between 8.2.x and 8.3.x in that patch.

I am hiding the bad patch and uploading a replacement. The interdiff on #33 shows the differences between 2664322-33.patch and 2664322-35.patch.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.0-beta1 was released on August 3, 2016, which means new developments and disruptive changes should now be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

dawehner’s picture

This patch looks almost ready to go!

  1. +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php
    @@ -323,12 +323,14 @@ public function preSave(EntityStorageInterface $storage) {
    diff --git a/core/lib/Drupal/Core/Field/FieldItemInterface.php b/core/lib/Drupal/Core/Field/FieldItemInterface.php
    
    diff --git a/core/lib/Drupal/Core/Field/FieldItemInterface.php b/core/lib/Drupal/Core/Field/FieldItemInterface.php
    index a257dd0..14cf9e9 100644
    
    index a257dd0..14cf9e9 100644
    --- a/core/lib/Drupal/Core/Field/FieldItemInterface.php
    
    --- a/core/lib/Drupal/Core/Field/FieldItemInterface.php
    +++ b/core/lib/Drupal/Core/Field/FieldItemInterface.php
    
    +++ b/core/lib/Drupal/Core/Field/FieldItemInterface.php
    +++ b/core/lib/Drupal/Core/Field/FieldItemInterface.php
    @@ -98,7 +98,7 @@ public function getEntity();
    
    @@ -98,7 +98,7 @@ public function getEntity();
       /**
        * Gets the langcode of the field values held in the object.
        *
    -   * @return string
    +   * @return $langcode
        *   The langcode.
        */
       public function getLangcode();
    diff --git a/core/lib/Drupal/Core/Field/FieldItemListInterface.php b/core/lib/Drupal/Core/Field/FieldItemListInterface.php
    
    diff --git a/core/lib/Drupal/Core/Field/FieldItemListInterface.php b/core/lib/Drupal/Core/Field/FieldItemListInterface.php
    index 2266c9c..e8bba02 100644
    
    index 2266c9c..e8bba02 100644
    --- a/core/lib/Drupal/Core/Field/FieldItemListInterface.php
    
    --- a/core/lib/Drupal/Core/Field/FieldItemListInterface.php
    +++ b/core/lib/Drupal/Core/Field/FieldItemListInterface.php
    
    +++ b/core/lib/Drupal/Core/Field/FieldItemListInterface.php
    +++ b/core/lib/Drupal/Core/Field/FieldItemListInterface.php
    @@ -44,7 +44,7 @@ public function setLangcode($langcode);
    
    @@ -44,7 +44,7 @@ public function setLangcode($langcode);
       /**
        * Gets the langcode of the field values held in the object.
        *
    -   * @return string
    +   * @return $langcode
        *   The langcode.
        */
       public function getLangcode();
    

    These are out of scope changes and are also a bit wrong :) (See https://www.drupal.org/node/1354 )

  2. +++ b/core/lib/Drupal/Core/KeyValueStore/DatabaseStorageExpirable.php
    @@ -34,71 +34,122 @@ public function __construct($collection, SerializationInterface $serializer, Con
    +      // @todo: Perhaps if the database is never going to be available,
    +      // key/value requests should return FALSE in order to allow exception
    +      // handling to occur but for now, keep it an array, always.
    +      $this->catchException($e);
    

    Do you think we could create its own follow up for just this?

  3. +++ b/core/modules/file/src/Entity/File.php
    @@ -188,11 +188,7 @@ public static function preCreate(EntityStorageInterface $storage, array &$values
    -    // The file itself might not exist or be available right now.
    -    $uri = $this->getFileUri();
    -    if ($size = @filesize($uri)) {
    -      $this->setSize($size);
    -    }
    +    $this->setSize(filesize($this->getFileUri()));
    
    +++ b/core/modules/file/src/Tests/FileManagedFileElementTest.php
    @@ -171,27 +171,4 @@ public function testManagedFileRemoved() {
    -  /**
    -   * Ensure a file entity can be saved when the file does not exist on disk.
    -   */
    -  public function testFileRemovedFromDisk() {
    -    $this->drupalGet('file/test/1/0/1');
    -    $test_file = $this->getTestFile('text');
    -    $file_field_name = 'files[nested_file][]';
    -
    -    $edit = [$file_field_name => drupal_realpath($test_file->getFileUri())];
    -    $this->drupalPostForm(NULL, $edit, t('Upload'));
    -    $this->drupalPostForm(NULL, array(), t('Save'));
    -
    -    $fid = $this->getLastFileId();
    -    /** @var $file \Drupal\file\FileInterface */
    -    $file = $this->container->get('entity_type.manager')->getStorage('file')->load($fid);
    -    $file->setPermanent();
    -    $file->save();
    -    $this->assertTrue(file_unmanaged_delete($file->getFileUri()));
    -    $file->save();
    -    $this->assertTrue($file->isPermanent());
    -    $file->delete();
    -  }
    -
    
    +++ b/core/modules/language/src/ContentLanguageSettingsInterface.php
    @@ -39,7 +39,7 @@ public function setTargetBundle($target_bundle);
        * @param string $default_langcode
        *   The default language code.
        *
    -   * @return $this
    +   * @return $this;
        */
       public function setDefaultLangcode($default_langcode);
    
    +++ b/core/modules/simpletest/src/WebTestBase.php
    @@ -2627,7 +2627,7 @@ protected function assertNoResponse($code, $message = '', $group = 'Browser') {
        * @param $override_server_vars
        *   An array of server variables to override.
        *
    -   * @return \Symfony\Component\HttpFoundation\Request
    +   * @return $request
        *   The mocked request object.
    
    +++ b/core/modules/views/src/Plugin/views/query/QueryPluginBase.php
    @@ -160,7 +160,7 @@ public function getLimit() {
        *   'where' or 'having'.
        *
    -   * @return
    +   * @return $group
        *   The group ID generated.
        */
    
    +++ b/core/modules/views/src/Plugin/views/query/Sql.php
    @@ -373,7 +373,7 @@ public function addRelationship($alias, JoinPluginBase $join, $base, $link_point
        *   A specific alias to use, rather than the default alias.
        *
    -   * @return string
    +   * @return $alias
        *   The alias of the table; this alias can be used to access information
        *   about the table and should always be used to refer to the table when
    
    @@ -412,7 +412,7 @@ public function addTable($table, $relationship = NULL, JoinPluginBase $join = NU
    -   * @return string
    +   * @return $alias
        *   The alias of the table; this alias can be used to access information
        *   about the table and should always be used to refer to the table when
        *   adding parts to the query. Or FALSE if the table was not able to be
    @@ -759,7 +759,7 @@ public function getTableInfo($table) {
    
    @@ -759,7 +759,7 @@ public function getTableInfo($table) {
        *   - aggregate: Set to TRUE to indicate that this value should be
        *     aggregated in a GROUP BY.
        *
    -   * @return string
    +   * @return $name
        *   The name that this field can be referred to as. Usually this is the alias.
        */
       public function addField($table, $field, $alias = '', $params = array()) {
    
    +++ b/core/phpcs.xml.dist
    @@ -45,6 +45,7 @@
       <rule ref="Drupal.Commenting.FunctionComment">
         <exclude name="Drupal.Commenting.FunctionComment.IncorrectTypeHint"/>
    +    <exclude name="Drupal.Commenting.FunctionComment.$InReturnType"/>
         <exclude name="Drupal.Commenting.FunctionComment.InvalidNoReturn"/>
         <exclude name="Drupal.Commenting.FunctionComment.InvalidReturnNotVoid"/>
    

    These changes also seem to be unrelated ...

dawehner’s picture

Status: Needs review » Needs work
almaudoh’s picture

Status: Needs work » Needs review
FileSize
43.16 KB

Just a reroll

almaudoh’s picture

Reverted unrelated changes in #37.1 and #37.3

Also raised #2787737: Finalize return value for KeyValueStoreInterface::getMultiple() for #37.2

almaudoh’s picture

Reverted some leftovers of unrelated changes in File.php. Also added the #2787737: Finalize return value for KeyValueStoreInterface::getMultiple() issue link in the todo.

dawehner’s picture

Nice, thank you for addressing those points!

alexpott’s picture

This change is looking pretty good. One thing that makes me hesitate is what happened when we made the change for url_alias. However I don't think that is a concern here because the state table will definitely exist by the end of the install. But still we should check that once this patch is applied requests to regular routes like node/1. Since if we're getting exceptions here then we'll slow down the critical path.

dawehner’s picture

FileSize
765 bytes

The only key_value entry we use more or less regularily is the system.cron_last key. Even in case we would not have the table installed after the installation, the first cron would trigger it, and we no longer have the exception.

I just checked a checkout of this patch and a new install, and we both had a key_value as well as key_value_expire in there. For key_value we have a lot of config.entity.key_store.action entries. For key_value_expire there is update module related stuff in by default.

I also attached a log of key_value::get(Multiple) calls, which happened after a drush cr;

Given that I'm quite convinced that there is no risk, unless with the url_alias example.

larowlan’s picture

+++ b/core/lib/Drupal/Core/Config/DatabaseStorage.php
@@ -181,7 +181,7 @@ protected function ensureTableExists()  {
+  public static function schemaDefinition() {

+++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php
@@ -323,12 +323,14 @@ public function preSave(EntityStorageInterface $storage) {
-    $matching_entities = $storage->getQuery()
-      ->condition('uuid', $this->uuid())
-      ->execute();
-    $matched_entity = reset($matching_entities);
-    if (!empty($matched_entity) && ($matched_entity != $this->id()) && $matched_entity != $this->getOriginalId()) {
-      throw new ConfigDuplicateUUIDException("Attempt to save a configuration entity '{$this->id()}' with UUID '{$this->uuid()}' when this UUID is already used for '$matched_entity'");
+    if ($uuid = $this->uuid()) {
+      $matching_entities = $storage->getQuery()
+        ->condition('uuid', $uuid)
+        ->execute();
+      $matched_entity = reset($matching_entities);
+      if (!empty($matched_entity) && ($matched_entity != $this->id()) && $matched_entity != $this->getOriginalId()) {
+        throw new ConfigDuplicateUUIDException("Attempt to save a configuration entity '{$this->id()}' with UUID '$uuid' when this UUID is already used for '$matched_entity'");
+      }

Are these unintended changes or are they because we've got tests for key value entity storage?

Other than that looks good to me

dawehner’s picture

This change was not related with the key-value entity storage, as that one is for content entities. This change fixes some of the test failures in #2664322-6: key_value table is only used by a core service but it depends on system install, but yeah I don't remember 100% the exact reason why it was needed. Fact is, the interface allows you to return NULL, so better not rely on the value :)

larowlan’s picture

Status: Needs review » Reviewed & tested by the community

works for me

catch’s picture

Status: Reviewed & tested by the community » Needs review
FileSize
35.59 KB

That change looked odd to me as well, uploading a patch without the hunk (essentially patch -p1 -R #45) to see what fails exactly.

Status: Needs review » Needs work

The last submitted patch, 48: 2664322-41-do-not-commit.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
4.47 KB
42.36 KB

I debugged for a while, why actually those failures occur. The problem is that the exported yml files don't have a uuid in them.
The problem is then that we setup the {config} table with raw data, so the uuid is never generated. Given that the fix is actually not needed and can be replaced by touching a couple of existing yml files and related code.

Status: Needs review » Needs work

The last submitted patch, 50: 2664322-50.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
43.06 KB
991 bytes

There was one uuid missing.

Status: Needs review » Needs work

The last submitted patch, 52: 2664322-52.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
43.06 KB

Just a quick reroll.

Status: Needs review » Needs work

The last submitted patch, 54: 2664322-54.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
43.33 KB

Forgot to use the --binary option.

alexpott’s picture

Status: Needs review » Reviewed & tested by the community

Nice - I think we should open an issue to add a SchemaDefinitionInterface so we can discover all the services which have schema for reporting and schema comparison.

catch’s picture

+++ b/core/lib/Drupal/Core/Config/DatabaseStorage.php
@@ -181,7 +181,7 @@ protected function ensureTableExists()  {
    */
-  protected static function schemaDefinition() {
+  public static function schemaDefinition() {
     $schema = array(
       'description' => 'The base table for configuration data.',
       'fields' => array(

Why is this change from protected to public in this patch? Looks unrelated.

dawehner’s picture

Nice - I think we should open an issue to add a SchemaDefinitionInterface so we can discover all the services which have schema for reporting and schema comparison.

I'm personally not convinced :) Schema definitions are kinda separated out in different areas already with the entity system. Having those few tables being part of some system, but some aren't is hard. Depending on the implementation #2371709: Move the on-demand-table creation into the database API though might give us that.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 56: 2664322-56.patch, failed testing.

dawehner’s picture

Status: Needs work » Reviewed & tested by the community

Just a random failure

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 56: 2664322-56.patch, failed testing.

dawehner’s picture

Status: Needs work » Reviewed & tested by the community

Random failure

catch’s picture

Status: Reviewed & tested by the community » Needs review

Still looking for an answer to #58.

dawehner’s picture

Well, let's try it out. I think its mostly about being consistent, as most other instances have that (KV needs it for some test \Drupal\system\Tests\Path\UrlAliasFixtures::tableDefinition), but sure, let's see whether we actually need it.

Status: Needs review » Needs work

The last submitted patch, 65: 2664322-65.patch, failed testing.

almaudoh’s picture

Status: Needs work » Needs review
FileSize
42.75 KB

Guess you forgot the --binary option again :) Same patch as in #65

almaudoh’s picture

Status: Needs review » Reviewed & tested by the community

Since #58 has been addressed, back to RTBC.

catch’s picture

Status: Reviewed & tested by the community » Fixed

Committed 5571193 and pushed to 8.3.x. Thanks!

  • catch committed 5571193 on 8.3.x
    Issue #2664322 by dawehner, benjifisher, almaudoh: key_value table is...
dawehner’s picture

Thanks a ton!

  • alexpott committed eaaea5d on 8.3.x
    Revert "Issue #2664322 by dawehner, benjifisher, almaudoh: key_value...
alexpott’s picture

Status: Fixed » Needs work

It turns out that this broke saving config entities without UUIDs. For example:

    $values = [
      'formId' => $form_id,
      'captchaType' => 'default',
      'status' => FALSE,
    ];
    $captcha_point = new CaptchaPoint($values, 'captcha_point');
    $captcha_point->trustData()->save();

From #2808087: Improve captcha_install(), correctly create dynamic captcha points, move static captcha points to default config

The problem is that when \Drupal\Core\Config\Entity\ConfigEntityBase::preSave() fires it assumes $this->uuid() returns a UUID but creating and saving config entities as above by passes the uuid assignment in entity storage create. We need to work out what to do here.

xjm’s picture

This also introduced a test failure on PHP7/Postgres so we should ensure any subsequent patch is tested against all environments.

dawehner’s picture

Status: Needs work » Needs review
FileSize
43.71 KB
2.18 KB

I think the right fix for this issue would be to return an empty array, if needed.

Status: Needs review » Needs work

The last submitted patch, 75: 2664322-74.patch, failed testing.

dawehner’s picture

mradcliffe’s picture

For some reason the connection class is trying to use PDOStatement instead of the statement class. This throws the HY00: General error: user-supplied statement does not accept constructor arguments error. I am not sure why the driver or our DBAL is switching back to PDOStatement.

All three queries being run, the watchdog insert query, the key_value store select query, and the table exists query are being run with PDOStatement. Everything else is using the defined statement class.

In C: https://github.com/php/php-src/blob/master/ext/pdo/pdo_dbh.c#L411

mradcliffe’s picture

ConnectionFailureTest calls Database::closeConnection();, which resets ATTR_STATEMENT_CLASS to PDOStatement. And then DBlog's custom connection thing might not be going through the right pathways to set it again.

I think the bug lies in the general Database/Connection classes rather than Watchdog module because Watchdog module shouldn't need to know about the intricacies of setting up the connection correctly to use Statement. It should be happening correctly in the constructor for a driver class, but it's not?

dawehner’s picture

@mradcliffe
What really confuses me is why this starts to fail as part of this patch. I mean it used to work fine without the patch, I guess.

mradcliffe’s picture

Yes, quite confusing.

This patch is probably not the correct approach, but to show that it's the problem.

Maybe a race condition in PDO object destruction and now since that test is doing more queries it uses an existing PDO object? Checking the statement class inside Dblog shows that it's Statement, but then checking inside prepareQuery shows that it's PDOStatement. If it is a race condition it would theoretically affect any driver using statement class.

Status: Needs review » Needs work

The last submitted patch, 81: 2664322-81.patch, failed testing.

mradcliffe’s picture

I had some weird changes locally and did not apply the patch in #77 correctly. Hiding my patch and interdiff.

The SQLite fail is valid as that driver doesn't override statementClass, which might be better if it did. I wanted to apply this generally because I have a sneaking suspicion that it could apply across the board in some rare cases. Or that we are hiding the error somewhere in exception handling.

I will apply the patch on a clean checkout and redo.

mradcliffe’s picture

Try with a clean patch.

Also an experimental patch that if it fails deserves its own issue. Edit: I expect sqlite to behave oddly, but we'll see if mysql and pgsql display an actual issue.

dawehner’s picture

@mradcliffe
So it seems to be that this is its own issue actually. Do you think we should open up its own issue and postpone this one, on the new one?

daffie’s picture

If I look at your statement-class-test.patch I see that you are trying to get the Statement class with the getAttribute() method. Looking at the PHP documentation for this, there are a number of PDO constants that can be used. And \PDO::ATTR_STATEMENT_CLASS is not one of them. So the construction that you are using is not supported by PHP. Maybe unofficial but not according to their manual (http://php.net/manual/en/pdo.getattribute.php). The constant \PDO::ATTR_STATEMENT_CLASS can only be used with the method setAttribute() to: "Set user-supplied statement class derived from PDOStatement. Cannot be used with persistent PDO instances. Requires array(string classname, array(mixed constructor_args)).". This is also according to the PHP manual. AFAIK a PDOStatement class is the result that you get after you run a PDO query.

mradcliffe’s picture

@dawehner, I retested #77 for php7 sqlite and got the same error as in #84 for php7 sqlite. I queued up #84 (pgsql and sqlite) and #77 (sqlite only) tests again in case these errors in #77 and #84 were random test failures.

Thanks, @daffie. It looks like it's a php docs bug. It's possible that it only applies on certain drivers: "Note that some database/driver combinations may not support all of the database connection attributes." Connection::__construct sets the attribute to use Statement class from a PDO Query, not PDOStatement. Drupal switches back to PDOStatement during Connection::destroy.

Edit: #77 and #84 still fail on SQLite (php7). Probably the same for PostgreSQL.

mradcliffe’s picture

PHP 7 isn't passing at the moment due to a PHP bug: https://www.drupal.org/node/2813981.

So i guess #84 is working for 5.5 and 5.6 mysql, sqlite, and pgsql. That's the patch that only adds a check in pgsql to ensure that Statement is the statement class instead of PDOStatement.

dawehner’s picture

@mradcliffe
So this is actually not the fault of this patch. Thank you for figuring that out!

almaudoh’s picture

Retesting on php7. So how do we want to proceed? Commit #84 as is? Or take those new parts to a new issue?

dawehner’s picture

We certainly need #84, because unlike, #77 this doesn't fail on PGSQL.

almaudoh’s picture

+    // the connection.
+    $statementClass = $this->connection->getAttribute(\PDO::ATTR_STATEMENT_CLASS);
+    if ($statementClass[0] <> $this->statementClass) {
+      $this->connection->setAttribute(\PDO::ATTR_STATEMENT_CLASS, array($this->statementClass, array($this)));
+    }

We should use !== instead of <>. Didn't realise that still works in php :)

dawehner’s picture

We should use !== instead of <>. Didn't realise that still works in php :)

Oh yeah that's right, @mradcliffe certainlys spends too much time in SQL land :)

almaudoh’s picture

Just the nitpicks. I guess I can RTBC this as I haven't made any changes since the revert.

dawehner’s picture

Oh yeah that should be fine. Thank you @almaudoh!

catch’s picture

Status: Reviewed & tested by the community » Needs review

It's good that the workaround works, but do we understand what the race condition is? And is it reproducible enough to post a PHP/PDO bug report for?

Status: Needs review » Needs work

The last submitted patch, 94: 2664322-94.patch, failed testing.

dawehner’s picture

@mradcliffe
Can you comment on that?

mradcliffe’s picture

Status: Needs work » Needs review

@catch, I don't have any specific data, but I think this is an application-level condition, not a PHP/PDO bug.

$connection is held open if there are any references to the object even when the destroy command is issued on it. I'm not sure where that could be coming from in the code base. A debug_zval_dump would help to prove that this is the case, but that doesn't help to figure out where it could be.

I added debug_zval_dump into ::destroy to see what it usually comes up with. During the test run of ConnectionFailureTest it is set to 2 reference counts in that method. Most likely those references are destroyed quickly in most use cases of database, but maybe not when Dblog is doing it?

dawehner’s picture

@mradcliffe
So do you think the question from catch is addressed and we could go back to RTBC here?

mradcliffe’s picture

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

Status: Reviewed & tested by the community » Needs work

I don't think we should add drivercode to cover a bug that's only reproducible in a test that's specifically messing with the database connection, or do it in this issue. Can we split this discussion out to a new issue and postpone this one?

  • catch committed 5571193 on 8.4.x
    Issue #2664322 by dawehner, benjifisher, almaudoh: key_value table is...
  • alexpott committed eaaea5d on 8.4.x
    Revert "Issue #2664322 by dawehner, benjifisher, almaudoh: key_value...

  • catch committed 5571193 on 8.4.x
    Issue #2664322 by dawehner, benjifisher, almaudoh: key_value table is...
  • alexpott committed eaaea5d on 8.4.x
    Revert "Issue #2664322 by dawehner, benjifisher, almaudoh: key_value...

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

benjifisher’s picture

Issue summary: View changes
Issue tags: +SprintWeekend2017, +SprintWeekendBOS

The patch from #67 was committed (5571193) and then reverted (eaaea5d for two reasons:

  1. This breaks saving config entities without UUIDs (#73).
  2. There are test failures with PHP7 and some database drivers (#74).

The first problem is addressed in #77, and later patches (up to #94) addressed the second problem.

There have been several suggestions (at least #85 and #102) to handle the second problem in a separate issue and to postpone this issue. I am updating the "Remaining Tasks" section of the issue summary.

benjifisher’s picture

I just created #2847855: Incorrect statement class caused by race condition when closing and opening the connection, added it as a related issue, and marked this issue as Postponed. Is there any way (other than this comment) to indicate that the related issue and the Postponed status are connected?

I did my best to describe the problem, but do not be shy about updating #2847855 if you have a better handle on it.

benjifisher’s picture

Issue summary: View changes
FileSize
1.91 KB
2.18 KB

In order to make reviews easier, I am attaching two interdiffs:

  • interdiff-67-77.txt shows the differences between the patch in #67 (which was committed and then reverted) and the one in #77.
  • interdiff-77-94.txt shows the differences between the patches in #77 and #94.

In fact, interdiff-67-77.txt is essentially the same as the interdiff attached to #74. (We used different methods to generate the interdiffs.) I think the only difference between #74 and #77 is that the second one was correctly generated with git diff --binary and the first without the --binary option.

The patch in #94 corrects a few typos in documentation comments. The code change that it introduces should be made in the new issue #2847855: Incorrect statement class caused by race condition when closing and opening the connection.

I think that further work on this issue should start with the patch in #77 (along with fixing the typos as in #94), which may need to be re-rolled.

benjifisher’s picture

Here is an attempt to re-roll the patch from #77, with the typo corrections from #94. Because of the changes in HEAD, I do not see an easy way to generate an interdiff. :-(

There was a merge conflict in EntityAutocompleteElementFormTest::setUp():

   protected function setUp() {
     parent::setUp();
 
-    $this->installSchema('system', ['key_value_expire']);
     \Drupal::service('router.builder')->rebuild();
 
     $this->testUser = User::create(array(

That snippet now looks like

   */
  protected function setUp() {
    parent::setUp();

    $this->installSchema('system', ['key_value_expire']);
    $this->installEntitySchema('entity_test_string_id');
    \Drupal::service('router.builder')->rebuild();

    $this->testUser = User::create(array(

so it is easy to see why the patch did not apply. Also easy to apply it manually.

The other conflict was in ConfigEntityStorageTest::testSaveMismatch(). This is more complicated, since that test was rewritten in #2824165: Remove brittleness from ConfigEntityStorageTest. But maybe the only reason that change was in the patch was to fix failing tests, in which case we may not have to deal with it in this issue.

I am curious to see which tests fail this time around. ;) So I am temporarily moving from Postponed to NR.

Status: Needs review » Needs work

The last submitted patch, 109: 2664322-109.patch, failed testing.

benjifisher’s picture

Status: Needs work » Needs review
FileSize
40.93 KB

Here is a new patch that, instead of giving up on on merge conflicts in ConfigEntityStorageTest , leaves that test completely alone. I expect that this will fix the test failures with MySQL.

I also removed the change to the dblog tests. If that fixes the test failures with Postgres, then we will have to re-think the idea that those are not really related to this issue.

dawehner’s picture

@benjifisher
Thank you for continuously working on it.

benjifisher’s picture

Issue summary: View changes
Status: Needs review » Postponed
FileSize
1.56 KB

@dawehner, it has been interesting!

I have attached an interdiff for the changes between #109 and #111. I already described the two small changes in #111.

Now that the testbot has spoken, I am moving this issue back to Postponed. I think the next step is to answer the question (#80, #96) why this patch triggers the PostgresQL error. In other words, we need to work on #2847855: Incorrect statement class caused by race condition when closing and opening the connection.

daffie’s picture

Status: Postponed » Needs review
FileSize
41.77 KB
906 bytes

Changing the implementation of the PostgreSQL version of the method Schema::tableExists to the same as the MySQL version fixes the problem in #2847855: Incorrect statement class caused by race condition when closing and opening the connection. Lets see if it does the same for this issue.

benjifisher’s picture

Let's give the testbot a workout. I expect PostgreSQL to work with various versions of PHP, but I do not know what to expect with SQLite.

See my comments in #111 and the interdiff in #114: only half of the changes I added in #111 should be kept.

As I suggested on #2847855: Incorrect statement class caused by race condition when closing and opening the connection I think that fixing tableExists() should be done in a separate issue, but I am really excited to see those test passes on this issue!

I would also like to address some of the comment from #17 before this issue is closed.

benjifisher’s picture

I did some debugging (reported on one of the related issues) and I now think we can fix this issue. We discovered some new issues while working on this, but this issue is no longer blocked by any of them. See my "fail" patch at #2847855-10: Incorrect statement class caused by race condition when closing and opening the connection, which reproduces the PostgreSQL test failure that has been blocking this issue.

This is why the test fails (answering @dawehner's question in #80):

  1. The Connection class has the wrong statementClass, leading to an exception.
  2. The patches on this issue change the exception handling in KeyValueStore\DatabaseStorage::getMultiple(). Previously, all exceptions were ignored. Earlier patches on this issue propagated the exception unless it seemed to be caused by a missing (to-be-lazy-loaded) database table.
  3. Different implementations of tableExists() give different results, which explains why the test fails only for PostgresQL.

I created #2847855: Incorrect statement class caused by race condition when closing and opening the connection to follow up on #1.
I just created #2850292: Improve exception handling indirectly in KeyValueStore\DatabaseStorage::getMultiple() method to follow up on #2.
@daffie has posted patches here and on #2847855: Incorrect statement class caused by race condition when closing and opening the connection related to #3. I suggest dealing with that problem in another issue: perhaps create a new one.

The attached patch reverts all changes to getMultiple(). Locally, this fixes the ConnectionFailureTest failure with PostgresQL. Like the patch in #111, it reverts all changes to ConfigEntityStorageTest because #2824165: Remove brittleness from ConfigEntityStorageTest already did a better job of fixing that test. Because of all that, I have attached an interdiff comparing to the re-roll in #109, not to one of the more recent patches.

I also plan to do some code cleanup once I have a patch that passes testing.

benjifisher’s picture

Here is a patch simplifying some of the try/catch blocks as I suggested in my review in #17. Unless I am missing something, this patch is functionally equivalent to the one in #116.

I have a little more cleanup to do, but this is tricky enough that I want to provide an interdiff that is not cluttered up with any other changes.

benjifisher’s picture

Ouch, I made two mistakes. One is the mistake that @almaudoh kept fixing on earlier patches: I forgot to use the --binary option when generating the patches in #116 and #117. Then I accidentally committed changes to composer.json and composer.lock in #117.

The attached patch fixes those problems and gives an interdiff against what #117 was supposed to be.

As the interdiff shows, this only does some cleanup: avoid mixing aray(...) and [...] syntax in the same file, in order to follow Drupal coding standards.

This is the last cleanup I have in mind, although I still plan to take another look at the patch.

benjifisher’s picture

Hiding patches (but not interdiffs) as explained in #118.

Status: Needs review » Needs work

The last submitted patch, 118: 2664322-118.patch, failed testing.

benjifisher’s picture

It does not help to introduce a syntax error. :-(

Again, I attach a patch and an interdiff against what I meant to upload in #117.

I need some sleep. That should give the testbot time to finish.

benjifisher’s picture

Sorry, I keep tweaking this.

I do not like the pattern

  public function foo(...) {
    try {
      // This is where the real work goes.
    }
    catch (\Exception $e) {
      if (...) {
        $this->foo(...);
      }
      else {
        throw $e;
      }
    }
  }

It is bad enough that we are using exceptions for control flow. (Where did I see a policy against that?) If the if(...) does the wrong thing, then we could be caught in an infinite loop. The attached patch replaces this pattern (twice each in DatabaseStorage and DatabaseStorageExpirable) with the pattern that I see in Batch\BatchStorage and Cache\DatabaseBackend:

  protected function doFoo(...) {
    // Do something useful.
  }

  public function foo(...) {
    try {
      return $this->doFoo(...);
    }
    catch (\Exception $e) {
      if (...) {
        $this->doFoo(...);
      }
      else {
        throw $e;
      }
    }
  }

Actually, my pattern is a little different from what I see in those two classes. As in #117, I simplify the control flow, removing the $try_again flag. With the simpler control flow, I think we do not need so many comments on the control flow, so I have removed several comment lines.

dawehner’s picture

It is bad enough that we are using exceptions for control flow. (Where did I see a policy against that?) If the if(...) does the wrong thing, then we could be caught in an infinite loop. The attached patch replaces this pattern (twice each in DatabaseStorage and DatabaseStorageExpirable) with the pattern that I see in Batch\BatchStorage and Cache\DatabaseBackend:

Well, for me its not that much of a difference at the end. But yeah, we have an infinite loop protection!

@benjifisher
Do you think we need to wait until pgsql is fixed?

benjifisher’s picture

Status: Needs review » Needs work
Issue tags: +Needs reroll

@dawehner: No, we do not have to wait for fixes to pgsql. See my comment in #116. And see all the green from the testbot. It needs a reroll, probably because of the short array syntax.

benjifisher’s picture

tstoeckler’s picture

+++ b/core/tests/Drupal/KernelTests/Core/KeyValueStore/StorageTestBase.php
@@ -94,6 +94,11 @@ public function testCRUD() {
+    $result = $stores[0]->getMultiple(array());
+    $this->assertEquals(array(), $result);

The testbot now complains about this not using the new array syntax.

benjifisher’s picture

Thanks for checking that! I was so worried about fixing the merge conflicts that I neglected to check the files where the merge was clean.

Here is a new version of the patch, and an interdiff.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

dawehner’s picture

I think adding uuids in all these places isn't the right solution either, this just hides the problem.

Berdir’s picture

+++ b/core/modules/views/tests/fixtures/update/views.view.test_boolean_filter_values.yml
@@ -1,4 +1,5 @@
 langcode: en
+uuid: 8C0C65A8-7099-4E90-9F6C-C5EE7BD3445D
 status: true

Also:

>>> \Drupal\Component\Uuid\Uuid::isValid('8C0C65A8-7099-4E90-9F6C-C5EE7BD3445D');
=> false

UUID's need to be lowercase.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

benjifisher’s picture

From #73:

It turns out that this broke saving config entities without UUIDs.

From #74:

This also introduced a test failure on PHP7/Postgres ...

I think we fixed the problem from #74, but we still need to fix the problem from #73. If I were sure of myself, then I would set this back to NW. Maybe the right thing to do is to add a failing test, and then the testbot will set the issue back to NW.

I see some recent activity on #2721271: Simplify exception handling for lazy-load pattern. If that issue gets in, then this patch will need a reroll.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

daffie’s picture

daffie’s picture

Rerolled the patch from comment #127.
Removed all the UUID stuff, because it was all in BC layers that where removed in Drupal 9.0. Therefor I have set this issue for 9.1
Removed from a couple of places in tests: $this->installSchema('system', ['key_value_expire']);.
The combined patch has the patches from #2847855: Incorrect statement class caused by race condition when closing and opening the connection and #2850292: Improve exception handling indirectly in KeyValueStore\DatabaseStorage::getMultiple() method.

The 2 problems named by @benjifisher in comment #135 are fixed (PHP7 on PostgreSQL) or are no longer relevant (config entities without UUIDs).

The last submitted patch, 138: 2664322-138-for-review-only.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

alexpott’s picture

+++ b/core/modules/field/tests/src/Kernel/FieldKernelTestBase.php
@@ -54,7 +54,7 @@ protected function setUp() {
-    $this->installSchema('system', ['sequences', 'key_value']);
+    $this->installSchema('system', ['sequences']);

So there are tonnes of these changes. Which makes it very likely this change will break tonnes of contrib and custom kernel tests. We need to handle this in a nicer way.

In \Drupal\KernelTests\KernelTestBase::installSchema we should trigger a silenced deprecation for removal in D10 that tells people that key_value and key_value_expire are no longer provided by the System module and are created on demand. That gives people most of D9 to upgrade their tests.

andypost’s picture

catch’s picture

+++ b/core/tests/Drupal/KernelTests/KernelTestBase.php
@@ -709,6 +709,13 @@ protected function installSchema($module, $tables) {
     $tables = (array) $tables;
     foreach ($tables as $table) {
+      // The tables key_value and key_value_expire are lazy loaded and therfor
+      // no longer have to be created with the installSchema() method.
+      // @see https://www.drupal.org/node/3143286
+      if ($module === 'system' && in_array($table, ['key_value', 'key_value_expire'])) {
+        @trigger_error('Installing the tables key_value and key_value_expire with the method KernelTestBase::installSchema() is deprecated in drupal:9.1.0 and is removed from drupal:10.0.0. The tables are now lazy loaded and therefor will be installed automatically when used. See https://www.drupal.org/node/3143286', E_USER_DEPRECATED);
+        continue;
+      }
       $schema = drupal_get_module_schema($module, $table);

It'd be nice if there was a way for contrib to be able to do this too - might be worth a follow-up? I think the hard-coding is fine for this patch though, this is a very low-level API and it would only help test authors writing kernel tests with dependencies on schema from contrib modules they don't control.

benjifisher’s picture

Looking at the snippet in #145, I think it should be therefore instead of therefor (twice).

BTW, I wish I had time to contribute more to this issue recently. A lot of people put a lot of effort into it (including me, a few years ago). I am happy to see that we are getting close to getting some benefit from that effort. Thanks for moving the issue forward!

daffie’s picture

For #145: Created the followup #3143448: Allow contrib modules to overrule the working of KernelTestBase::installSchema().

For #146: Fixed. Why do easy when you can do complicated. Sorry, I am not a native English speaker.

daffie’s picture

Issue tags: +blocker
quietone’s picture

Issue summary: View changes

Had a go at updating the IS, the proposed resolution and remaining tasks.

quietone’s picture

Issue summary: View changes

Fix formatting in IS

quietone’s picture

Issue summary: View changes

Another format fix to the IS.

quietone’s picture

Status: Needs review » Needs work
Issue tags: +Needs reroll

Patch no longer applies to 9.1.x and there is a coding standard error.

daffie’s picture

Assigned: Unassigned » daffie
Status: Needs work » Needs review

Working on a reroll.

daffie’s picture

Status: Needs review » Needs work
daffie’s picture

Assigned: daffie » Unassigned
Status: Needs work » Needs review
Issue tags: -Needs reroll
FileSize
48.39 KB

Rerolled the patch and fixed the coding standard errors.

quietone’s picture

Issue summary: View changes
Status: Needs review » Needs work

I've reviewed the patch paying particular attention to the comments and doc blocs and only found a few things. :-)

  1. +++ b/core/lib/Drupal/Core/KeyValueStore/DatabaseStorage.php
    @@ -149,20 +219,98 @@ public function rename($key, $new_key) {
    +   * Check if the table exists and create it if not.
    ...
    +  protected function ensureTableExists() {
    

    Needs an @return

  2. +++ b/core/lib/Drupal/Core/KeyValueStore/DatabaseStorage.php
    @@ -149,20 +219,98 @@ public function rename($key, $new_key) {
    +    // If the table already exists, then attempting to recreate it will throw an
    +    // exception. In this case just catch the exception and do nothing.
    

    Why is is better to do this instead of checking if the tables exists first?

  3. +++ b/core/lib/Drupal/Core/KeyValueStore/DatabaseStorage.php
    @@ -149,20 +219,98 @@ public function rename($key, $new_key) {
    +   * Act on an exception when the table might not have been created.
    +   *
    +   * If the table does not yet exist, that's fine, but if the table exists and
    +   * something else caused the exception, then propagate it.
    

    I am having difficulty with this text. Can we simplify it a bit? And expand the 'that's fine' to an explanation. Why is it fine to ignore the exception?

    Act on an exception when the table might not exist.
    
    If the table does not exist ignore the exception because ... If the table exists propagate the exception. 

    Same applies to catchException in KeyValueStore/KeyValueDatabaseExpirableFactory.php.

  4. +++ b/core/modules/system/system.install
    @@ -1404,7 +1339,7 @@ function system_schema() {
    -     ],
    +    ],
    

    Nit, out of scope change.

Removed the item and therefor/therefore from the IS because that is complete. It was my mistake that added it the IS.

daffie’s picture

@quietone: Thank you for your review.

For #157.1: Fixed.

For #157.2: For > 99% of the time that this method is called the table will not exists. Every time we check if a table exists it will result in a query to the database. Which we like to do a few as possible. Also other database storage classes where the table is lazy loaded, have the same method ensureTableExists(). They all work in the same way.

For #157.3: When a select, update or delete query fails, because the table does not exists, that is not a problem. The table is created by lazy loading. Therefore an exception must be caught and suppressed when it is the result of a non-existing table. I did not create the text and the current text is very much in line with the other methods in core with the same name. Also I am not a native English speaker, so if you have any suggestion how to improve the text then please say so.

For #157.4: Removed the change.

daffie’s picture

As discussed with @quietone on Slack, 2 docblock texts are updated.

@quietone: Thank you for your help!

quietone’s picture

@daffie, your welcome!

I reviewed the changes in response to #157 and they have all been addressed or fixed as needed. I think the patch looks good and is ready for RTBC but this is not an area I have enough knowledge in to set to RTBC.

catch’s picture

Status: Needs review » Reviewed & tested by the community

This looks great to me now, looked over older comments and can't believe it's 4 years since we had to revert the original patch.

daffie’s picture

Thank you @catch for the RTBC!

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed faabcf1 and pushed to 9.1.x. Thanks!

I tried to re-create the problem in #73 on 9.1.x. Firstly I can the captcha tests with this patch and they passed and then i ran that code. We successfully created a config entity with a NULL uuid - which is wrong but that's what we've told the system to do and it's not related to the this issue. The state query we make on config entity save does not appear to be broken by this change so that's great.

  • alexpott committed faabcf1 on 9.1.x
    Issue #2664322 by benjifisher, dawehner, daffie, almaudoh, mradcliffe,...
daffie’s picture

Thanks to everybody who worked on this issue!!!

Status: Fixed » Closed (fixed)

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