[インデックス 18629] ファイルの概要
このコミットは、Go言語の標準ライブラリ net
パッケージの Dialer
型に KeepAlive
オプションを追加するものです。これにより、TCP接続の確立時にキープアライブ機能を設定できるようになり、アイドル状態のネットワーク接続の健全性を維持し、NATタイムアウトなどによる接続切断を防ぐことが可能になります。
コミット
- コミットハッシュ:
fdfbb406d12a36b09425ecdc76f32345c1af8ffd
- 作者: Brad Fitzpatrick bradfitz@golang.org
- コミット日時: 2014年2月24日 月曜日 13:14:48 -0800
- 変更ファイル:
src/pkg/net/dial.go
src/pkg/net/dial_test.go
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/fdfbb406d12a36b09425ecdc76f32345c1af8ffd
元コミット内容
net: add Dialer.KeepAlive option
LGTM=rsc
R=rsc
CC=golang-codereviews
https://golang.org/cl/68380043
変更の背景
この変更の主な背景には、TCP接続の信頼性と効率性の向上が挙げられます。特に、長期間アイドル状態になる可能性のあるネットワーク接続において、以下のような問題が発生することがあります。
- NAT (Network Address Translation) タイムアウト: 多くのルーターやファイアウォールは、一定時間データが流れないアイドル状態のTCP接続を自動的に切断するNATタイムアウト機能を持っています。これにより、アプリケーションが接続を使用しようとした際に、実際には接続が切断されているにもかかわらず、その事実を認識できず、エラーが発生することがありました。
- 接続の健全性確認: 接続が確立された後も、相手側のホストがクラッシュしたり、ネットワーク経路に問題が発生したりする可能性があります。キープアライブパケットを定期的に送信することで、このような問題を早期に検出し、無効な接続をクリーンアップすることができます。
- リソースの解放: 無効になった接続を早期に検出して閉じることで、システムのリソース(ファイルディスクリプタ、メモリなど)を効率的に解放し、サーバーの安定性とスケーラビリティを向上させることができます。
Dialer
はGoアプリケーションがネットワーク接続を確立する際の主要な手段であり、この Dialer
レベルでキープアライブ設定を制御できるようにすることで、開発者はより柔軟かつ堅牢なネットワークアプリケーションを構築できるようになります。
前提知識の解説
TCP Keep-Alive (TCPキープアライブ)
TCPキープアライブは、アイドル状態のTCP接続がまだ有効であるかどうかを確認するために、定期的に小さなパケット(キープアライブプローブ)を送信するメカニズムです。このパケットはデータを含まず、シーケンス番号が相手が期待する範囲外であるため、相手はACKを返しますが、アプリケーション層には影響を与えません。
- 目的:
- アイドル状態の接続が、NATタイムアウトやファイアウォールによって切断されていないかを確認する。
- ピア(接続相手)がクラッシュしたり、到達不能になったりした場合に、それを検出する。
- 無効な接続を早期にクローズし、リソースを解放する。
- 動作:
- 一定時間(キープアライブタイムアウト)データが流れないと、キープアライブプローブが送信される。
- ピアが応答すれば、接続はアクティブとみなされ、タイマーがリセットされる。
- ピアが応答しない場合、プローブは複数回(キープアライブインターバルで)再送される。
- 設定された再送回数を超えても応答がない場合、接続は切断されたと判断され、ソケットエラーがアプリケーションに通知される。
Go言語の net
パッケージ
net
パッケージは、Go言語でネットワークI/Oを実装するための基本的なインターフェースを提供します。TCP/IP、UDP、Unixドメインソケットなど、様々なネットワークプロトコルをサポートしています。
net.Dialer
: ネットワーク接続を確立するための設定をカプセル化する構造体です。タイムアウト、ローカルアドレスのバインド、デュアルスタックサポートなどのオプションを設定できます。Dial
メソッドを呼び出すことで、指定されたネットワークとアドレスへの接続を試みます。net.Conn
インターフェース: 汎用的なネットワーク接続を表すインターフェースです。Read
、Write
、Close
、LocalAddr
、RemoteAddr
、SetDeadline
などのメソッドを定義します。net.TCPConn
:net.Conn
インターフェースを実装する具体的な型の一つで、TCP接続を表します。TCP固有の操作(例:SetKeepAlive
,SetNoDelay
)を提供します。time.Duration
: Go言語で時間の長さを表す型です。ナノ秒単位で時間を表現し、time.Second
やtime.Minute
などの定数を使って人間が読みやすい形で時間を指定できます。
技術的詳細
このコミットでは、net.Dialer
に KeepAlive
フィールド(型は time.Duration
)が追加され、Dialer.Dial
メソッド内でこの設定が利用されるように変更されました。
変更のロジックは以下の通りです。
Dialer
構造体にKeepAlive time.Duration
フィールドが追加されます。このフィールドがゼロの場合、キープアライブは有効になりません。Dialer.Dial
メソッド内で接続が成功した後、d.KeepAlive > 0
であるかどうかがチェックされます。- もし
KeepAlive
がゼロより大きい値に設定されており、かつ確立された接続が*net.TCPConn
型に型アサーションできる場合(つまりTCP接続である場合)、以下のメソッドが呼び出されます。tc.SetKeepAlive(true)
: これにより、TCPソケットレベルでキープアライブ機能が有効になります。tc.SetKeepAlivePeriod(d.KeepAlive)
: これにより、キープアライブプローブを送信する間隔がDialer.KeepAlive
で指定された期間に設定されます。
testHookSetKeepAlive
というテスト用のフック関数が導入されました。これは、Dialer.Dial
メソッド内でSetKeepAlive
が呼び出されたことをテストから確認するために使用されます。デフォルトでは何もしない関数ですが、テスト中に上書きされます。
この実装により、ユーザーは Dialer
を介して接続を確立する際に、簡単にTCPキープアライブを設定できるようになりました。これは、特にHTTPクライアントやデータベースクライアントなど、長期間接続を維持する必要があるアプリケーションにとって非常に有用です。
コアとなるコードの変更箇所
src/pkg/net/dial.go
--- a/src/pkg/net/dial.go
+++ b/src/pkg/net/dial.go
@@ -44,6 +44,12 @@ type Dialer struct {
// destination is a host name that has multiple address family
// DNS records.
DualStack bool
+
+ // KeepAlive specifies the keep-alive period for an active
+ // network connection.
+ // If zero, keep-alives are not enabled. Network protocols
+ // that do not support keep-alives ignore this field.
+ KeepAlive time.Duration
}
// Return either now+Timeout or Deadline, whichever comes first.
@@ -162,9 +168,19 @@ func (d *Dialer) Dial(network, address string) (Conn, error) {
return dialMulti(network, address, d.LocalAddr, ras, deadline)
}
}\n-\treturn dial(network, ra.toAddr(), dialer, d.deadline())\n+\tc, err := dial(network, ra.toAddr(), dialer, d.deadline())\n+\tif d.KeepAlive > 0 && err == nil {\n+\t\tif tc, ok := c.(*TCPConn); ok {\n+\t\t\ttc.SetKeepAlive(true)\n+\t\t\ttc.SetKeepAlivePeriod(d.KeepAlive)\n+\t\t\ttestHookSetKeepAlive()\n+\t\t}\n+\t}\n+\treturn c, err
}\n
+var testHookSetKeepAlive = func() {} // changed by dial_test.go
+
// dialMulti attempts to establish connections to each destination of
// the list of addresses. It will return the first established
// connection and close the other connections. Otherwise it returns
src/pkg/net/dial_test.go
--- a/src/pkg/net/dial_test.go
+++ b/src/pkg/net/dial_test.go
@@ -555,3 +555,36 @@ func TestDialDualStackLocalhost(t *testing.T) {
}
}\n}\n+\n+func TestDialerKeepAlive(t *testing.T) {\n+\tln := newLocalListener(t)\n+\tdefer ln.Close()\n+\tdefer func() {\n+\t\ttestHookSetKeepAlive = func() {}\n+\t}()\n+\tgo func() {\n+\t\tfor {\n+\t\t\tc, err := ln.Accept()\n+\t\t\tif err != nil {\n+\t\t\t\treturn\n+\t\t\t}\n+\t\t\tc.Close()\n+\t\t}\n+\t}()\n+\tfor _, keepAlive := range []bool{false, true} {\n+\t\tgot := false\n+\t\ttestHookSetKeepAlive = func() { got = true }\n+\t\tvar d Dialer\n+\t\tif keepAlive {\n+\t\t\td.KeepAlive = 30 * time.Second\n+\t\t}\n+\t\tc, err := d.Dial(\"tcp\", ln.Addr().String())\n+\t\tif err != nil {\n+\t\t\tt.Fatal(err)\n+\t\t}\n+\t\tc.Close()\n+\t\tif got != keepAlive {\n+\t\t\tt.Errorf(\"Dialer.KeepAlive = %v: SetKeepAlive called = %v, want %v\", d.KeepAlive, got, !got)\n+\t\t}\n+\t}\n+}\n```
## コアとなるコードの解説
### `src/pkg/net/dial.go` の変更点
1. **`Dialer` 構造体への `KeepAlive` フィールドの追加**:
```go
type Dialer struct {
// ... 既存のフィールド ...
// KeepAlive specifies the keep-alive period for an active
// network connection.
// If zero, keep-alives are not enabled. Network protocols
// that do not support keep-alives ignore this field.
KeepAlive time.Duration
}
```
`Dialer` に `KeepAlive` という `time.Duration` 型のフィールドが追加されました。このフィールドは、キープアライブパケットを送信する間隔を指定します。コメントにもあるように、`0` の場合はキープアライブが無効になり、TCP以外のプロトコルでは無視されます。
2. **`Dialer.Dial` メソッド内のキープアライブ設定ロジック**:
```go
func (d *Dialer) Dial(network, address string) (Conn, error) {
// ... 既存の接続確立ロジック ...
c, err := dial(network, ra.toAddr(), dialer, d.deadline())
if d.KeepAlive > 0 && err == nil { // KeepAliveが設定されており、接続が成功した場合
if tc, ok := c.(*TCPConn); ok { // 接続がTCPConnである場合
tc.SetKeepAlive(true) // キープアライブを有効にする
tc.SetKeepAlivePeriod(d.KeepAlive) // キープアライブの間隔を設定する
testHookSetKeepAlive() // テストフックを呼び出す
}
}
return c, err
}
```
`Dial` メソッドは、内部の `dial` 関数で実際の接続を確立した後、返された接続 `c` とエラー `err` を受け取ります。
- `d.KeepAlive > 0 && err == nil`: `Dialer` にキープアライブ期間が設定されており、かつ接続がエラーなく確立された場合に、キープアライブ設定の処理に進みます。
- `if tc, ok := c.(*TCPConn); ok`: 確立された接続 `c` が `*TCPConn` 型であるかどうかの型アサーションを行います。キープアライブはTCP固有の機能であるため、TCP接続の場合のみ設定が必要です。
- `tc.SetKeepAlive(true)`: `*TCPConn` の `SetKeepAlive` メソッドを呼び出し、ソケットオプションとしてキープアライブを有効にします。
- `tc.SetKeepAlivePeriod(d.KeepAlive)`: `*TCPConn` の `SetKeepAlivePeriod` メソッドを呼び出し、キープアライブパケットの送信間隔を `Dialer.KeepAlive` で指定された値に設定します。
- `testHookSetKeepAlive()`: テスト時にキープアライブが設定されたことを検出するためのフック関数です。
3. **`testHookSetKeepAlive` の追加**:
```go
var testHookSetKeepAlive = func() {} // changed by dial_test.go
```
これは、テストコードからキープアライブ設定が正しく呼び出されたことを検証するためのグローバル変数です。テスト時にはこの関数が上書きされ、呼び出しを記録します。
### `src/pkg/net/dial_test.go` の変更点
1. **`TestDialerKeepAlive` 関数の追加**:
```go
func TestDialerKeepAlive(t *testing.T) {
ln := newLocalListener(t)
defer ln.Close()
defer func() {
testHookSetKeepAlive = func() {} // テスト終了後にフックをリセット
}()
go func() {
for {
c, err := ln.Accept()
if err != nil {
return
}
c.Close() // 接続を受け入れたらすぐに閉じる
}
}()
for _, keepAlive := range []bool{false, true} {
got := false
testHookSetKeepAlive = func() { got = true } // フックを上書きして呼び出しを記録
var d Dialer
if keepAlive {
d.KeepAlive = 30 * time.Second // キープアライブ期間を設定
}
c, err := d.Dial("tcp", ln.Addr().String())
if err != nil {
t.Fatal(err)
}
c.Close()
if got != keepAlive { // 期待通りの結果が得られたか検証
t.Errorf("Dialer.KeepAlive = %v: SetKeepAlive called = %v, want %v", d.KeepAlive, got, !got)
}
}
}
```
このテスト関数は、`Dialer.KeepAlive` オプションが正しく機能するかどうかを検証します。
- ローカルリスナー (`ln`) を作成し、接続を受け入れるゴルーチンを起動します。
- `testHookSetKeepAlive` を上書きして、キープアライブが設定された場合に `got` 変数を `true` に設定するようにします。
- `keepAlive` が `true` の場合と `false` の場合の両方で `Dialer` を使用して接続を試みます。
- `Dialer.KeepAlive` が設定されている (`keepAlive` が `true`) 場合にのみ `testHookSetKeepAlive` が呼び出され (`got` が `true` になる) 、そうでない場合は呼び出されない (`got` が `false` のまま) ことを検証します。これにより、`Dialer.KeepAlive` の設定が `SetKeepAlive` の呼び出しに正しく反映されることを確認しています。
## 関連リンク
- Go `net` パッケージのドキュメント: [https://pkg.go.dev/net](https://pkg.go.dev/net)
- Go `net.Dialer` のドキュメント: [https://pkg.go.dev/net#Dialer](https://pkg.go.dev/net#Dialer)
- Go `net.TCPConn` のドキュメント: [https://pkg.go.dev/net#TCPConn](https://pkg.go.dev/net#TCPConn)
- Go `time` パッケージのドキュメント: [https://pkg.go.dev/time](https://pkg.go.dev/time)
- このコミットのGo Code Review (CL): [https://golang.org/cl/68380043](https://golang.org/cl/68380043)
## 参考にした情報源リンク
- TCP Keepalive HOWTO: [https://tldp.org/HOWTO/TCP-Keepalive-HOWTO/index.html](https://tldp.org/HOWTO/TCP-Keepalive-HOWTO/index.html)
- What is TCP Keepalive?: [https://www.baeldung.com/linux/tcp-keepalive](https://www.baeldung.com/linux/tcp-keepalive)
- Go net package source code (for context and understanding of `dial` function): [https://github.com/golang/go/tree/master/src/net](https://github.com/golang/go/tree/master/src/net)
- Go `net` package history (to understand the context around 2014): [https://go.googlesource.com/go/+log/refs/heads/master/src/net](https://go.googlesource.com/go/+log/refs/heads/master/src/net)
(Note: Specific historical context was inferred from the commit date and general knowledge of network programming needs.)
# [インデックス 18629] ファイルの概要
このコミットは、Go言語の標準ライブラリ `net` パッケージの `Dialer` 型に `KeepAlive` オプションを追加するものです。これにより、TCP接続の確立時にキープアライブ機能を設定できるようになり、アイドル状態のネットワーク接続の健全性を維持し、NATタイムアウトなどによる接続切断を防ぐことが可能になります。
## コミット
- **コミットハッシュ**: `fdfbb406d12a36b09425ecdc76f32345c1af8ffd`
- **作者**: Brad Fitzpatrick <bradfitz@golang.org>
- **コミット日時**: 2014年2月24日 月曜日 13:14:48 -0800
- **変更ファイル**:
- `src/pkg/net/dial.go`
- `src/pkg/net/dial_test.go`
## GitHub上でのコミットページへのリンク
[https://github.com/golang/go/commit/fdfbb406d12a36b09425ecdc76f32345c1af8ffd](https://github.com/golang/go/commit/fdfbb406d12a36b09425ecdc76f32345c1af8ffd)
## 元コミット内容
net: add Dialer.KeepAlive option
LGTM=rsc R=rsc CC=golang-codereviews https://golang.org/cl/68380043
## 変更の背景
この変更の主な背景には、TCP接続の信頼性と効率性の向上が挙げられます。特に、長期間アイドル状態になる可能性のあるネットワーク接続において、以下のような問題が発生することがありました。
1. **NAT (Network Address Translation) タイムアウト**: 多くのルーターやファイアウォールは、一定時間データが流れないアイドル状態のTCP接続を自動的に切断するNATタイムアウト機能を持っています。これにより、アプリケーションが接続を使用しようとした際に、実際には接続が切断されているにもかかわらず、その事実を認識できず、エラーが発生することがありました。
2. **接続の健全性確認**: 接続が確立された後も、相手側のホストがクラッシュしたり、ネットワーク経路に問題が発生したりする可能性があります。キープアライブパケットを定期的に送信することで、このような問題を早期に検出し、無効な接続をクリーンアップすることができます。
3. **リソースの解放**: 無効になった接続を早期に検出して閉じることで、システムのリソース(ファイルディスクリプタ、メモリなど)を効率的に解放し、サーバーの安定性とスケーラビリティを向上させることができます。
`Dialer` はGoアプリケーションがネットワーク接続を確立する際の主要な手段であり、この `Dialer` レベルでキープアライブ設定を制御できるようにすることで、開発者はより柔軟かつ堅牢なネットワークアプリケーションを構築できるようになります。
## 前提知識の解説
### TCP Keep-Alive (TCPキープアライブ)
TCPキープアライブは、アイドル状態のTCP接続がまだ有効であるかどうかを確認するために、定期的に小さなパケット(キープアライブプローブ)を送信するメカニズムです。このパケットはデータを含まず、シーケンス番号が相手が期待する範囲外であるため、相手はACKを返しますが、アプリケーション層には影響を与えません。
- **目的**:
- アイドル状態の接続が、NATタイムアウトやファイアウォールによって切断されていないかを確認する。
- ピア(接続相手)がクラッシュしたり、到達不能になったりした場合に、それを検出する。
- 無効な接続を早期にクローズし、リソースを解放する。
- **動作**:
1. 一定時間(キープアライブタイムアウト)データが流れないと、キープアライブプローブが送信される。
2. ピアが応答すれば、接続はアクティブとみなされ、タイマーがリセットされる。
3. ピアが応答しない場合、プローブは複数回(キープアライブインターバルで)再送される。
4. 設定された再送回数を超えても応答がない場合、接続は切断されたと判断され、ソケットエラーがアプリケーションに通知される。
### Go言語の `net` パッケージ
`net` パッケージは、Go言語でネットワークI/Oを実装するための基本的なインターフェースを提供します。TCP/IP、UDP、Unixドメインソケットなど、様々なネットワークプロトコルをサポートしています。
- **`net.Dialer`**: ネットワーク接続を確立するための設定をカプセル化する構造体です。タイムアウト、ローカルアドレスのバインド、デュアルスタックサポートなどのオプションを設定できます。`Dial` メソッドを呼び出すことで、指定されたネットワークとアドレスへの接続を試みます。
- **`net.Conn` インターフェース**: 汎用的なネットワーク接続を表すインターフェースです。`Read`、`Write`、`Close`、`LocalAddr`、`RemoteAddr`、`SetDeadline` などのメソッドを定義します。
- **`net.TCPConn`**: `net.Conn` インターフェースを実装する具体的な型の一つで、TCP接続を表します。TCP固有の操作(例: `SetKeepAlive`, `SetNoDelay`)を提供します。
- **`time.Duration`**: Go言語で時間の長さを表す型です。ナノ秒単位で時間を表現し、`time.Second` や `time.Minute` などの定数を使って人間が読みやすい形で時間を指定できます。
## 技術的詳細
このコミットでは、`net.Dialer` に `KeepAlive` フィールド(型は `time.Duration`)が追加され、`Dialer.Dial` メソッド内でこの設定が利用されるように変更されました。
変更のロジックは以下の通りです。
1. `Dialer` 構造体に `KeepAlive time.Duration` フィールドが追加されます。このフィールドがゼロの場合、キープアライブは有効になりません。
2. `Dialer.Dial` メソッド内で接続が成功した後、`d.KeepAlive > 0` であるかどうかがチェックされます。
3. もし `KeepAlive` がゼロより大きい値に設定されており、かつ確立された接続が `*net.TCPConn` 型に型アサーションできる場合(つまりTCP接続である場合)、以下のメソッドが呼び出されます。
- `tc.SetKeepAlive(true)`: これにより、TCPソケットレベルでキープアライブ機能が有効になります。
- `tc.SetKeepAlivePeriod(d.KeepAlive)`: これにより、キープアライブプローブを送信する間隔が `Dialer.KeepAlive` で指定された期間に設定されます。
4. `testHookSetKeepAlive` というテスト用のフック関数が導入されました。これは、`Dialer.Dial` メソッド内で `SetKeepAlive` が呼び出されたことをテストから確認するために使用されます。デフォルトでは何もしない関数ですが、テスト中に上書きされます。
この実装により、ユーザーは `Dialer` を介して接続を確立する際に、簡単にTCPキープアライブを設定できるようになりました。これは、特にHTTPクライアントやデータベースクライアントなど、長期間接続を維持する必要があるアプリケーションにとって非常に有用です。
## コアとなるコードの変更箇所
### `src/pkg/net/dial.go`
```diff
--- a/src/pkg/net/dial.go
+++ b/src/pkg/net/dial.go
@@ -44,6 +44,12 @@ type Dialer struct {
// destination is a host name that has multiple address family
// DNS records.
DualStack bool
+
+ // KeepAlive specifies the keep-alive period for an active
+ // network connection.
+ // If zero, keep-alives are not enabled. Network protocols
+ // that do not support keep-alives ignore this field.
+ KeepAlive time.Duration
}
// Return either now+Timeout or Deadline, whichever comes first.
@@ -162,9 +168,19 @@ func (d *Dialer) Dial(network, address string) (Conn, error) {
return dialMulti(network, address, d.LocalAddr, ras, deadline)
}
}\n-\treturn dial(network, ra.toAddr(), dialer, d.deadline())\n+\tc, err := dial(network, ra.toAddr(), dialer, d.deadline())\n+\tif d.KeepAlive > 0 && err == nil {\n+\t\tif tc, ok := c.(*TCPConn); ok {\n+\t\t\ttc.SetKeepAlive(true)\n+\t\t\ttc.SetKeepAlivePeriod(d.KeepAlive)\n+\t\t\ttestHookSetKeepAlive()\n+\t\t}\n+\t}\n+\treturn c, err
}\n
+var testHookSetKeepAlive = func() {} // changed by dial_test.go
+
// dialMulti attempts to establish connections to each destination of
// the list of addresses. It will return the first established
// connection and close the other connections. Otherwise it returns
src/pkg/net/dial_test.go
--- a/src/pkg/net/dial_test.go
+++ b/src/pkg/net/dial_test.go
@@ -555,3 +555,36 @@ func TestDialDualStackLocalhost(t *testing.T) {
}
}\n}\n+\n+func TestDialerKeepAlive(t *testing.T) {\n+\tln := newLocalListener(t)\n+\tdefer ln.Close()\n+\tdefer func() {\n+\t\ttestHookSetKeepAlive = func() {}\n+\t}()\n+\tgo func() {\n+\t\tfor {\n+\t\t\tc, err := ln.Accept()\n+\t\t\tif err != nil {\n+\t\t\t\treturn\n+\t\t\t}\n+\t\t\tc.Close()\n+\t\t}\n+\t}()\n+\tfor _, keepAlive := range []bool{false, true} {\n+\t\tgot := false\n+\t\ttestHookSetKeepAlive = func() { got = true }\n+\t\tvar d Dialer\n+\t\tif keepAlive {\n+\t\t\td.KeepAlive = 30 * time.Second\n+\t\t}\n+\t\tc, err := d.Dial(\"tcp\", ln.Addr().String())\n+\t\tif err != nil {\n+\t\t\tt.Fatal(err)\n+\t\t}\n+\t\tc.Close()\n+\t\tif got != keepAlive {\n+\t\t\tt.Errorf(\"Dialer.KeepAlive = %v: SetKeepAlive called = %v, want %v\", d.KeepAlive, got, !got)\n+\t\t}\n+\t}\n+}\n```
## コアとなるコードの解説
### `src/pkg/net/dial.go` の変更点
1. **`Dialer` 構造体への `KeepAlive` フィールドの追加**:
```go
type Dialer struct {
// ... 既存のフィールド ...
// KeepAlive specifies the keep-alive period for an active
// network connection.
// If zero, keep-alives are not enabled. Network protocols
// that do not support keep-alives ignore this field.
KeepAlive time.Duration
}
```
`Dialer` に `KeepAlive` という `time.Duration` 型のフィールドが追加されました。このフィールドは、キープアライブパケットを送信する間隔を指定します。コメントにもあるように、`0` の場合はキープアライブが無効になり、TCP以外のプロトコルでは無視されます。
2. **`Dialer.Dial` メソッド内のキープアライブ設定ロジック**:
```go
func (d *Dialer) Dial(network, address string) (Conn, error) {
// ... 既存の接続確立ロジック ...
c, err := dial(network, ra.toAddr(), dialer, d.deadline())
if d.KeepAlive > 0 && err == nil { // KeepAliveが設定されており、接続が成功した場合
if tc, ok := c.(*TCPConn); ok { // 接続がTCPConnである場合
tc.SetKeepAlive(true) // キープアライブを有効にする
tc.SetKeepAlivePeriod(d.KeepAlive) // キープアライブの間隔を設定する
testHookSetKeepAlive() // テストフックを呼び出す
}
}
return c, err
}
```
`Dial` メソッドは、内部の `dial` 関数で実際の接続を確立した後、返された接続 `c` とエラー `err` を受け取ります。
- `d.KeepAlive > 0 && err == nil`: `Dialer` にキープアライブ期間が設定されており、かつ接続がエラーなく確立された場合に、キープアライブ設定の処理に進みます。
- `if tc, ok := c.(*TCPConn); ok`: 確立された接続 `c` が `*TCPConn` 型であるかどうかの型アサーションを行います。キープアライブはTCP固有の機能であるため、TCP接続の場合のみ設定が必要です。
- `tc.SetKeepAlive(true)`: `*TCPConn` の `SetKeepAlive` メソッドを呼び出し、ソケットオプションとしてキープアライブを有効にします。
- `tc.SetKeepAlivePeriod(d.KeepAlive)`: `*TCPConn` の `SetKeepAlivePeriod` メソッドを呼び出し、キープアライブパケットの送信間隔を `Dialer.KeepAlive` で指定された値に設定します。
- `testHookSetKeepAlive()`: テスト時にキープアライブが設定されたことを検出するためのフック関数です。
3. **`testHookSetKeepAlive` の追加**:
```go
var testHookSetKeepAlive = func() {} // changed by dial_test.go
```
これは、テストコードからキープアライブ設定が正しく呼び出されたことを検証するためのグローバル変数です。テスト時にはこの関数が上書きされ、呼び出しを記録します。
### `src/pkg/net/dial_test.go` の変更点
1. **`TestDialerKeepAlive` 関数の追加**:
```go
func TestDialerKeepAlive(t *testing.T) {
ln := newLocalListener(t)
defer ln.Close()
defer func() {
testHookSetKeepAlive = func() {} // テスト終了後にフックをリセット
}()
go func() {
for {
c, err := ln.Accept()
if err != nil {
return
}
c.Close() // 接続を受け入れたらすぐに閉じる
}
}()
for _, keepAlive := range []bool{false, true} {
got := false
testHookSetKeepAlive = func() { got = true } // フックを上書きして呼び出しを記録
var d Dialer
if keepAlive {
d.KeepAlive = 30 * time.Second // キープアライブ期間を設定
}
c, err := d.Dial("tcp", ln.Addr().String())
if err != nil {
t.Fatal(err)
}
c.Close()
if got != keepAlive { // 期待通りの結果が得られたか検証
t.Errorf("Dialer.KeepAlive = %v: SetKeepAlive called = %v, want %v", d.KeepAlive, got, !got)
}
}
}
```
このテスト関数は、`Dialer.KeepAlive` オプションが正しく機能するかどうかを検証します。
- ローカルリスナー (`ln`) を作成し、接続を受け入れるゴルーチンを起動します。
- `testHookSetKeepAlive` を上書きして、キープアライブが設定された場合に `got` 変数を `true` に設定するようにします。
- `keepAlive` が `true` の場合と `false` の場合の両方で `Dialer` を使用して接続を試みます。
- `Dialer.KeepAlive` が設定されている (`keepAlive` が `true`) 場合にのみ `testHookSetKeepAlive` が呼び出され (`got` が `true` になる) 、そうでない場合は呼び出されない (`got` が `false` のまま) ことを検証します。これにより、`Dialer.KeepAlive` の設定が `SetKeepAlive` の呼び出しに正しく反映されることを確認しています。
## 関連リンク
- Go `net` パッケージのドキュメント: [https://pkg.go.dev/net](https://pkg.go.dev/net)
- Go `net.Dialer` のドキュメント: [https://pkg.go.dev/net#Dialer](https://pkg.go.dev/net#Dialer)
- Go `net.TCPConn` のドキュメント: [https://pkg.go.dev/net#TCPConn](https://pkg.go.dev/net#TCPConn)
- Go `time` パッケージのドキュメント: [https://pkg.go.dev/time](https://pkg.go.dev/time)
- このコミットのGo Code Review (CL): [https://golang.org/cl/68380043](https://golang.org/cl/68380043)
## 参考にした情報源リンク
- TCP Keepalive HOWTO: [https://tldp.org/HOWTO/TCP-Keepalive-HOWTO/index.html](https://tldp.org/HOWTO/TCP-Keepalive-HOWTO/index.html)
- What is TCP Keepalive?: [https://www.baeldung.com/linux/tcp-keepalive](https://www.baeldung.com/linux/tcp-keepalive)
- Go net package source code (for context and understanding of `dial` function): [https://github.com/golang/go/tree/master/src/net](https://github.com/golang/go/tree/master/src/net)
- Go `net` package history (to understand the context around 2014): [https://go.googlesource.com/go/+log/refs/heads/master/src/net](https://go.googlesource.com/go/+log/refs/heads/master/src/net)
(Note: Specific historical context was inferred from the commit date and general knowledge of network programming needs.)