[インデックス 16364] ファイルの概要
このコミットは、Go言語の標準ライブラリである database/sql
パッケージ内の src/pkg/database/sql/sql.go
ファイルに対する変更です。database/sql
パッケージは、GoアプリケーションからSQLデータベースにアクセスするための汎用的なインターフェースを提供します。このファイルには、データベース接続、ステートメント、トランザクションなどの管理に関するコアロジックが含まれています。
コミット
commit 0e10196982e81be38c46b77572837ccf90cb3366
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Tue May 21 14:58:08 2013 -0700
database/sql: remove extra RemoveDep call
This should have been removed in 45c12efb4635. Not a correctness
issue, but unnecessary work.
This CL also adds paranoia checks in removeDep so this doesn't
happen again.
Fixes #5502
R=adg
CC=gobot, golang-dev, google
https://golang.org/cl/9543043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0e10196982e81be38c46b77572837ccf90cb3366
元コミット内容
database/sql: remove extra RemoveDep call
This should have been removed in 45c12efb4635. Not a correctness
issue, but unnecessary work.
This CL also adds paranoia checks in removeDep so this doesn't
happen again.
Fixes #5502
変更の背景
このコミットは、以前のコミット 45c12efb4635
で完全に削除されるべきだった、database/sql
パッケージ内の不要な RemoveDep
(または removeDep
) の呼び出しを修正することを目的としています。この不要な呼び出しは、プログラムの正しさには影響を与えませんでしたが、余分な処理を引き起こし、パフォーマンスの低下やリソースの無駄遣いにつながる可能性がありました。
また、このコミットは、将来的に同様の問題が発生しないように、removeDep
関数に「パラノイアチェック」(厳密な検証)を追加しています。これは、依存関係の削除が常に期待通りに行われることを保証するための防御的なプログラミング手法です。
前提知識の解説
database/sql
パッケージ: Go言語の標準ライブラリで、SQLデータベースとのやり取りのための汎用的なインターフェースを提供します。データベースドライバーを介して、様々なデータベース(MySQL, PostgreSQL, SQLiteなど)に接続できます。finalCloser
インターフェース:database/sql
パッケージ内部で使用されるインターフェースで、リソースが最終的にクローズされるべきであることを示すために使用されます。データベース接続やステートメントなどがこのインターフェースを実装することがあります。- 依存関係の追跡:
database/sql
パッケージでは、データベース接続 (DB
) が、その接続から派生したステートメント (Stmt
) やトランザクションなどのリソースのライフサイクルを管理するために、内部的に依存関係を追跡しています。これは、親リソースがクローズされる前に、子リソースが適切にクリーンアップされることを保証するためです。 removeDep
関数:database/sql
パッケージ内部の関数で、特定の依存関係を追跡リストから削除する役割を担っています。リソースが不要になったり、クローズされたりした際に呼び出されます。- 「不要な作業」 (unnecessary work): プログラムの実行において、結果に影響を与えないにもかかわらず行われる処理のことです。これは、CPUサイクル、メモリ、ネットワーク帯域などのリソースを無駄に消費し、アプリケーションの効率を低下させる可能性があります。
- 「パラノイアチェック」 (paranoia checks): プログラムの堅牢性を高めるために、通常は発生しないと想定されるような異常な状態を検出するための厳密なチェックのことです。これにより、予期せぬバグやロジックの欠陥を早期に発見し、パニック(プログラムの異常終了)を引き起こすことで、より深刻な問題の発生を防ぎます。
技術的詳細
このコミットの主要な変更点は、database/sql/sql.go
ファイル内の removeDepLocked
関数と Stmt.finalClose
関数にあります。
removeDepLocked
関数は、DB
オブジェクトが管理する依存関係マップ db.dep
から、特定の finalCloser
オブジェクト (x
) に関連付けられた依存関係 (dep
) を削除する役割を担っています。この関数は、ミューテックスによって保護されたロックされたコンテキストで実行されることを想定しています。
変更前は、removeDepLocked
は done
というブール変数を使用して、依存関係が削除されたかどうかを追跡していました。しかし、このロジックは、依存関係が実際に存在しない場合に、delete
操作が何も削除しないという Go の map
の特性を考慮していませんでした。
変更後、removeDepLocked
には以下の「パラノイアチェック」が追加されました。
xdep, ok := db.dep[x]
:db.dep
マップからx
に対応する依存関係マップ (xdep
) を取得します。if !ok { panic(fmt.Sprintf("unpaired removeDep: no deps for %T", x)) }
: もしx
に対応する依存関係マップが存在しない場合(!ok
)、これはremoveDep
が存在しない依存関係に対して呼び出されたことを意味し、論理的なエラーであるためpanic
を発生させます。l0 := len(xdep)
:delete
操作を行う前のxdep
の長さを記録します。delete(xdep, dep)
: 実際に依存関係dep
をxdep
から削除します。switch len(xdep) { case l0: panic(...) }
:delete
操作後のxdep
の長さがl0
と同じ場合、これはdep
がxdep
に存在せず、何も削除されなかったことを意味します。これも論理的なエラーであるためpanic
を発生させます。このpanic
メッセージは"unpaired removeDep: no %T dep on %T"
となり、どの依存関係がどのオブジェクトから削除されなかったのかを示します。
これらのチェックは、removeDep
が常に有効な依存関係に対して呼び出され、実際に依存関係が削除されることを保証するためのものです。これにより、将来的に removeDep
の呼び出しが誤って行われた場合に、早期に問題を検出できるようになります。
また、Stmt.finalClose
関数からは、s.db.removeDep(v.dc, s)
という行が削除されました。これは、コミットメッセージにある「extra RemoveDep call」に該当する部分です。この呼び出しは冗長であり、不要な処理を引き起こしていたため削除されました。
コアとなるコードの変更箇所
--- a/src/pkg/database/sql/sql.go
+++ b/src/pkg/database/sql/sql.go
@@ -357,21 +357,27 @@ func (db *DB) removeDep(x finalCloser, dep interface{}) error {
func (db *DB) removeDepLocked(x finalCloser, dep interface{}) func() error {
//println(fmt.Sprintf("removeDep(%T %p, %T %p)", x, x, dep, dep))
- done := false
- xdep := db.dep[x]
- if xdep != nil {
- delete(xdep, dep)
- if len(xdep) == 0 {
- delete(db.dep, x)
- done = true
- }
+ xdep, ok := db.dep[x]
+ if !ok {
+ panic(fmt.Sprintf("unpaired removeDep: no deps for %T", x))
}
- if !done {
+ l0 := len(xdep)
+ delete(xdep, dep)
+
+ switch len(xdep) {
+ case l0:
+ // Nothing removed. Shouldn't happen.
+ panic(fmt.Sprintf("unpaired removeDep: no %T dep on %T", dep, x))
+ case 0:
+ // No more dependencies.
+ delete(db.dep, x)
+ return x.finalClose
+ default:
+ // Dependencies remain.
return func() error { return nil }
}
- return x.finalClose
}
// Open opens a database specified by its database driver name and a
@@ -1261,7 +1267,6 @@ func (s *Stmt) finalClose() error {
for _, v := range s.css {
s.db.noteUnusedDriverStatement(v.dc, v.si)
v.dc.removeOpenStmt(v.si)
- s.db.removeDep(v.dc, s)
}
s.css = nil
return nil
コアとなるコードの解説
removeDepLocked
関数の変更
-
変更前:
done
というフラグを使って、依存関係が削除されたかどうかを追跡していました。xdep := db.dep[x]
で依存関係マップを取得し、if xdep != nil
で存在チェックをしていました。delete(xdep, dep)
で依存関係を削除し、len(xdep) == 0
で依存関係がなくなった場合にdb.dep
からも削除していました。if !done
の条件で、依存関係が削除されなかった場合にnil
を返す関数を返していました。
-
変更後:
xdep, ok := db.dep[x]
を使用して、依存関係マップの存在チェックをより Go らしい方法で行っています。if !ok { panic(...) }
の追加:x
に対応する依存関係マップが存在しない場合、これは論理的なエラーであり、removeDep
が誤って呼び出されたことを意味するため、panic
を発生させます。これが「パラノイアチェック」の一つです。l0 := len(xdep)
の追加:delete
操作の前にxdep
の要素数を記録します。switch len(xdep) { case l0: panic(...) }
の追加:delete
操作後に要素数が変わっていない場合、それは削除しようとしたdep
が存在しなかったことを意味します。これも論理的なエラーであるため、panic
を発生させます。これがもう一つの「パラノイアチェック」です。case 0:
: 依存関係がすべて削除された場合(xdep
が空になった場合)、x.finalClose
を返します。これは、親リソースがクリーンアップされるべきであることを示します。default:
: 依存関係がまだ残っている場合、nil
を返す関数を返します。
これらの変更により、removeDepLocked
はより堅牢になり、依存関係の管理における潜在的なバグを早期に検出できるようになりました。
Stmt.finalClose
関数の変更
-
変更前:
s.db.removeDep(v.dc, s)
という行がありました。これは、ステートメント (s
) がクローズされる際に、そのステートメントが依存しているデータベース接続 (v.dc
) から、ステートメント自身の依存関係を削除する呼び出しです。
-
変更後:
s.db.removeDep(v.dc, s)
の行が完全に削除されました。
この削除は、コミットメッセージにある「remove extra RemoveDep call」に直接対応しています。この呼び出しは冗長であり、database/sql
パッケージの内部ロジックにおいて不要な処理を引き起こしていたため、削除されました。これにより、不要なオーバーヘッドが削減され、コードの効率が向上します。
関連リンク
- Go Issue #5502: このコミットが修正したとされる問題のIDです。
参考にした情報源リンク
- コミットメッセージ自体
- Go言語の
database/sql
パッケージのドキュメント (一般的な情報) - Go言語のソースコード (変更された
sql.go
ファイル)