Problem/Motivation

Follow up of #3398767: Allow returning explicitly to the prior nesting level in transactions (aka allow explicit COMMIT in Transaction objects).

Right now, most transactions in Drupal core are COMMITed or a RELEASEd SAVEPOINT only when their Transaction object goes out of scope. In practice, it's an autocommit.

This has caused, along time, several issues - see #1025314: Transactions should be allowed to be committed explicitly for a lot of rationale on why this is not optimal. More recently, usage of xdebug is impaired and not solved yet when transaction are involved - see #3405976: Transaction autocommit during shutdown relies on unreliable object destruction order (xdebug 3.3+ enabled).

In the parent issue we introduced the possibility to explicitly commit Transaction objects.

Xdebug testing

Manual testing an environment with xdebug 3.3 or greater enabled to make sure that we do not get any longer failures related to the unpredictable object destruction sequence. xdebug should be in develop mode, not debug mode for testing.

Potential testing scenarios from #3405976: Transaction autocommit during shutdown relies on unreliable object destruction order (xdebug 3.3+ enabled):

  • @pacproduct: Our installation process is entirely run on command-line via Drush commands, and one of them (applying a recipe: ddev recipe-apply recipes/our_recipe ddev drush recipe ../recipes/name-of-recipe) fails right at the end with the following error....
  • @joelpittet: working on a test in og module (potentially this is from reviewing a MR that became og_migrate).

Proposed resolution

In this issue we convert all Drupal core transactions to use explicit commit.

In follow ups:

Remaining tasks

User interface changes

No

API changes

No

Data model changes

No

Release notes snippet

Issue fork drupal-3406985

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

mondrake created an issue. See original summary.

mondrake changed the visibility of the branch 3406985-pp-1-convert-all to hidden.

mondrake changed the visibility of the branch 3406985-pp-1-convert-all to active.

mondrake’s picture

Issue summary: View changes

mondrake changed the visibility of the branch 3406985-pp-1-convert-all to hidden.

mondrake changed the visibility of the branch 3406985-pp-1-convert-all to active.

mondrake changed the visibility of the branch 3406985-pp-1-convert-all to hidden.

mondrake changed the visibility of the branch 3406985-pp-1-convert-all to active.

mondrake’s picture

Title: [PP-1] Convert all transactions to explicit COMMIT » [PP-1] Convert all transactions in core to use explicit ::commitOrRelease(), deprecate implicit commit-on-destruct
nicxvan’s picture

It would be helpful to have a small summary on the why in the issue summary

mondrake’s picture

Title: [PP-1] Convert all transactions in core to use explicit ::commitOrRelease(), deprecate implicit commit-on-destruct » Convert all transactions in core to use explicit ::commitOrRelease(), deprecate implicit commit-on-destruct
Status: Postponed » Active
Issue tags: +Needs issue summary update

Blocker is in, tagging per #12.

mondrake’s picture

Issue summary: View changes
Status: Active » Needs review
Issue tags: -Needs issue summary update

Updated IS.

mondrake’s picture

Status: Needs review » Needs work
mondrake’s picture

Status: Needs work » Needs review

made suggested changes, thanks @alexpott

mondrake’s picture

Added specific deprecation tests for the implicit commit behavior and for ::commitAll() when there are pending transactions open.

smustgrave’s picture

Status: Needs review » Needs work

Feedback appears to be addressed but CR is still tbd

mondrake’s picture

Status: Needs work » Needs review

Fleshed the CR

mondrake’s picture

Issue tags: +Needs manual testing

Per https://drupal.slack.com/archives/C1BMUQ9U6/p1747310372256759?thread_ts=..., the MR could use some manual testing to check that with xdebug 3.3+ enabled we do not get any longer failures related to the unpredictable object destruction sequence.

mondrake’s picture

Addressed review comments.

mradcliffe’s picture

StatusFileSize
new12.53 KB

Current test failures are deprecations for

1) /builds/issue/drupal-3406985/core/lib/Drupal/Core/Test/HttpClientMiddleware/TestHttpClientMiddleware.php:52
Database commit by letting a Transaction object go out of scope is deprecated in drupal:11.3.0 and is removed from drupal:13.0.0. Commit explicitly via Transaction::commitOrRelease() instead. See https://www.drupal.org/node/3524461

1) /builds/issue/drupal-3406985/core/lib/Drupal/Core/Database/Transaction/TransactionManagerBase.php:364
Database commit by letting a Transaction object go out of scope is deprecated in drupal:11.3.0 and is removed from drupal:13.0.0. Commit explicitly via Transaction::commitOrRelease() instead. See https://www.drupal.org/node/3524461

Uploaded as a text file after parsing through Functional and FunctionalJavaScript test results. Summarized as

- locale
- config_translation
- AssetOptimizationUmamiTest
- help
- demo_umami
- core Installer tests

mondrake’s picture

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

Some recent commits may have added deprecated behaviour. Needs sorting out which. On that.

mondrake’s picture

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

Culprit was #3520750: Use a transaction PoDatabaseWriter to improve performance. But that helped proving that deprecation of implicit commit works.

mradcliffe’s picture

Issue summary: View changes

I added some manual testing scenarios for xdebug 3.3+ in develop mode.

smustgrave’s picture

With regards to #20 any suggestion on how to test that?

mondrake’s picture

@ceonizm confirmed that the MR here fixes #3538834: Error at the end of any PHPunit test

mondrake’s picture

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new90 bytes

The Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

mondrake’s picture

Status: Needs work » Needs review

rebased

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new90 bytes

The Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

mondrake’s picture

Status: Needs work » Needs review
ghost of drupal past’s picture

  1. Maybe consider adding a few words to the CR as to why? "Drupal by default was committing a database transaction when its Transaction object goes out of scope, during the Transaction object destruction." and then add something to the tunes of "This causes problems with unpredictable object destruction order especially under xdebug 3.3+ in develop mode" because at first glance it's not at all understandable why such a boilerplate-y thing needs to be added -- most code doesn't want to return its transaction, destruction seems to be the right time to do it.
  2. David nuked direct commit in #301049: Transaction nesting not tracked by connection were those problems considered? (DBTNG is mostly Crell and me which is the only reason I comment here -- but the transaction code is David's. Back then we didn't have the experience necessary for that, he did.)
mondrake’s picture

Version: 11.x-dev » main
mondrake’s picture

godotislate’s picture

Are there an easily reproducible steps with XDebug enabled in develop mode to test this? I've read through the examples in the IS, as well as comments from #3405976: Transaction autocommit during shutdown relies on unreliable object destruction order (xdebug 3.3+ enabled) and #3451460: Sessions are invalidated on subsequent page request when written while transaction is open, but reproducing seems to be non-trivial or requires contrib or custom code that's not specified.

Meanwhile, the references to 11.3.0 in the deprecation messages need updating to 11.4.0, and the version set in the CR needs updating as well.

mondrake’s picture

Meanwhile, the references to 11.3.0 in the deprecation messages need updating to 11.4.0, and the version set in the CR needs updating as well.

Let's fix that when someone is ready to RTBC the issue.

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new90 bytes

The Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

mondrake’s picture

Status: Needs work » Needs review

Merged with main; a conflict in PgSQL Insert class to resolve after a0acc2c1005d9470745f140672d70c0905494212

godotislate’s picture

I've found some test steps with consistently repeatable failures that the changes in the MR here do not solve. It seems related, but not I'm not completely sure. I can file a separate issue if appropriate.

Local env:
DDEV v1.24.10
Using ddev-core-dev add-on: https://github.com/justafish/ddev-drupal-core-dev
DB version SQLite 3.45.1
Xdebug v3.5.0
xdebug.mode = develop
PHP 8.5.2

Steps:
Checkout MR branch
Install Drupal core with standard: ddev drush si standard -y
Uninstall Toolbar ddev drush pmu toolbar
Install Navigation ddev drush en navigation -y
Enable Xdebug ddev xdebug
Rebuild cache ddev drush cr
Observe exception:

Failed to log error: TypeError: Drupal\Core\Database\StatementPrefetchIterator::__construct(): Argument #1 ($clientConnection) must be of type object, null given, called in /var/www/html/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php on line 429 in Drupal\Core\Database\StatementPrefetchIterator->__construct() (line 36 of /var/www/html/core/lib/Drupal/Core/Database/StatementPrefetchIterator.php). #0 /var/www/html/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php(429): Drupal\Core\Database\StatementPrefetchIterator->__construct()
#1 /var/www/html/core/lib/Drupal/Core/Database/Connection.php(667): Drupal\sqlite\Driver\Database\sqlite\Connection->prepareStatement()
#2 /var/www/html/core/lib/Drupal/Core/Database/Query/Select.php(521): Drupal\Core\Database\Connection->query()
#3 /var/www/html/core/lib/Drupal/Core/Database/Query/Merge.php(366): Drupal\Core\Database\Query\Select->execute()
#4 /var/www/html/core/lib/Drupal/Core/Cache/DatabaseCacheTagsChecksum.php(42): Drupal\Core\Database\Query\Merge->execute()
#5 /var/www/html/core/lib/Drupal/Core/Cache/CacheTagsChecksumTrait.php(50): Drupal\Core\Cache\DatabaseCacheTagsChecksum->doInvalidateTags()
#6 [internal function]: Drupal\Core\Cache\DatabaseCacheTagsChecksum->rootTransactionEndCallback()
#7 /var/www/html/core/lib/Drupal/Core/Database/Transaction/TransactionManagerBase.php(580): call_user_func()
#8 /var/www/html/core/lib/Drupal/Core/Database/Transaction/TransactionManagerBase.php(329): Drupal\Core\Database\Transaction\TransactionManagerBase->processPostTransactionCallbacks()
#9 /var/www/html/core/lib/Drupal/Core/Database/Transaction.php(37): Drupal\Core\Database\Transaction\TransactionManagerBase->purge()
#10 [internal function]: Drupal\Core\Database\Transaction->__destruct()
#11 {main}
TypeError: Drupal\Core\Database\StatementPrefetchIterator::__construct(): Argument #1 ($clientConnection) must be of type object, null given, called in /var/www/html/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php on line 429 in /var/www/html/core/lib/Drupal/Core/Database/StatementPrefetchIterator.php on line 36 #0 /var/www/html/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php(429): Drupal\Core\Database\StatementPrefetchIterator->__construct()
#1 /var/www/html/core/lib/Drupal/Core/Database/Connection.php(667): Drupal\sqlite\Driver\Database\sqlite\Connection->prepareStatement()
#2 /var/www/html/core/lib/Drupal/Core/Database/Query/Select.php(521): Drupal\Core\Database\Connection->query()
#3 /var/www/html/core/lib/Drupal/Core/Database/Query/Merge.php(366): Drupal\Core\Database\Query\Select->execute()
#4 /var/www/html/core/lib/Drupal/Core/Cache/DatabaseCacheTagsChecksum.php(42): Drupal\Core\Database\Query\Merge->execute()
#5 /var/www/html/core/lib/Drupal/Core/Cache/CacheTagsChecksumTrait.php(50): Drupal\Core\Cache\DatabaseCacheTagsChecksum->doInvalidateTags()
#6 [internal function]: Drupal\Core\Cache\DatabaseCacheTagsChecksum->rootTransactionEndCallback()
#7 /var/www/html/core/lib/Drupal/Core/Database/Transaction/TransactionManagerBase.php(580): call_user_func()
#8 /var/www/html/core/lib/Drupal/Core/Database/Transaction/TransactionManagerBase.php(329): Drupal\Core\Database\Transaction\TransactionManagerBase->processPostTransactionCallbacks()
#9 /var/www/html/core/lib/Drupal/Core/Database/Transaction.php(37): Drupal\Core\Database\Transaction\TransactionManagerBase->purge()
#10 [internal function]: Drupal\Core\Database\Transaction->__destruct()
#11 {main}
TypeError: Drupal\Core\Database\StatementPrefetchIterator::__construct(): Argument #1 ($clientConnection) must be of type object, null given, called in /var/www/html/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php on line 429 in Drupal\Core\Database\StatementPrefetchIterator->__construct() (line 36 of /var/www/html/core/lib/Drupal/Core/Database/StatementPrefetchIterator.php).

Note that if Xdebug is not enabled, or Xdebug mode is "debug" or if Navigation is uninstalled, the exception is not thrown. I don't know if this is SQLite specific, because I haven't tested on MySQL or Mariadb.

mondrake’s picture

Thanks @godotislate, I think #40 is very much overlapping with #3569316: Client connection ($clientConnection) must be of type object, null given when deleting a node.

IMHO what we are facing here is that currently not only we execute database transaction commits in the object destruction danger zone, but that we also execute post-commit callbacks once the commit is completed, in the same danger zone. @ålexpott already identified this as risky business in #3447097-10: TransactionNameNonUniqueException: A transaction named drupal_transaction is already in use.

For me (I am repeating myself), the way out is the plan in #3495728: [meta] Drop implicit autocommit on Transaction destruction: here we take away the transaction commit from the danger zone, so that they are executed during normal runtime based on the explicit call to ::commitOrRelease(). In follow ups, we will do the same for the post-transaction callbacks, moving their execution from the object destruction time to post ::commitOrRelease(). In the plan I am suggesting to use events for that, but we can consider alternatives of course.

mondrake’s picture

Meanwhile, the references to 11.3.0 in the deprecation messages need updating to 11.4.0, and the version set in the CR needs updating as well.

Done that.

godotislate’s picture

I would like to help get this to RTBC, but I don't think I understand the DB layer well enough to do so without having repeatable test steps that show what this solves.

mondrake’s picture

#43 of course, thanks anyway.

In general, this issue is not trying to ‘solve’ any bug. If it does, it’s incidental. There are plenty of issues open already about bugs with transactions on destruction.

This is ‘simply’ implementing across core the explicit commit feature already introduced in the parent issue, and deprecating (but not fixing bugs for) commit on destruction.

mondrake’s picture

In the hope to move things forward, I have opened MR!15375 with a minimal scope - only convert Drupal core transactions to use explicit commit. That leaves deprecations for a later stage.

mondrake changed the visibility of the branch 3406985-pp-1-convert-all to hidden.

mondrake’s picture

Title: Convert all transactions in core to use explicit ::commitOrRelease(), deprecate implicit commit-on-destruct » Convert all transactions in core to use explicit ::commitOrRelease()
Issue summary: View changes
mondrake’s picture

Issue summary: View changes
Issue tags: -Needs manual testing

No longer needs manual testing with new, reduced, scope - that may be relevant in followups to check xdebug behaviour.

mradcliffe’s picture

I set the primary issue for the change record draft to the meta for now. I don’t think we should publish the change record until after the deprecation issue, right?

mondrake’s picture

#50 good points, +1

mondrake’s picture

godotislate’s picture

I don't have a deep understanding of the DB layer, but the MR seems straightforward from the explanation/goals defined in the IS. I do have a couple general questions and one small punctuation nit.

mondrake’s picture

Thanks. Applied suggestion and replied to comment inline.

godotislate’s picture

Status: Needs review » Reviewed & tested by the community

Thanks for the explanation. This makes sense to me.

mondrake’s picture

  • amateescu committed 3c48dc8c on 11.x
    task: #3406985 Convert all transactions in core to use explicit ::...

  • amateescu committed 8d78571b on main
    task: #3406985 Convert all transactions in core to use explicit ::...
amateescu’s picture

Version: main » 11.x-dev
Status: Reviewed & tested by the community » Fixed

I see that no one replied to @ghost of drupal past's question from #33, so I've read #301049: Transaction nesting not tracked by connection, and, as far as I can see from the patch that was committed there, the primary goal of that issue was transaction nesting, which is fully preserved by commitOrRelease() (it works through the same stack mechanism). The "prohibition" of explicit commits in that patch was a safeguard against bypassing the nesting layer via raw PDO::commit(), not a design goal in itself.

The new mechanism for explicit commits were discussed and agreed upon in #1025314: Transactions should be allowed to be committed explicitly and especially #3398767: Allow returning explicitly to the prior nesting level in transactions (aka allow explicit COMMIT in Transaction objects), where commitOrRelease() was introduced.

I've read this MR a few times already in the past few weeks, and the smaller scope is appreciated :)

Committed and pushed 8d78571bbef to main and 3c48dc8cccc to 11.x. Thanks!

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

amateescu’s picture

Note that I haven't published the change record, I think that should be done in #3584238: Deprecate implicit commit-on-destruct.

Status: Fixed » Closed (fixed)

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