[インデックス 12551] ファイルの概要
このコミットは、Go言語の database/sql
パッケージにおける重要なバグ修正と改善を目的としています。具体的には、トランザクション内で Stmt.Query
がエラーを返した場合に、データベース接続がフリーリストに二重に解放される可能性があった問題を修正しています。また、driver.ErrBadConn
の伝播を改善し、問題のある接続が適切にプールから除外されるようにしています。
コミット
commit 3297fc63d6226f6ed47a4fdb5962c78c55c5339c
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Sat Mar 10 10:00:02 2012 -0800
database/sql: fix double connection free on Stmt.Query error
In a transaction, on a Stmt.Query error, it was possible for a
connection to be added to a db's freelist twice. Should use
the local releaseConn function instead.
Thanks to Gwenael Treguier for the failing test.
Also in this CL: propagate driver errors through releaseConn
into *DB.putConn, which conditionally ignores the freelist
addition if the driver signaled ErrBadConn, introduced in a
previous CL.
R=golang-dev, gary.burd
CC=golang-dev
https://golang.org/cl/5798049
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3297fc63d6226f6ed47a4fdb5962c78c55c5339c
元コミット内容
トランザクション内で Stmt.Query
がエラーを返した場合に、データベース接続が db
のフリーリストに二重に解放される可能性があった問題を修正します。この修正では、ローカルの releaseConn
関数を適切に使用するように変更されています。また、releaseConn
を介してドライバエラーを *DB.putConn
に伝播させ、driver.ErrBadConn
がシグナルされた場合にはフリーリストへの追加を条件付きで無視するように改善されています。
変更の背景
この変更は、database/sql
パッケージにおける接続管理の堅牢性を高めるために行われました。
-
二重解放のバグ: 以前の実装では、トランザクション内で
Stmt.Query
メソッドがエラーを返した場合、内部的に接続が二重にフリーリスト(接続プール)に戻される可能性がありました。これは、接続プールの状態を不正にし、後続のデータベース操作で予期せぬ動作やパニックを引き起こす可能性のある深刻なバグです。接続が二重に解放されると、同じ接続が複数のゴルーチンに割り当てられたり、接続が閉じられた後に再度使用されたりする「Use-After-Free」のような問題につながる可能性があります。 -
driver.ErrBadConn
の伝播:database/sql
パッケージは、データベースドライバがdriver.ErrBadConn
を返すことで、接続が不良状態であることを通知するメカニズムを持っています。以前のコミットでこのエラーが導入されましたが、このコミットでは、Stmt.Query
のエラーパスにおいてもこのErrBadConn
が適切にputConn
関数に伝播されるように改善されています。これにより、不良な接続が接続プールに再利用可能なものとして戻されることを防ぎ、アプリケーションが常に健全な接続を使用できるようにします。
これらの問題は、データベースとのインタラクションにおいて信頼性と安定性を確保するために、早急に修正する必要がありました。特に、トランザクションは複数の操作をアトミックに実行する性質上、その途中で発生するエラーのハンドリングは非常に重要です。
前提知識の解説
このコミットを理解するためには、Go言語の database/sql
パッケージの基本的な概念と、データベース接続管理に関する知識が必要です。
-
database/sql
パッケージ: Go標準ライブラリの一部で、GoアプリケーションからSQLデータベースと対話するための汎用的なインターフェースを提供します。このパッケージ自体は特定のデータベースドライバを含まず、driver
インターフェースを実装するサードパーティのドライバを介してデータベースと通信します。 -
接続プール (Connection Pool):
database/sql
パッケージは、データベース接続のプールを内部的に管理します。アプリケーションがデータベース操作を行う際、新しい接続を毎回確立するのではなく、プールから既存の接続を再利用します。これにより、接続確立のオーバーヘッドを削減し、パフォーマンスを向上させます。操作が完了すると、接続はプールに戻され、他の操作のために再利用可能になります。 -
*sql.DB
: データベースへの抽象的なハンドルを表します。これは接続プールを管理し、新しい接続の取得や既存の接続の解放を行います。 -
driver.Conn
: データベースドライバが実装するインターフェースで、単一のデータベース接続を表します。 -
*sql.Stmt
(Prepared Statement): プリペアドステートメントを表します。これは、SQLクエリを事前にデータベースに送信して準備しておくことで、繰り返し実行する際のパフォーマンスを向上させます。トランザクション内で作成されたStmt
は、そのトランザクションに紐付けられます。 -
Query
メソッド:*sql.DB
や*sql.Tx
、*sql.Stmt
などで提供されるメソッドで、結果セット(行の集合)を返すSQLクエリを実行するために使用されます。 -
*sql.Tx
(Transaction): データベーストランザクションを表します。トランザクションは、複数のデータベース操作を単一の論理的な作業単位としてグループ化し、すべて成功するか、すべて失敗する(ロールバックされる)ことを保証します。トランザクション内で取得された接続は、そのトランザクションがコミットまたはロールバックされるまで解放されません。 -
releaseConn
関数: このコミットで修正の対象となっている、接続を解放し、接続プールに戻すための内部的なヘルパー関数です。 -
putConn
関数:*sql.DB
の内部メソッドで、使用済みのdriver.Conn
を接続プール(フリーリスト)に戻す役割を担います。 -
driver.ErrBadConn
:database/sql/driver
パッケージで定義されている特別なエラーです。データベースドライバが、その接続が不良状態であり、再利用できないことをdatabase/sql
パッケージに通知するために使用します。例えば、ネットワークの問題で接続が切断された場合や、データベースサーバーが接続を閉じた場合などにドライバがこのエラーを返します。database/sql
パッケージはErrBadConn
を受け取ると、その接続をプールから破棄し、新しい接続を確立して操作を再試行しようとします。これにより、アプリケーションは不良な接続を意識することなく、透過的に健全な接続を利用できます。
技術的詳細
このコミットの技術的詳細は、主に database/sql
パッケージの内部的な接続管理ロジック、特にトランザクションとプリペアドステートメントのエラーハンドリングに焦点を当てています。
1. 二重解放の修正:
問題は、トランザクション内で Stmt.Query
がエラーを返した際に、接続が二重に db.freeConn
に追加される可能性があった点です。
元のコードでは、Stmt.Query
のエラーパスで s.db.putConn(ci, err)
が直接呼び出されていました。しかし、Stmt
がトランザクションに紐付けられている場合、接続の解放は s.tx.releaseConn()
を通じて行われるべきでした。
このコミットでは、Stmt.Query
のエラーパスで releaseConn(err)
を呼び出すように変更されています。ここでいう releaseConn
は、Stmt.connStmt()
関数内で定義されるクロージャであり、Stmt
がトランザクションに紐付けられているか否かに応じて、適切な接続解放ロジック(s.tx.releaseConn()
または s.db.putConn(conn, nil)
)を呼び出すように設計されています。これにより、トランザクション内の Stmt.Query
エラー時にも、接続が一度だけ、かつ適切な方法で解放されることが保証されます。
2. driver.ErrBadConn
の伝播と処理:
以前のコミットで driver.ErrBadConn
が導入されましたが、このコミットではそのエラーが putConn
関数に適切に伝播されるように改善されています。
releaseConn
関数のシグネチャが func()
から func(error)
に変更され、エラー情報を引数として受け取るようになりました。これにより、Stmt.Query
や Rows.Close
などで発生したエラー(特に driver.ErrBadConn
)が releaseConn
を経由して putConn
に渡されるようになります。
*DB.putConn
関数は、渡されたエラーが driver.ErrBadConn
であるかどうかをチェックします。もし ErrBadConn
であれば、その接続は不良であると判断され、接続プール(db.freeConn
)には追加されずに破棄されます。これにより、不良な接続がプールに残り、後続の操作で再利用されてしまうことを防ぎます。
3. テストの追加と putConnHook
:
このコミットには、二重解放のバグを再現し、修正を検証するための新しいテストケース (TestTxQueryInvalid
) が含まれています。
また、putConnHook
というテスト用のフックが追加されています。これは、putConn
が呼び出された際にカスタムロジックを実行できるようにするためのもので、接続が二重に解放されていないかを検出するために使用されます。init
関数内で putConnHook
が設定され、db.freeConn
内に既に存在する接続が再度 putConn
に渡された場合にパニックを発生させることで、二重解放を厳密にチェックしています。
4. fakedb_test.go
の変更:
fakeConn.Close()
メソッドのエラーメッセージがより具体的になるように修正されています。これは直接的なバグ修正ではありませんが、テストのデバッグ可能性を向上させるための改善です。
これらの変更により、database/sql
パッケージの接続管理ロジックはより堅牢になり、特にエラー発生時の接続のライフサイクルが正確に管理されるようになりました。
コアとなるコードの変更箇所
このコミットで変更された主要なファイルとコードブロックは以下の通りです。
-
src/pkg/database/sql/fakedb_test.go
:fakeConn.Close()
メソッド内のエラーメッセージが"can't close; in a Transaction"
から"can't close fakeConn; in a Transaction"
へ、また"can't close; already closed"
から"can't close fakeConn; already closed"
へと変更されました。これはテストのデバッグを容易にするための微調整です。
-
src/pkg/database/sql/sql.go
:putConnHook
というfunc(*DB, driver.Conn)
型のグローバル変数が追加されました。これはテスト目的でputConn
の呼び出しをフックするために使用されます。putConn
関数内でputConnHook
が設定されている場合に呼び出されるロジックが追加されました。Stmt.Exec
メソッドのdefer releaseConn()
がdefer releaseConn(nil)
に変更されました。これはreleaseConn
のシグネチャ変更に伴うものです。Stmt.connStmt
関数のシグネチャがfunc (s *Stmt) connStmt() (ci driver.Conn, releaseConn func(), si driver.Stmt, err error)
からfunc (s *Stmt) connStmt() (ci driver.Conn, releaseConn func(error), si driver.Stmt, err error)
に変更され、releaseConn
クロージャがエラーを引数として受け取るようになりました。Stmt.connStmt
内で定義されるreleaseConn
クロージャの定義が、エラー引数を受け取るように変更されました。Stmt.Query
メソッドのエラーパスで、s.db.putConn(ci, err)
の代わりにreleaseConn(err)
が呼び出されるように変更されました。これが二重解放の主要な修正点です。Rows
構造体のreleaseConn
フィールドの型がfunc()
からfunc(error)
に変更されました。Rows.Close
メソッド内で、rs.releaseConn()
がrs.releaseConn(err)
に変更され、Rows.Close
で発生したエラーが接続解放時に伝播されるようになりました。
-
src/pkg/database/sql/sql_test.go
:init
関数が追加され、putConnHook
を設定しています。このフックは、接続が二重にputConn
に渡された場合にパニックを発生させることで、二重解放を検出します。TestTxQueryInvalid
という新しいテストケースが追加されました。これは、トランザクション内で不正なクエリを実行し、Stmt.Query
がエラーを返した際に接続が適切に解放されることを検証します。stack()
ヘルパー関数が追加され、スタックトレースを取得するために使用されます。これはputConnHook
で二重解放が検出された際に、どこから接続が解放されたかを特定するのに役立ちます。
コアとなるコードの解説
このコミットの核心は、database/sql
パッケージにおける接続のライフサイクル管理、特にエラー発生時の接続の解放ロジックの改善にあります。
Stmt.Query
の変更:
最も重要な変更は、src/pkg/database/sql/sql.go
の Stmt.Query
メソッド内です。
変更前:
rowsi, err := si.Query(sargs)
if err != nil {
s.db.putConn(ci, err) // ここで直接 putConn が呼ばれていた
return nil, err
}
変更後:
rowsi, err := si.Query(sargs)
if err != nil {
releaseConn(err) // ローカルの releaseConn クロージャを呼び出す
return nil, err
}
この変更により、Stmt.Query
でエラーが発生した場合でも、接続の解放は Stmt.connStmt()
で定義された releaseConn
クロージャを通じて行われるようになりました。このクロージャは、Stmt
が通常の DB
接続を使用しているか、それともトランザクション (Tx
) 接続を使用しているかに応じて、適切な putConn
または tx.releaseConn
を呼び出します。これにより、トランザクション内の Stmt.Query
エラー時に接続が二重に解放される問題が解決されました。
releaseConn
シグネチャの変更とエラー伝播:
Stmt.connStmt
関数内で定義される releaseConn
クロージャのシグネチャが func()
から func(error)
に変更されました。
変更前:
func (s *Stmt) connStmt() (ci driver.Conn, releaseConn func(), si driver.Stmt, err error) {
// ...
releaseConn = func() { s.tx.releaseConn() } // または s.db.putConn(conn, nil)
// ...
}
変更後:
func (s *Stmt) connStmt() (ci driver.Conn, releaseConn func(error), si driver.Stmt, err error) {
// ...
releaseConn = func(error) { s.tx.releaseConn() } // トランザクションの場合
// ...
releaseConn = func(err error) { s.db.putConn(conn, err) } // 通常のDB接続の場合
// ...
}
この変更により、Stmt.Query
や Rows.Close
などで発生したエラー(特に driver.ErrBadConn
)が releaseConn
を介して putConn
に伝播されるようになりました。
putConn
での ErrBadConn
処理:
src/pkg/database/sql/sql.go
の putConn
関数は、渡されたエラーが driver.ErrBadConn
である場合に、その接続をフリーリストに追加しないように変更されました。
func (db *DB) putConn(c driver.Conn, err error) {
if err == driver.ErrBadConn { // ErrBadConn の場合、接続を破棄
db.numOpen--
c.Close()
return
}
// ... 既存の接続プールへの追加ロジック ...
}
このロジックにより、不良な接続がプールに再利用可能なものとして戻されることがなくなり、アプリケーションが常に健全な接続を使用できるようになります。
テスト用 putConnHook
:
src/pkg/database/sql/sql_test.go
の init
関数で設定される putConnHook
は、デバッグとテストの強力なツールです。
func init() {
// ...
putConnHook = func(db *DB, c driver.Conn) {
for _, oc := range db.freeConn {
if oc == c {
// 二重解放を検出した場合、パニック
println("double free of conn. conflicts are:\nA) " + freedFrom[dbConn{db, c}] + "\n\nand\nB) " + stack())
panic("double free of conn.")
}
}
freedFrom[dbConn{db, c}] = stack()
}
}
このフックは、putConn
が呼び出されるたびに、その接続が既にフリーリストに存在しないかを確認します。もし存在すれば、それは二重解放を意味するため、パニックを発生させてテストを失敗させます。これにより、接続管理のバグを早期に発見できるようになります。
これらの変更は、database/sql
パッケージの内部的な接続管理の正確性と堅牢性を大幅に向上させています。
関連リンク
- Go CL (Change List): https://golang.org/cl/5798049
参考にした情報源リンク
- Go
database/sql
パッケージのドキュメント: https://pkg.go.dev/database/sql - Go
database/sql/driver
パッケージのドキュメント: https://pkg.go.dev/database/sql/driver database/sql.ErrBadConn
に関する情報:- https://go.dev/blog/database
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFQqXCG4FIuTcaYnsQCaMG0_VlgJIMQOzFjPpTr3OIK80-rj4cnRocjF3LIN7l6q27Z2XFFUj-VsQZ2-w6D4CvJ0DySf2qQ_sidviSZf8ID2GpVk7sNwpKb2FkdPJe977h1
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHniVKh91tc1c5_m1j6qrf5UDxROACEtwl9h7VM6MMD6mtgdEOfioKKDaJZhs4_hwhHRgp4Y7izpiAyvOXRxePTVeYAuVmjBuBoD_BroAF7aClO1fmJkBJus24Xs_HXnzJkRkPe8f1CWSc-QB7QpimmrLdWE5pEwDvBdc5pNSgh96-4AuniESAW6EEL40V9I-KJne2B0xB5OPWMnr7m3sHd
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHheCnl1e5n-6d7G9Ssc7I5TSmi0MXN44GZtlv1_SBJhvUkqGn1261GeI9lefKJ9sO29en3gedng7kUYoan3fWVJX2Ds_dzFlmaoeoNixZqUydaV4GyPAcDlxO5cs_b6YBMlcAkkrQ4XKzfue-sjy2uaGqwrEEErrjjHm-uSZGksra4t6bXNGkEYyxG9g==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFFZRjp_sr9PkLlAUYhkAgOK1tUQo0IWiAyymqBwz_wAYp7D2XnzeJbZ5QQ6z117RPvSL3j9F8Wi6QinNad-4S_vX9TNTx4Xe8KbU2g1KP8SR2P4sG2tBFedDsJ-x1m88riX_MBLFAVJZWD_A==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEEnhP4bnvD__C646FsoMaTboVTzJyXWzJnrryBUWq22ZgswzFkCfpxK-Y2sbU2-z3WA1t-VtP7gh444mNfGSTDopIR-IpO5w-vd_4_E9j2C9g4B8pIXq0ejRRHCjeJ9sBBBE41fFo0cBpoCQ==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFCnHt4KKrMlHLRkafMz9mQD8XdP7ufw51Uxi2ilVDIyP_D4cBtq210VYjutbbKwTbgBKMvWxT5FrNmNIdQG3RwvVGokWQcBAyV8W384YCNnLRqjSDWRbo-vhRdgqUYpUJgfQCJ
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHUBvdflsxgsl_5tQzaDoOPeabzwQ-_MgpuwTNwEW8HTOHoRaweB8bRIvusJw2enp3zsHJ6tjWnEYRkP9usvhK4NVL_QDuZHGa-aYbV9bIG3_SNgJd-CYx0R5_B5mwoJmtFpKiY
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGlbGAqNRX6ocwunkvVOekXUvVZdlfe3cZSFZTy2ek7zIMvk5rzNCXBtVTBPxlJ0308-bYDJaBk1-9mm1X0s2-4ETYU73P4yM_vxpOHUGMfX1Y99CXenlQHNZC_fr8VvnCIci_T