Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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) を削除する役割を担っています。この関数は、ミューテックスによって保護されたロックされたコンテキストで実行されることを想定しています。

変更前は、removeDepLockeddone というブール変数を使用して、依存関係が削除されたかどうかを追跡していました。しかし、このロジックは、依存関係が実際に存在しない場合に、delete 操作が何も削除しないという Go の map の特性を考慮していませんでした。

変更後、removeDepLocked には以下の「パラノイアチェック」が追加されました。

  1. xdep, ok := db.dep[x]: db.dep マップから x に対応する依存関係マップ (xdep) を取得します。
  2. if !ok { panic(fmt.Sprintf("unpaired removeDep: no deps for %T", x)) }: もし x に対応する依存関係マップが存在しない場合(!ok)、これは removeDep が存在しない依存関係に対して呼び出されたことを意味し、論理的なエラーであるため panic を発生させます。
  3. l0 := len(xdep): delete 操作を行う前の xdep の長さを記録します。
  4. delete(xdep, dep): 実際に依存関係 depxdep から削除します。
  5. switch len(xdep) { case l0: panic(...) }: delete 操作後の xdep の長さが l0 と同じ場合、これは depxdep に存在せず、何も削除されなかったことを意味します。これも論理的なエラーであるため 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 ファイル)