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.