[インデックス 17810] ファイルの概要
このコミットは、Go言語の標準ライブラリdatabase/sql
パッケージのテストに関する修正です。具体的には、go test -cpu=n,n
フラグを使用して並列テストを実行した際に発生するデッドロックを解消するための変更が加えられています。このデッドロックは、TestConnectionLeak
というテストが実行された後に、内部的に使用されるfakedb
ドライバーが接続を開く際に常に待機状態になることが原因でした。
コミット
commit e39eda1366384cdef21f04c5c964ae93e2ea9ce3
Author: Alberto García Hierro <alberto@garciahierro.com>
Date: Thu Oct 17 09:02:32 2013 -0700
database/sql: make tests repeatable with -cpu=n,n
New test added in CL 14611045 causes a deadlock when
running the tests with -cpu=n,n because the fakedb
driver always waits when opening a new connection after
running TestConnectionLeak. Reset its state after.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/14780043
---
src/pkg/database/sql/fakedb_test.go | 2 ++\n 1 file changed, 2 insertions(+)
diff --git a/src/pkg/database/sql/fakedb_test.go b/src/pkg/database/sql/fakedb_test.go
index 2ed1364759..a8adfdd942 100644
--- a/src/pkg/database/sql/fakedb_test.go
+++ b/src/pkg/database/sql/fakedb_test.go
@@ -151,6 +151,8 @@ func (d *fakeDriver) Open(dsn string) (driver.Conn, error) {
if d.waitCh != nil {
d.waitingCh <- struct{}{}
<-d.waitCh
+ d.waitCh = nil
+ d.waitingCh = nil
}
return conn, nil
}
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e39eda1366384cdef21f04c5c964ae93e2ea9ce3
元コミット内容
このコミットは、database/sql
パッケージのテストが、go test -cpu=n,n
フラグ(複数のCPUコアを使用してテストを並列実行する設定)で実行された際に再現可能なデッドロックを修正することを目的としています。
問題は、以前の変更セット(CL 14611045)で追加された新しいテストが原因で発生していました。このテスト、具体的にはTestConnectionLeak
が実行された後、fakedb
というテスト用の偽のデータベースドライバーが、新しい接続を開こうとする際に常に待機状態に入ってしまうというものでした。この待機状態が、並列実行される他のテストとの間でデッドロックを引き起こしていました。
このコミットでは、TestConnectionLeak
の実行後にfakedb
ドライバーの状態をリセットすることで、このデッドロックを解消しています。
変更の背景
Go言語のテストフレームワークは、go test
コマンドに-cpu
フラグを渡すことで、テストを並列実行するCPUコアの数を指定できます。これは、テストの実行時間を短縮したり、並行処理に関連する潜在的な競合状態やデッドロックを検出したりするのに役立ちます。
このコミットが作成された時点では、database/sql
パッケージのテストスイートにTestConnectionLeak
という新しいテストが追加されていました。このテストは、データベース接続のリーク(解放忘れ)を検出することを目的としていたと考えられます。しかし、このテストが実行された後、テスト内部で使用されるfakedb
ドライバーが特定の状態に陥り、その後の接続要求に対して無限に待機してしまうという副作用がありました。
この問題は、特に-cpu=n,n
のように複数のCPUコアでテストが並列実行される環境で顕在化しました。TestConnectionLeak
が実行された後に別のテストがfakedb
ドライバーを介して新しい接続を要求すると、fakedb
ドライバーが待機状態に入ってしまい、テストスイート全体がデッドロックに陥り、テストが完了しなくなるという状況が発生していました。
このコミットは、このデッドロックを解消し、database/sql
パッケージのテストが-cpu
フラグを使用しても安定して実行できるようにするために必要とされました。
前提知識の解説
database/sql
パッケージ
database/sql
は、Go言語の標準ライブラリに含まれるパッケージで、SQLデータベースとの一般的なインターフェースを提供します。このパッケージ自体は特定のデータベースドライバーを含んでおらず、データベース固有の操作は、このパッケージが定義するインターフェースを実装した外部のデータベースドライバー(例: github.com/go-sql-driver/mysql
、github.com/lib/pq
など)によって提供されます。
主な役割は以下の通りです。
- 抽象化: データベースの種類に依存しない統一されたAPIを提供します。
- 接続プール: データベースへの接続を効率的に管理するための接続プールを内部的に持ちます。
- プレースホルダ: SQLインジェクション攻撃を防ぐためのプレースホルダをサポートします。
- トランザクション: データベーストランザクションを管理するための機能を提供します。
fakedb
ドライバー
fakedb
ドライバーは、Go標準ライブラリのdatabase/sql
パッケージの内部テストで使用される、モック(偽物)のデータベースドライバーです。これは、実際のデータベースに接続することなく、database/sql
パッケージ自体のコア機能や振る舞いをテストするために設計されています。
fakedb
は、database/sql/driver
インターフェースを実装しており、データベースの接続、クエリ実行、トランザクションなどの操作をシミュレートします。これにより、テストは高速かつ決定論的になり、外部サービス(実際のデータベース)への依存がなくなります。このコミットの文脈では、fakedb
ドライバーが特定のテストシナリオで予期せぬ状態に陥り、デッドロックを引き起こす原因となっていました。
go test -cpu
フラグ
go test
コマンドは、Go言語のテストを実行するための標準ツールです。-cpu
フラグは、テストを並列実行する際に使用するCPUコアの数を指定するために使用されます。
go test -cpu 1
: テストを単一のCPUコアで実行します。これは、並行処理に関連する問題(競合状態やデッドロック)を隠蔽する可能性があります。go test -cpu N
: テストを最大N
個のCPUコアで並列実行します。N
は整数です。go test -cpu=n,n
: これは、GOMAXPROCS
環境変数を設定するのと同じ効果を持ちます。n
は利用可能なCPUコアの数を示します。この設定は、テストがシステム上のすべての利用可能なCPUコアを最大限に活用して並列実行されることを意味します。これにより、並行処理のバグがより顕著に現れる可能性が高まります。
このフラグは、特に並行処理を多用するコードのテストにおいて、競合状態やデッドロックなどの問題を早期に発見するために非常に重要です。
デッドロック (Deadlock)
デッドロックとは、複数のプロセスやスレッドが、互いに相手が保持しているリソースの解放を待機し、結果としてどのプロセスも処理を進めることができなくなる状態を指します。Go言語の並行処理では、ゴルーチンとチャネルがデッドロックの原因となることがあります。
このコミットのケースでは、fakedb
ドライバーがチャネルを介して待機状態に入り、そのチャネルが閉じられるか、データが送信されるのを待っていました。しかし、TestConnectionLeak
の実行後にチャネルが適切にリセットされなかったため、後続のテストが同じfakedb
ドライバーを使用しようとした際に、無限の待機状態に陥り、デッドロックが発生しました。
技術的詳細
このコミットで修正された問題は、database/sql
パッケージのテストスイートにおけるfakedb
ドライバーの内部状態管理に起因していました。
fakedb
ドライバーのOpen
メソッドは、新しいデータベース接続を開く際に呼び出されます。このOpen
メソッド内には、d.waitCh
とd.waitingCh
という2つのチャネルに関連するロジックが存在します。
func (d *fakeDriver) Open(dsn string) (driver.Conn, error) {
// ...
if d.waitCh != nil {
d.waitingCh <- struct{}{} // waitingChにシグナルを送信
<-d.waitCh // waitChからのシグナルを待機
}
// ...
}
このコードは、d.waitCh
がnil
でない場合に、d.waitingCh
にシグナルを送信し、その後d.waitCh
からのシグナルを待機するという同期メカニズムを実装しています。これは、特定のテストシナリオ(おそらくTestConnectionLeak
のような、接続のライフサイクルや並行性をテストするシナリオ)において、接続のオープン処理を一時停止させ、外部からの制御を可能にするために使用されていたと考えられます。
問題は、TestConnectionLeak
が実行された後、このd.waitCh
とd.waitingCh
の状態が適切にリセットされなかったことにありました。つまり、TestConnectionLeak
がこれらのチャネルを特定の状態(例えば、d.waitCh
が閉じられていない、またはデータが送信されない状態)にしたまま終了してしまったのです。
その結果、go test -cpu=n,n
で並列実行される他のテストが、TestConnectionLeak
の後に同じfakedb
ドライバーインスタンスを使用してOpen
メソッドを呼び出すと、d.waitCh != nil
の条件が真となり、<-d.waitCh
の行で無限に待機してしまいました。これは、d.waitCh
が閉じられるか、別のゴルーチンからデータが送信されることを期待しているにもかかわらず、それが決して起こらないためです。この無限の待機が、テストスイート全体のデッドロックを引き起こしていました。
このコミットの修正は非常にシンプルですが効果的です。Open
メソッドの同期ロジックの直後に、d.waitCh
とd.waitingCh
をnil
にリセットする行を追加しています。
<-d.waitCh
d.waitCh = nil // ここでチャネルをnilにリセット
d.waitingCh = nil // ここでチャネルをnilにリセット
これにより、Open
メソッドが一度同期処理を完了すると、これらのチャネルは即座にリセットされます。その結果、同じfakedb
ドライバーインスタンスが後続のテストで再利用された場合でも、d.waitCh != nil
の条件が偽となり、同期ロジックがスキップされるため、無限の待機状態に陥ることがなくなります。
この修正は、fakedb
ドライバーの内部状態がテスト間で適切に隔離され、並列実行されるテストが互いに干渉しないようにするために不可欠でした。
コアとなるコードの変更箇所
変更はsrc/pkg/database/sql/fakedb_test.go
ファイルにのみ行われています。
--- a/src/pkg/database/sql/fakedb_test.go
+++ b/src/pkg/database/sql/fakedb_test.go
@@ -151,6 +151,8 @@ func (d *fakeDriver) Open(dsn string) (driver.Conn, error) {
if d.waitCh != nil {
d.waitingCh <- struct{}{}
<-d.waitCh
+ d.waitCh = nil
+ d.waitingCh = nil
}
return conn, nil
}
コアとなるコードの解説
変更は、fakeDriver
構造体のOpen
メソッド内にあります。
元のコードでは、d.waitCh
がnil
でない場合に、d.waitingCh
チャネルに空の構造体struct{}{}
を送信し、その後d.waitCh
チャネルからの受信を待機していました。これは、テストの特定のシナリオで接続のオープン処理を一時停止させるための同期メカニズムです。
追加された2行は以下の通りです。
+ d.waitCh = nil
+ d.waitingCh = nil
これらの行は、<-d.waitCh
による待機が完了した直後に実行されます。これにより、d.waitCh
とd.waitingCh
のチャネルがnil
にリセットされます。
このリセットの重要性は以下の点にあります。
- 状態のクリア:
TestConnectionLeak
のようなテストがfakedb
ドライバーのwaitCh
とwaitingCh
を特定の状態(例えば、waitCh
が閉じられていない、またはデータが送信されない状態)にしたまま終了した場合、その後のテストが同じfakedb
ドライバーインスタンスを再利用すると、Open
メソッドが再び<-d.waitCh
で無限に待機してしまう可能性がありました。 - 並列テストの安定性:
go test -cpu=n,n
でテストが並列実行される場合、fakedb
ドライバーのインスタンスが複数のテストゴルーチン間で共有される可能性があります。このリセットにより、あるテストがfakedb
ドライバーを特定の同期状態にしたまま終了しても、次のテストがその影響を受けずにクリーンな状態でOpen
メソッドを呼び出すことができるようになります。 - デッドロックの解消: チャネルが
nil
にリセットされることで、Open
メソッドが再度呼び出された際にif d.waitCh != nil
の条件が偽となり、同期ロジック全体がスキップされます。これにより、以前発生していた無限の待機とそれに伴うデッドロックが解消されます。
この修正は、fakedb
ドライバーの内部状態をテスト間で適切に隔離し、テストの再現性と安定性を向上させるための重要な変更です。
関連リンク
- Go CL 14780043: https://golang.org/cl/14780043
参考にした情報源リンク
- Go
database/sql
Package Testing: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEo6MEVl14HrfRmdOjE1VLiAMyXA_Iv3YW5HAinnTi1s13ypmpEnu6X4MBUF-BP_ehkIvUBJksUb01yUIcJmqca7o05CSwlChDz5l57M6fqBPZ8igTXXkulYZsbF63Wjz_1Y4dcP5TUSQTyIKvxzzXVOlVxfKqQupf0MMY6ruDUxc4= - Go
go test -cpu
flag: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGNQuSpoTRiVpGBijY-OqBTdx6oBMXoyuZ803MwKjl5bk8B1_AKaVNDDoDUq_O6hkL7Jb22f8N31S4rJEuiOYdLzNg5bywJbU0m-tudiSYj1U95kPp4WlkEdAIU7F01YL-tY91f3whRdiOYcz0oGmozc8Xg - Go
fakedb
driver: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEIs1TzrHn9NUHi8DgbqKRHFDr_c-qeDp13n9MHHRV8UHUkv85LKovcDVb8ut4mkexRnmDRoncTxhKpCgbUKI1S1m5ko4IOWWK-DhALwMh3LnXjRUqyCfqALYc_D-IGuhCbeTW4yFCAKZz9-xQndxVffJ8QTxkMNQJCi0A5i5M34w==