Skip to content

Retry transaction errors on the MYSQLI adapter - #360

Merged
sgiehl merged 2 commits into
6.x-devfrom
fix-mysqli-transaction-error-tests
Oct 27, 2026
Merged

sgiehl merged 2 commits into
6.x-devfrom
fix-mysqli-transaction-error-tests

Conversation

@sgiehl

@sgiehl sgiehl commented Oct 26, 2026 •

Copy link
Copy Markdown
Member

Description

Follow-up to #348 and #349. On the MYSQLI database adapter, deadlocks, lock wait timeouts and snapshot isolation conflicts were never recognised as retryable, so the transaction was not restarted and the failed request set was cut short instead of retried. This predates #348: the earlier deadlock and lock wait retry used the same check, so it never worked on MYSQLI either. The handling from #348 and #349 therefore only ever applied on PDO.

The cause: the mysqli adapter's isErrNo() reads the error number from the connection, not the exception. By the time Handler::onException() runs, the failed statement has been freed, which clears the connection's error number, and the mysqli error message doesn't include it. The tracker's DbException does carry the number as its code, so the handler now accepts that too. On PDO the code is the integer value of the SQLSTATE, so it never matches these numbers, and detection there still comes from the message.

This also explains why core's MYSQLI CI jobs fail on the #348/#349 tests (e.g. in matomo-org/matomo#25585) while this plugin's CI passed: it only ran on PDO.

  • Handler also checks the code of the tracker DbException.
  • The tests fake these errors with a DbException carrying the number, so the production check runs on both adapters. A new test raises a real lock wait timeout through a second connection. Without the fix it fails on MYSQLI. A unit test also covers an error whose number is only in the code, so every CI job checks the new branch, not just the MYSQLI one.
  • The plugin tests now also run in one MYSQLI job (max PHP, maximum supported Matomo, MySQL 8.0).

Known limitation: on MYSQLI, two kinds of error still lose the number and are not recognised. Errors raised by the fetch* methods of core's tracker mysqli adapter are rewrapped without the code, and prepare failures throw without the number. Tracking runs its writes and its one locking read through query(), which keeps the code, so this only matters for locking reads through fetch* (e.g. under SERIALIZABLE isolation) or prepare-time errors (e.g. on Galera). The fix belongs in core.

Follow-up: 5.x has the same bug (#350 in 5.2.1 also uses isErrNo() only), so this needs a backport to 5.x-dev.

Issue No

Related to #331

Steps to Replicate the Issue

  1. Set adapter = "MYSQLI" in [database_tests] and run HandlerTest.
  2. Expected: a real lock wait timeout is recognised as a transaction error and the request set is queued for retry.
  3. Actual: it is treated as an ordinary failure.

Checklist

  • [✔] Tested locally or on demo2/demo3?
  • [✔] New test case added/updated?
  • [NA] Are all newly added texts included via translation?
  • [NA] Are text sanitized properly? (Eg use of v-text v/s v-html for vue)
  • [NA] Version bumped? (6.x-dev is already on the unreleased 6.0.3; this adds to its changelog entry)
  • [✔] I have understood, reviewed, and tested all AI outputs before use
  • [✔] All AI instructions respect security, IP, and privacy rules
  • [NA] Documentation updated?

@sgiehl
sgiehl force-pushed the fix-mysqli-transaction-error-tests branch from 7c50e53 to d77264d Compare October 26, 2026 16:39
@sgiehl sgiehl changed the title Fix the transaction-error tests on the MYSQLI adapter [ignore_release] Retry transaction errors on the MYSQLI adapter Oct 26, 2026
@sgiehl
sgiehl force-pushed the fix-mysqli-transaction-error-tests branch 2 times, most recently from 4672925 to 0318311 Compare October 27, 2026 07:38
The mysqli adapter's isErrNo() reads the error number from the connection,
which no longer has it once the failed statement was freed, so deadlocks,
lock wait timeouts and snapshot isolation conflicts were never retried.
Also accept the tracker DbException's code, which carries the number.

The tests now fake these errors with a coded DbException, so the production
check runs on both adapters, and one test raises a real lock wait timeout.
@sgiehl
sgiehl force-pushed the fix-mysqli-transaction-error-tests branch from 0318311 to c6aa9f9 Compare October 27, 2026 07:39
@sgiehl
sgiehl marked this pull request as ready for review October 27, 2026 07:43
@sgiehl
sgiehl requested a review from a team October 27, 2026 07:43
@sgiehl
sgiehl merged commit 0396250 into 6.x-dev Oct 27, 2026
24 of 37 checks passed
@sgiehl
sgiehl deleted the fix-mysqli-transaction-error-tests branch October 27, 2026 09:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants