Quantcast
Viewing all articles
Browse latest Browse all 294835

Postgres driver issue with nested savepoints mimic_implicit_commit duplicated

We're having an issue with Workbench Moderation and Group installed and have traced it back to an issue with the Postgres db driver. The Postgres driver automatically creates savepoints named "mimic_implicit_commit" for most queries running inside of an ongoing transaction.

The problem occurs when a DB query is run that triggers another DB query in the first's preExecute() phase (typically a hook such as hook_node_grants). This creates a situation where a new savepoint also called "mimic_implicit_commit" is added before the original "mimic_implicit_commit" savepoint is released. pushTransaction() doesn't allow for duplicate savepoint names.

The easiest way to re-create is to install/enable Workbench Moderation and then have any module that implements hook_node_grants() with a DB query using the ->execute() method enabled as well (such as Group/Group Node). When you add try to add content as a user without "bypass node access", you'll get this error message when you try to save:

Drupal\Core\Entity\EntityStorageException: mimic_implicit_commit is already in use. in Drupal\Core\Entity\Sql\SqlContentEntityStorage->save() (line 777 of core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php).

Here's some additional information about the problem. Basically all DB queries funnel into the pgsql\Select class's execute() method, which arbitrarily runs addSavepoint($savepoint_name = 'mimic_implicit_commit')

https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Database%...

# Drupal\Core\Database\Driver\pgsql\Select
public function execute() {
  $this->connection->addSavepoint();
  try {
    $result = parent::execute();
  }
  catch (\Exception $e) {
    $this->connection->rollbackSavepoint();
    throw $e;
  }
  $this->connection->releaseSavepoint();

  return $result;
}

pgsql\Select\execute() is called from many different places. Only one path via the pgsql/Connection query() method does it actually check for the duplicate 'mimic_implicit_commit' name:
https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Database%...

# Drupal\Core\Database\Driver\pgsql\Connection
public function query($query, array $args = [], $options = []) {
  $options += $this->defaultOptions();

  // The PDO PostgreSQL driver has a bug which doesn't type cast booleans
  // correctly when parameters are bound using associative arrays.
  // @see http://bugs.php.net/bug.php?id=48383
  foreach ($args as &$value) {
    if (is_bool($value)) {
      $value = (int) $value;
    }
  }

  // We need to wrap queries with a savepoint if:
  // - Currently in a transaction.
  // - A 'mimic_implicit_commit' does not exist already.
  // - The query is not a savepoint query.
  $wrap_with_savepoint = $this->inTransaction() &&
    !isset($this->transactionLayers['mimic_implicit_commit']) &&
    !(is_string($query) && (
    stripos($query, 'ROLLBACK TO SAVEPOINT ') === 0 ||
    stripos($query, 'RELEASE SAVEPOINT ') === 0 ||
    stripos($query, 'SAVEPOINT ') === 0
    )
    );
  if ($wrap_with_savepoint) {
    // Create a savepoint so we can rollback a failed query. This is so we can
    // mimic MySQL and SQLite transactions which don't fail if a single query
    // fails. This is important for tables that are created on demand. For
    // example, \Drupal\Core\Cache\DatabaseBackend.
    $this->addSavepoint();
    try {
      $return = parent::query($query, $args, $options);
      $this->releaseSavepoint();
    }
    catch (\Exception $e) {
      $this->rollbackSavepoint();
      throw $e;
    }
  }
  else {
    $return = parent::query($query, $args, $options);
  }

  return $return;
}

However, both Groups and Workbench moderation call the pgsql/Select execute() method directly, bypassing the duplicate check for mimic_implicit_commit in pgsql/Connection query().

Groups:

#0  Drupal\Core\Database\Driver\pgsql\Select->execute() called at [/opt/tbs/wcms/open_gov/web/current/html/core/lib/Drupal/Core/Config/DatabaseStorage.php:273]
#1  Drupal\Core\Config\DatabaseStorage->listAll() called at [/opt/tbs/wcms/open_gov/web/current/html/core/lib/Drupal/Core/Config/CachedStorage.php:207]
#2  Drupal\Core\Config\CachedStorage->findByPrefix() called at [/opt/tbs/wcms/open_gov/web/current/html/core/lib/Drupal/Core/Config/CachedStorage.php:183]
#3  Drupal\Core\Config\CachedStorage->listAll() called at [/opt/tbs/wcms/open_gov/web/current/html/core/lib/Drupal/Core/Config/ConfigFactory.php:328]
#4  Drupal\Core\Config\ConfigFactory->listAll() called at [/opt/tbs/wcms/open_gov/web/current/html/core/lib/Drupal/Core/Config/Entity/Query/Query.php:172]
#5  Drupal\Core\Config\Entity\Query\Query->loadRecords() called at [/opt/tbs/wcms/open_gov/web/current/html/core/lib/Drupal/Core/Config/Entity/Query/Query.php:82]
#6  Drupal\Core\Config\Entity\Query\Query->execute() called at [/opt/tbs/wcms/open_gov/web/current/html/core/lib/Drupal/Core/Entity/EntityStorageBase.php:503]
#7  Drupal\Core\Entity\EntityStorageBase->loadByProperties() called at [/opt/tbs/wcms/open_gov/web/current/html/modules/contrib/group/src/GroupMembershipLoader.php:146]
#8  Drupal\group\GroupMembershipLoader->loadByUser() called at [/opt/tbs/wcms/open_gov/web/current/html/modules/contrib/group/modules/gnode/gnode.module:169]

Workbench:

#0  Drupal\Core\Database\Driver\pgsql\Select->execute() called at [/opt/tbs/wcms/open_gov/web/current/html/core/lib/Drupal/Core/Entity/Query/Sql/Query.php:250]
#1  Drupal\Core\Entity\Query\Sql\Query->result() called at [/opt/tbs/wcms/open_gov/web/current/html/core/lib/Drupal/Core/Entity/Query/Sql/Query.php:77]
#2  Drupal\Core\Entity\Query\Sql\Query->execute() called at [/opt/tbs/wcms/open_gov/web/current/html/modules/contrib/workbench_moderation/src/ModerationInformation.php:166]
#3  Drupal\workbench_moderation\ModerationInformation->getLatestRevisionId() called at [/opt/tbs/wcms/open_gov/web/current/html/modules/contrib/workbench_moderation/src/ModerationInformation.php:151]

The pgsql driver may need to be patched to handle the duplicate savepoint condition when Query class's execute() function are accessed directly.


Viewing all articles
Browse latest Browse all 294835

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>