Skip to content
Snippets Groups Projects
Commit 61530261 authored by catch's avatar catch
Browse files

Issue #3405976 by alexpott, jrglasgow, fago, catch, mondrake, solideogloria,...

Issue #3405976 by alexpott, jrglasgow, fago, catch, mondrake, solideogloria, mglaman, Driskell, derickr, longwave, Mile23, YesCT, daffie: Transaction autocommit during shutdown relies on unreliable object destruction order (xdebug 3.3+ enabled)

(cherry picked from commit 26308296)
parent 5b72d300
No related branches found
No related tags found
36 merge requests!13092Issue #3498963 by benjifisher, heddn: Add lookup_migrations configuration to...,!12802Issue #3537193 by opauwlo: Add enable absolute path option for CKEditor5 image uploads,!12745Fixed: Path alias language doesn't changes on changing of node language,!12684Issue #3220784,!12537Add ViewsConfigUpdater deprecation support for default_argument_skip_url,!12523Issue #3493858 by vidorado, xavier.masson, smustgrave: Extend ViewsBlockBase...,!122353526426-warning-for-missing,!12212Issue #3445525 by alexpott, japerry, catch, mglaman, longwave: Add BC layer...,!11958Issue #3490507 by alexpott, smustgrave: Fix bogus mocking in...,!11769Issue #3517987: Add option to contextual filters to encode slashes in query parameter.,!11185Issue #3477324 by andypost, alexpott: Fix usage of str_getcsv() and fgetcsv() for PHP 8.4,!10602Issue #3438769 by vinmayiswamy, antonnavi, michelle, amateescu: Sub workspace does not clear,!10301Issue #3469309 by mstrelan, smustgrave, moshe weitzman: Use one-time login...,!10187Issue #3487488 by dakwamine: ExtensionMimeTypeGuesser::guessMimeType must support file names with "0" (zero) like foo.0.zip,!9944Issue #3483353: Consider making the createCopy config action optionally fail...,!9929Issue #3445469 by pooja_sharma, smustgrave: Add additional test coverage for...,!9787Resolve issue 3479427 - bootstrap barrio issue under Windows,!9742Issue #3463908 by catch, quietone: Split OptionsFieldUiTest into two,!9526Issue #3458177 by mondrake, catch, quietone, godotislate, longwave, larowlan,...,!8738Issue #3424162 by camilledavis, dineshkumarbollu, smustgrave: Claro...,!8704Make greek characters available in ckeditor5,!8597Draft: Issue #3442259 by catch, quietone, dww: Reduce time of Migrate Upgrade tests...,!8533Issue #3446962 by kim.pepper: Remove incorrectly added...,!8517Issue #3443748 by NexusNovaz, smustgrave: Testcase creates false positive,!8325Update file Sort.php,!8095Expose document root on install,!7930Resolve #3427374 "Taxonomytid viewsargumentdefault plugin",!7627Issue #3439440 by nicxvan, Binoli Lalani, longwave: Remove country support from DateFormatter,!7445Issue #3440169: When using drupalGet(), provide an associative array for $headers,!7401#3271894 Fix documented StreamWrapperInterface return types for realpath() and dirname(),!7384Add constraints to system.advisories,!7078Issue #3320569 by Spokje, mondrake, smustgrave, longwave, quietone, Lendude,...,!6622Issue #2559833 by piggito, mohit_aghera, larowlan, guptahemant, vakulrai,...,!6502Draft: Resolve #2938524 "Plach testing issue",!38582585169-10.1.x,!3226Issue #2987537: Custom menu link entity type should not declare "bundle" entity key
Pipeline #101217 passed
Pipeline: drupal

#101218

    ......@@ -292,6 +292,31 @@ public function __destruct() {
    $this->connection = NULL;
    }
    /**
    * Commits all the open transactions.
    *
    * @internal
    * This method exists only to work around a bug caused by Drupal incorrectly
    * relying on object destruction order to commit transactions. Xdebug 3.3.0
    * changes the order of object destruction when the develop mode is enabled.
    */
    public function commitAll() {
    $manager = $this->transactionManager();
    if ($manager && $manager->inTransaction() && method_exists($manager, 'commitAll')) {
    $this->transactionManager()->commitAll();
    }
    // BC layer.
    // @phpstan-ignore-next-line
    if (!empty($this->transactionLayers)) {
    // Make all transactions committable.
    // @phpstan-ignore-next-line
    $this->transactionLayers = array_fill_keys(array_keys($this->transactionLayers), FALSE);
    // @phpstan-ignore-next-line
    $this->popCommittableTransactions();
    }
    }
    /**
    * Returns the client-level database connection object.
    *
    ......
    ......@@ -486,10 +486,18 @@ public static function closeConnection($target = NULL, $key = NULL) {
    if (!isset($key)) {
    $key = self::$activeKey;
    }
    if (isset($target)) {
    if (isset($target) && isset(self::$connections[$key][$target])) {
    if (self::$connections[$key][$target] instanceof Connection) {
    self::$connections[$key][$target]->commitAll();
    }
    unset(self::$connections[$key][$target]);
    }
    else {
    elseif (isset(self::$connections[$key])) {
    foreach (self::$connections[$key] as $connection) {
    if ($connection instanceof Connection) {
    $connection->commitAll();
    }
    }
    unset(self::$connections[$key]);
    }
    // Force garbage collection to run. This ensures that client connection
    ......@@ -740,4 +748,45 @@ private static function isWithinModuleNamespace(string $namespace) {
    return ($first === 'Drupal' && strtolower($second) === $second);
    }
    /**
    * Calls commitAll() on all the open connections.
    *
    * If drupal_register_shutdown_function() exists the commit will occur during
    * shutdown so that it occurs at the latest possible moment.
    *
    * @param bool $shutdown
    * Internal param to denote that the method is being called by
    * _drupal_shutdown_function().
    *
    * @return void
    *
    * @internal
    * This method exists only to work around a bug caused by Drupal incorrectly
    * relying on object destruction order to commit transactions. Xdebug 3.3.0
    * changes the order of object destruction when the develop mode is enabled.
    */
    public static function commitAllOnShutdown(bool $shutdown = FALSE): void {
    static $registered = FALSE;
    if ($shutdown) {
    foreach (self::$connections as $targets) {
    foreach ($targets as $connection) {
    if ($connection instanceof Connection) {
    $connection->commitAll();
    }
    }
    }
    return;
    }
    if (!function_exists('drupal_register_shutdown_function')) {
    return;
    }
    if (!$registered) {
    $registered = TRUE;
    drupal_register_shutdown_function('\Drupal\Core\Database\Database::commitAllOnShutdown', TRUE);
    }
    }
    }
    ......@@ -57,6 +57,11 @@ public function __construct(
    $name = NULL,
    protected readonly string $id = '',
    ) {
    // Transactions rely on objects being destroyed in order to be committed.
    // PHP makes no guarantee about the order in which objects are destroyed so
    // ensure all transactions are committed on shutdown.
    Database::commitAllOnShutdown();
    if ($connection->transactionManager()) {
    $this->connection = $connection;
    $this->name = $name;
    ......
    ......@@ -127,6 +127,20 @@ protected function stack(): array {
    return $this->stack;
    }
    /**
    * Commits the entire transaction stack.
    *
    * @internal
    * This method exists only to work around a bug caused by Drupal incorrectly
    * relying on object destruction order to commit transactions. Xdebug 3.3.0
    * changes the order of object destruction when the develop mode is enabled.
    */
    public function commitAll(): void {
    foreach (array_reverse($this->stack()) as $id => $item) {
    $this->unpile($item->name, $id);
    }
    }
    /**
    * Adds an item to the transaction stack.
    *
    ......@@ -253,7 +267,6 @@ public function unpile(string $name, string $id): void {
    // DDL statement breaking an active transaction). That should be listed in
    // $voidedItems, so we can remove it from there.
    if (!isset($this->stack()[$id]) || $this->stack()[$id]->name !== $name) {
    assert(isset($this->voidedItems[$id]), "Transaction {$id}/{$name} is out of sequence. Active stack: " . $this->dumpStackItemsAsString());
    unset($this->voidedItems[$id]);
    return;
    }
    ......
    ......@@ -4,8 +4,10 @@
    use Drupal\Core\Database\Database;
    use Drupal\Core\Database\Transaction;
    use Drupal\Core\Database\Transaction\ClientConnectionTransactionState;
    use Drupal\Core\Database\Transaction\StackItem;
    use Drupal\Core\Database\Transaction\StackItemType;
    use Drupal\Core\Database\Transaction\TransactionManagerBase;
    use Drupal\Core\Database\TransactionExplicitCommitNotAllowedException;
    use Drupal\Core\Database\TransactionNameNonUniqueException;
    use Drupal\Core\Database\TransactionOutOfOrderException;
    ......@@ -807,11 +809,24 @@ public function testTransactionManagerFailureOnPendingStackItems(): void {
    $testConnection = Database::getConnection('test_fail');
    // Add a fake item to the stack.
    $reflectionMethod = new \ReflectionMethod(get_class($testConnection->transactionManager()), 'addStackItem');
    $reflectionMethod->invoke($testConnection->transactionManager(), 'bar', new StackItem('qux', StackItemType::Savepoint));
    $manager = $testConnection->transactionManager();
    $reflectionMethod = new \ReflectionMethod($manager, 'addStackItem');
    $reflectionMethod->invoke($manager, 'bar', new StackItem('qux', StackItemType::Root));
    // Ensure transaction state can be determined during object destruction.
    // This is necessary for the test to pass when xdebug.mode has the 'develop'
    // option enabled.
    $reflectionProperty = new \ReflectionProperty(TransactionManagerBase::class, 'connectionTransactionState');
    $reflectionProperty->setValue($manager, ClientConnectionTransactionState::Active);
    $this->expectException(\AssertionError::class);
    $this->expectExceptionMessageMatches("/^Transaction .stack was not empty\\. Active stack: bar\\\\qux/");
    // Ensure that __destruct() results in an assertion error. Note that this
    // will normally be called by PHP during the object's destruction but Drupal
    // will commit all transactions when a database is closed thereby making
    // this impossible to test unless it is called directly.
    $manager->__destruct();
    // Clean up.
    unset($testConnection);
    Database::closeConnection('test_fail');
    }
    ......@@ -854,6 +869,10 @@ public function testConnectionDeprecations(): void {
    $this->expectDeprecation('Drupal\\Core\\Database\\Connection::popCommittableTransactions() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use TransactionManagerInterface methods instead. See https://www.drupal.org/node/3381002');
    $this->expectDeprecation('Drupal\\Core\\Database\\Connection::doCommit() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use TransactionManagerInterface methods instead. See https://www.drupal.org/node/3381002');
    $this->connection->popTransaction('foo');
    // Ensure there are no outstanding transactions left. This is necessary for
    // the test to pass when xdebug.mode has the 'develop' option enabled.
    $this->connection->commitAll();
    }
    }
    0% Loading or .
    You are about to add 0 people to the discussion. Proceed with caution.
    Please to comment