📝 Description
When initiating a transaction using BeginTx(ctx, opts) in the raimeecas/mysql driver, a context cancellation or timeout occurring during the transaction setup phase can lead to a connection state mismatch. Specifically, if the driver successfully sends the START TRANSACTION (or BEGIN) command to the MySQL server, but the context is canceled before the transaction is fully established and returned to the user, the connection may be returned to the connection pool while still in an active transaction state.
This results in "dirty" connections in the pool. Subsequent queries executing on these connections will unexpectedly run inside the uncommitted transaction, leading to potential data isolation issues, lock contention, and silent failures.
🎯 Acceptance Criteria
🛠️ Technical Specifications & Context
In Go's database/sql driver interface, Conn.BeginTx(ctx context.Context, opts driver.TxOptions) is responsible for starting a transaction.
Potential Areas of Modification:
-
connection.go / driver.go (Transaction Initialization):
Within the implementation of BeginTx, look for where the START TRANSACTION query is written to the network socket.
If ctx.Done() is triggered during or immediately after sending the query but before returning the driver.Tx implementation, the connection state is dirty.
-
Connection Discard Policy:
If the context is canceled, executing a ROLLBACK over the same connection might fail if the context is already expired. In such cases, the driver should return driver.ErrBadConn to signal to the database/sql connection pool that the connection is compromised and must be closed and discarded rather than returned to the free pool.
Example pattern to implement in BeginTx:
// Inside BeginTx implementation
if err := ctx.Err(); err != nil {
return nil, err
}
// Send BEGIN command to MySQL
err := mc.writeCommandPacketStr(comQuery, "START TRANSACTION")
if err != nil {
return nil, err
}
// Check if context was canceled during the roundtrip
select {
case <-ctx.Done():
// The transaction started on the server, but the context is dead.
// We must close the connection to prevent returning a dirty connection to the pool.
mc.Close()
return nil, ctx.Err()
default:
// Proceed normally
}
🧪 Verification & Testing
To verify the fix, implement a test case that simulates a context cancellation during transaction start:
- Unit/Integration Test:
Create a test that calls BeginTx with a pre-canceled context or a context that cancels concurrently during the connection setup.
ctx, cancel := context.WithCancel(context.Background())
// Trigger cancellation immediately or with a tiny delay to race with BeginTx
cancel()
tx, err := db.BeginTx(ctx, nil)
if err == nil {
tx.Rollback()
t.Fatal("Expected BeginTx to fail with context canceled")
}
- Pool Cleanliness Verification:
After the failed BeginTx, acquire a new connection from the pool and verify it is not in a transaction:
var inTransaction int
// In MySQL, check if we are in a transaction (e.g., by checking @@in_transaction or performing a test write/rollback)
err = db.QueryRow("SELECT @@in_transaction").Scan(&inTransaction)
if err != nil {
t.Fatal(err)
}
if inTransaction != 0 {
t.Error("Connection returned to the pool was left in an active transaction state!")
}

This repo is using Opire - what does it mean? 👇
💵 Everyone can add rewards for this issue commenting /reward 100 (replace 100 with the amount).
🕵️♂️ If someone starts working on this issue to earn the rewards, they can comment /try to let everyone know!
🙌 And when they open the PR, they can comment /claim #2 either in the PR description or in a PR's comment.
🪙 Also, everyone can tip any user commenting /tip 20 @raimeecas (replace 20 with the amount, and @raimeecas with the user to tip).
📖 If you want to learn more, check out our documentation.
📝 Description
When initiating a transaction using
BeginTx(ctx, opts)in theraimeecas/mysqldriver, a context cancellation or timeout occurring during the transaction setup phase can lead to a connection state mismatch. Specifically, if the driver successfully sends theSTART TRANSACTION(orBEGIN) command to the MySQL server, but the context is canceled before the transaction is fully established and returned to the user, the connection may be returned to the connection pool while still in an active transaction state.This results in "dirty" connections in the pool. Subsequent queries executing on these connections will unexpectedly run inside the uncommitted transaction, leading to potential data isolation issues, lock contention, and silent failures.
🎯 Acceptance Criteria
BeginTxis canceled or times out during the transaction initialization, the driver must guarantee that the connection is not returned to the pool in an active transaction state.BeginTx, the driver must attempt to roll back the transaction (ROLLBACK) or, if the connection state is uncertain, discard/close the connection entirely so it is not reused.🛠️ Technical Specifications & Context
In Go's
database/sqldriver interface,Conn.BeginTx(ctx context.Context, opts driver.TxOptions)is responsible for starting a transaction.Potential Areas of Modification:
connection.go/driver.go(Transaction Initialization):Within the implementation of
BeginTx, look for where theSTART TRANSACTIONquery is written to the network socket.If
ctx.Done()is triggered during or immediately after sending the query but before returning thedriver.Tximplementation, the connection state is dirty.Connection Discard Policy:
If the context is canceled, executing a
ROLLBACKover the same connection might fail if the context is already expired. In such cases, the driver should returndriver.ErrBadConnto signal to thedatabase/sqlconnection pool that the connection is compromised and must be closed and discarded rather than returned to the free pool.Example pattern to implement in
BeginTx:🧪 Verification & Testing
To verify the fix, implement a test case that simulates a context cancellation during transaction start:
Create a test that calls
BeginTxwith a pre-canceled context or a context that cancels concurrently during the connection setup.After the failed
BeginTx, acquire a new connection from the pool and verify it is not in a transaction:This repo is using Opire - what does it mean? 👇
💵 Everyone can add rewards for this issue commenting
/reward 100(replace100with the amount).🕵️♂️ If someone starts working on this issue to earn the rewards, they can comment
/tryto let everyone know!🙌 And when they open the PR, they can comment
/claim #2either in the PR description or in a PR's comment.🪙 Also, everyone can tip any user commenting
/tip 20 @raimeecas(replace20with the amount, and@raimeecaswith the user to tip).📖 If you want to learn more, check out our documentation.