Go MySQL Driver Error Handling Strategy for Network Failures: When and How ErrBadConn Is Returned
The go-sql-driver/mysql returns driver.ErrBadConn whenever a TCP connection becomes unsafe for reuse, signaling database/sql to purge the connection from the pool and retry on a fresh socket.
The error handling strategy in the go-sql-driver/mysql repository centers on the standard driver.ErrBadConn sentinel to manage network failures gracefully. When the driver detects conditions that render a connection unreliable—such as closed sockets, failed writes before data transmission, or read-only failover scenarios—it returns this specific error to trigger automatic connection replacement.
How the Driver Detects Bad Connections
The driver implements a strict hierarchy of checks in connection.go to determine connection viability. At the start of every major API method—including Begin, Prepare, Exec, and Query—the driver verifies the connection state via mc.closed.Load(). If the connection is already marked as closed or cancelled, the method returns driver.ErrBadConn immediately (as seen in connection.go lines 44-45), preventing operations on known-dead sockets.
Specific Conditions That Trigger ErrBadConn
Write Failures Before Data Transmission
In packets.go, the writePacket function distinguishes between partial and complete write failures. When the first write iteration returns zero bytes written, the driver returns the internal sentinel errBadConnNoWrite (defined in errors.go lines 33-38). The caller then invokes mc.markBadConn(err) in connection.go (lines 29-35), which converts this internal error into the public driver.ErrBadConn. This distinction matters because no data reached the server, making the operation safe to retry.
Read Packet Network Errors
The readPacket function in packets.go (lines 52-60) handles network read failures, timeouts, EOF conditions, or malformed packets by immediately closing the underlying net.Conn and returning ErrInvalidConn. Higher-level calls that encounter these errors subsequently translate them into driver.ErrBadConn, ensuring the connection is evicted from the pool when the socket becomes unreadable.
Read-Only Failover Detection
When the server returns error codes 1792, 1290, or 1836 and the RejectReadOnly configuration is enabled, the driver's handleErrorPacket logic in packets.go (lines 100-112) closes the connection and returns driver.ErrBadConn directly. This allows write operations to fail fast on read-only replicas and retry on primary instances.
Safe-to-Retry Command Failures
For commands like COM_STMT_PREPARE that can be replayed safely, the driver returns driver.ErrBadConn immediately if writeCommandPacket fails. The driver treats these commands as idempotent, ensuring that transient network interruptions do not permanently fail the operation while allowing database/sql to retry on a fresh connection.
When ErrBadConn Is NOT Returned
The driver deliberately preserves original errors when the server may have partially processed a request. If writePacket fails after transmitting some bytes but not others, the driver returns the raw error rather than driver.ErrBadConn. This prevents duplicate execution of non-idempotent operations, as retrying could cause the server to apply the same change twice.
Source Code Implementation Details
The error handling logic spans four critical files:
connection.go: Contains themarkBadConnhelper that converts internalerrBadConnNoWritetodriver.ErrBadConn, and guards high-level API methods withmc.closed.Load()checks.packets.go: Implements low-level packet I/O viawritePacketandreadPacket, detects read-only failover conditions inhandleErrorPacket, and distinguishes between zero-byte write failures and partial write errors.errors.go: Declares the internalerrBadConnNoWritesentinel and documents its conversion semantics to the standard driver error.statement.go: Propagatesdriver.ErrBadConnthroughExecandQuerypaths (lines 54-57) when underlying connections are closed.
Practical Code Examples
Handling Automatic Connection Replacement
When using database/sql, the pool automatically discards connections marked with driver.ErrBadConn and establishes new ones:
db, err := sql.Open("mysql", dsn)
if err != nil {
log.Fatal(err)
}
// The driver returns driver.ErrBadConn for network failures,
// triggering automatic retry on a fresh connection
result, err := db.Exec("UPDATE users SET last_login = NOW() WHERE id = ?", userID)
if err != nil {
if errors.Is(err, driver.ErrBadConn) {
// Connection was replaced; application-level retry optional
log.Println("Network failure detected, connection replaced")
}
}
Detecting Write Failures Before Transmission
The internal logic in packets.go (lines 53-57) demonstrates how the driver handles zero-byte write scenarios before converting to driver.ErrBadConn:
// This mirrors the internal check in packets.go
func (mc *mysqlConn) writePacket(data []byte) error {
// Attempt first write
n, err := mc.netConn.Write(data)
if err != nil && n == 0 {
// No bytes sent - safe to mark as bad connection
return errBadConnNoWrite
}
// If n > 0 and err != nil, return original error (partial write)
return err
}
Configuring Read-Only Failover Handling
Enable RejectReadOnly to force ErrBadConn on read-only errors, as implemented in packets.go:
cfg := mysql.NewConfig()
cfg.RejectReadOnly = true
db, err := sql.Open("mysql", cfg.FormatDSN())
if err != nil {
log.Fatal(err)
}
// This will return driver.ErrBadConn if the server is read-only,
// allowing retry on a writable primary
_, err = db.Exec("INSERT INTO orders (item) VALUES (?)", "widget")
Summary
- The driver returns
driver.ErrBadConnwhenever a TCP connection becomes unusable due to closed sockets, zero-byte write failures, read errors, or read-only failover conditions. - File locations: Core logic resides in
connection.go(state checks andmarkBadConn),packets.go(I/O error detection andhandleErrorPacket), anderrors.go(internal sentinel definitions). - Safe retry semantics: The driver only returns
ErrBadConnwhen no data reached the server or when the connection is known to be unusable, preventing duplicate execution of partial operations. - Automatic pool management:
database/sqlautomatically evicts connections marked withErrBadConnand establishes fresh replacements without manual intervention.
Frequently Asked Questions
What is the difference between errBadConnNoWrite and driver.ErrBadConn?
errBadConnNoWrite is an internal sentinel defined in errors.go that indicates a write failure occurred before any bytes were transmitted to the server. The markBadConn function in connection.go (lines 29-35) converts this internal error into the standard driver.ErrBadConn sentinel, which the database/sql package recognizes as a signal to discard the connection from the pool and retry the operation.
Why doesn't the driver return ErrBadConn for all network errors?
The driver preserves original errors for write failures that occur after partial data transmission. When some bytes reach the server before the connection fails (detected in packets.go), retrying the operation could cause duplicate execution. By returning the raw error instead of ErrBadConn, the driver prevents automatic retries that might result in unintended side effects or data corruption.
How does the RejectReadOnly configuration affect error handling?
When RejectReadOnly is enabled and the server returns error codes 1792, 1290, or 1836 (indicating a read-only state), the driver's handleErrorPacket function in packets.go (lines 100-112) closes the connection immediately and returns driver.ErrBadConn. This triggers database/sql to retry the operation on a fresh connection, ideally routing to a writable primary server instead of a read-only replica.
Where does the driver check if a connection is already closed?
Every high-level API method—including Begin, Prepare, Exec in connection.go, and Exec/Query in statement.go (lines 54-57)—checks mc.closed.Load() before executing operations. If this atomic boolean indicates the connection is closed, the method returns driver.ErrBadConn immediately without attempting network I/O.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →