Repository navigation
Conversation
Correct phase-two status, idempotency, and error classification while transferring held XA connections safely between the SQL pool and keeper. Add regression coverage for failure, retry, detach, and concurrent close paths.
There was a problem hiding this comment.
Pull request overview
This PR hardens the XA SQL datasource implementation around phase-two correctness (status direction, idempotency, retry semantics) and physical connection ownership, including MySQL-specific capability probing and phase-two error classification, with substantial regression and concurrency test coverage.
Changes:
- Add directional, cache-aware phase-two handling with optional database-specific terminal/unretryable classification.
- Preserve original phase-one END/PREPARE errors while still performing compensating rollback when needed.
- Introduce synchronized keeper/pool/physical-close ownership rules to prevent premature reuse or loss of retry state; add MySQL
xa_detach_on_prepareprobing.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| pkg/datasource/sql/xa/xa_resource.go | Adds optional XAPhaseTwoErrorClassifier extension interface for phase-two error classification. |
| pkg/datasource/sql/xa/mysql_xa_connection.go | Implements MySQL phase-two rollback/protocol error classification. |
| pkg/datasource/sql/xa/mysql_xa_connection_test.go | Adds unit coverage for MySQL phase-two classification behavior. |
| pkg/datasource/sql/xa_resource_manager.go | Updates phase-two commit/rollback status semantics, cache idempotency, and classifier integration. |
| pkg/datasource/sql/xa_resource_manager_test.go | Adds table-driven regression tests for phase-two status/caching/idempotency and retry behavior. |
| pkg/datasource/sql/types/const.go | Corrects/extends MySQL/MariaDB XA-related error code constants. |
| pkg/datasource/sql/types/const_test.go | Extends tests to validate XA error code constants. |
| pkg/datasource/sql/db.go | Adds MySQL detach-on-prepare probing and conservative fallback behavior. |
| pkg/datasource/sql/conn_xa.go | Preserves phase-one errors, synchronizes keeper/pool close ownership, and enforces pool discard for kept branches. |
| pkg/datasource/sql/conn_xa_test.go | Adds regression/concurrency tests for error preservation, forced-close cleanup, ownership transfer, and session probing. |
| changes/dev.md | Records the bugfix entry for #1147 in release notes. |
| changes/dev_zh.md | Records the bugfix entry for #1147 in Chinese release notes. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Separate temporary keeper caching from mandatory owner retention, synchronize prepared-state snapshots, and keep XA capability probes out of AT resource initialization.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #1148 +/- ##
==========================================
+ Coverage 61.52% 62.16% +0.63%
==========================================
Files 309 309
Lines 22625 22845 +220
==========================================
+ Hits 13920 14201 +281
+ Misses 7420 7349 -71
- Partials 1285 1295 +10 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
plz fix conflicts~ |
Resolve the XA connection hold fallback conflict by preserving per-connection capability decisions while retaining unknown database types conservatively.
|
Conflicts are resolved in 62cde06 by merging the latest The resolution keeps both sides of the XA hold behavior:
Validation after the merge:
|
|
plz fix conflicts |
Resolve development changelog conflicts while preserving the XA fix entry and the latest upstream release notes.
|
Conflicts resolved. |
|
I found that I have updated the phase-two classifier to consider the requested operation:
I also removed the duplicate local rollback in the timeout path and added regression coverage for the timeout → local rollback → TC rollback → |
|
plz fix CI and Conflicts. |
Keep both sides of the changelog and remove the unused SQL types import left after the XA timeout cleanup changes.
|
Thanks for flagging this! I merged the latest |
What this PR does:
This PR fixes XA failure-path correctness, retry semantics, idempotency, and physical connection ownership.
Problem and Root Cause
1. Incorrect phase-two status and terminal cache
When XA phase two fails, the current implementation can return or record an incorrect terminal result:
BranchCommitreturns a rollback-failure status when resource or connection acquisition fails.PhasetwoCommittedtobranchStatusCache.PhasetwoRollbackedto the cache.The root cause is that phase-two error handling does not distinguish commit and rollback directions, retryable and unretryable errors, confirmed terminal outcomes and ambiguous errors, or first execution and repeated callbacks. The cache is also updated from failure branches instead of only after a confirmed terminal result.
2. Phase-one errors can be lost
When
XA ENDorXA PREPAREfails,commitErrorHandleperforms a compensating rollback. If that rollback succeeds, the previous implementation returnsnil, because the local error variable only receives the rollback result. The original END/PREPARE error is therefore lost, and the caller can subsequently treat phase one as successful.3. Retryable failures lose their owner connection
XAConn.XaCommitandXAConn.XaRollbackrelease the keeper entry regardless of the database result. For databases or configurations that still require the original physical connection, a temporary error therefore removes the only usable owner before the TC retries.The root cause is that database execution and connection ownership completion are handled independently. The code does not model whether phase two actually reached a terminal state.
4. Held connections can still be reused by
database/sqlKeeping an
XAConninDBResource.keeperdoes not automatically remove its physical connection from thedatabase/sqlpool.ResetSessionpreviously returnednil, so the pool could reuse a prepared connection while the sameXAConnwas still referenced by the keeper.At the same time, pool discard, phase-two completion, and timeout close could operate on the same physical connection without a shared ownership guard. This can cause prepared connection reuse, overwritten branch state, phase-two/timeout races, duplicate closes, and keeper entries pointing to unusable connections.
5. Forced close can leak keeper state
XAConn.CloseForcereturns immediately when the underlying physical close returns an error. As a result, the keeper entry and local branch state are not cleaned up.6. Cached terminal states are ignored
XAConn.terminationreadsbranchStatusCache, but only checks whether the cache lookup itself returned an error. A successfully retrievedPhasetwoCommittedorPhasetwoRollbackedvalue is ignored.7. MySQL detach capability is determined incompletely
MySQL 8.0.29 introduced cross-connection phase two, but it also depends on the effective session variable
@@session.xa_detach_on_prepare. The previous implementation only checked the server version. Because this is a session variable, checking it once on an initialization connection is also insufficient: the actual XA physical connection may have a different value.8. The existing error classifier is not sufficient for phase two
XAErrorClassifier.IsAlreadyEndedonly helps the XA END path. It cannot distinguish already committed, already rollbacked, deterministic protocol errors, ambiguousXAER_NOTA, or temporary connection and RM failures.Solution
1. Make terminal-state publication directional and idempotent
Phase-two handling now:
The terminal cache is published before releasing the keeper, so a concurrent retry cannot observe neither the owner nor the terminal result.
2. Preserve the original phase-one error
commitErrorHandlenow receives the original failure explicitly. If compensating rollback succeeds, the original END/PREPARE error is returned. If rollback also fails, both errors are retained witherrors.Join.3. Release keeper ownership only after confirmed completion
XaCommitandXaRollbackrelease the keeper only when the database operation succeeds or the database-specific classifier proves the exact same terminal direction. Retryable errors keep the owner connection available for TC retry. Opposite terminal outcomes are recorded with their actual status and returned as unretryable conflicts.4. Transfer pending connections out of the pool safely
Held XA connections now have explicit synchronized ownership state:
ResetSessionreturnsdriver.ErrBadConnfor a pending keeper-owned branch;database/sqldiscards the wrapper instead of reusing it;XAConn.Closedoes not close the physical connection while keeper owns it;physicalClosedguard prevents duplicate closes.If phase two finishes before pool discard, the connection can remain reusable. If the pool already discarded it, phase-two completion closes it exactly once.
5. Always clean up after forced close
CloseForcenow performs keeper and branch-state cleanup regardless of the physical close result. The original close error is still returned.6. Handle cached terminal states explicitly
terminationnow rejects branches cached asPhasetwoCommittedorPhasetwoRollbackedand releases any remaining keeper ownership.7. Probe MySQL detach capability on the actual XA connection
For MySQL 8.0.29 and later:
@@session.xa_detach_on_prepare.8. Add optional phase-two error classification
This PR introduces the optional
XAPhaseTwoErrorClassifier:The MySQL implementation keeps
XAER_NOTAambiguous and retryable, recognizes XA rollback terminal codes, treatsXAER_INVALandXAER_OUTSIDEas unretryable, and leaves unknown RM and connection failures retryable.Implementation Details
pkg/datasource/sql/xa_resource_manager.gopkg/datasource/sql/conn_xa.gocommitErrorHandle.errors.Joinwhen compensating rollback also fails.driver.ErrBadConnfor pool reuse of held branches.CloseForce.termination.pkg/datasource/sql/db.goxa_detach_on_preparefrom driver-native result types.pkg/datasource/sql/xa/xa_resource.goXAPhaseTwoErrorClassifier.XAResourceandXAResourceFactorycontracts unchanged.pkg/datasource/sql/xa/mysql_xa_connection.goXAER_NOTAunclassified because it does not prove a terminal direction.pkg/datasource/sql/types/const.goCorrect and extend MySQL/MariaDB XA error constants for
XAER_NOTA,XAER_INVAL,XAER_RMFAIL,XAER_OUTSIDE,XAER_RMERR,XA_RBROLLBACK,XA_RBTIMEOUT, andXA_RBDEADLOCK.Regression Tests
Added coverage for:
Which issue(s) this PR fixes:
Fixes #1147
Special notes for your reviewer:
branchStatusCacheremains a process-local idempotency optimization and is not used as durable recovery storage.XAER_NOTAremains retryable because it cannot prove whether a branch committed, rolled back, or never existed.XAResourceinterface is unchanged.Test Plan
On Go 1.26.5 / macOS arm64, the repository-wide test command reaches an unrelated pre-existing SIGBUS in a gomonkey-based gRPC test. The modified SQL datasource packages and focused race suites pass.
Does this PR introduce a user-facing change?: