[インデックス 18214] ファイルの概要
このコミットは、Go言語の標準ライブラリであるnet
パッケージにおいて、Windows環境でのTCP Keep-Alive期間を設定する機能を追加するものです。具体的には、net.TCPConn
のSetKeepAlivePeriod
メソッドがWindows上で実際に機能するように、基盤となるシステムコール層の変更と、それを利用するネットワーク層のコードが追加されています。
コミット
commit 5277b90ec43e9b75f481ce737ade1d2a78bd32e1
Author: Nicholas Katsaros <nick@nickkatsaros.com>
Date: Fri Jan 10 14:33:54 2014 +1100
net: add SetKeepAlivePeriod for windows
R=golang-codereviews, alex.brainman, bradfitz, mikioh.mikioh
CC=golang-codereviews
https://golang.org/cl/11393043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5277b90ec43e9b75f481ce737ade1d2a78bd32e1
元コミット内容
このコミットの元々の内容は、Go言語のnet
パッケージにおいて、Windows環境でTCP Keep-Aliveの期間を設定する機能(SetKeepAlivePeriod
)を実装することです。以前は、この機能はWindowsでは「未実装(no-op)」であり、設定しようとしても実際には何も行われませんでした。このコミットによって、Windows固有のシステムコールを利用して、この機能が有効化されます。
変更の背景
TCP Keep-Aliveは、アイドル状態のTCP接続がまだ有効であるかを確認するために、定期的に小さなパケットを送信するメカニズムです。これにより、ネットワーク機器(ルーター、ファイアウォールなど)がアイドル接続をタイムアウトで切断してしまうことを防ぎ、また、接続相手がクラッシュしたりネットワークから切断されたりした場合に、アプリケーションがそれを検知できるようになります。
Go言語のnet
パッケージは、クロスプラットフォームで動作するように設計されていますが、特定のOS固有の機能については、それぞれのOSのAPIを利用して実装する必要があります。Windows環境において、SetKeepAlivePeriod
が機能しないことは、長時間アイドル状態になる可能性のあるTCP接続を扱うアプリケーションにとって問題でした。例えば、データベース接続やメッセージキューへの接続など、常にオープンである必要があるが、データ転送が頻繁ではないアプリケーションでは、Keep-Aliveが適切に設定されていないと、意図せず接続が切断されてしまう可能性がありました。
このコミットは、WindowsのネットワークAPIであるWSAIoctl
とSIO_KEEPALIVE_VALS
を利用して、この機能の欠落を解消し、GoアプリケーションがWindows上でも信頼性の高いTCP接続を維持できるようにすることを目的としています。
前提知識の解説
TCP Keep-Alive
TCP Keep-Aliveは、TCPプロトコルの一部として提供されるオプション機能です。これは、データが交換されていないアイドル状態のTCP接続が、まだ有効であるかどうかを確認するために使用されます。
- 目的:
- アイドル接続の維持: ネットワーク機器(NATルーター、ファイアウォールなど)は、一定期間データが流れないアイドル接続を自動的に切断することがあります。Keep-Aliveパケットを定期的に送信することで、接続がアクティブであると認識させ、意図しない切断を防ぎます。
- ピアの生存確認: 接続相手のホストがクラッシュしたり、ネットワークから切断されたりした場合、アプリケーションは通常、データ送信を試みるまでその状態を知ることができません。Keep-Aliveパケットへの応答がない場合、接続が切断されたと判断でき、アプリケーションは適切なエラー処理を行うことができます。
- 動作:
- TCP Keep-Aliveは、設定されたアイドル期間が経過すると、小さな(通常は1バイトの)Keep-Aliveプローブパケットを相手に送信します。
- 相手が応答すれば、接続はアクティブであると見なされ、タイマーがリセットされます。
- 応答がない場合、設定された回数だけ再試行されます。
- すべての再試行が失敗した場合、接続は切断されたと判断され、アプリケーションにエラーが通知されます。
- 設定パラメータ:
- Keep-Alive Time (KEEPALIVE_TIME): 接続がアイドル状態になってから最初のKeep-Aliveプローブを送信するまでの時間。
- Keep-Alive Interval (KEEPALIVE_INTERVAL): 最初のプローブが応答されなかった場合に、後続のプローブを送信する間隔。
- Keep-Alive Probes (KEEPALIVE_PROBES): 接続が切断されたと判断するまでに送信するプローブの最大回数。
Windows Sockets 2 (Winsock2) と WSAIoctl
Windows環境でのネットワークプログラミングは、Winsock2 APIを通じて行われます。WSAIoctl
(Windows Socket Asynchronous I/O Control)は、Winsock2 APIの一部であり、ソケットに対して様々な制御操作を行うための汎用関数です。
WSAIoctl
の役割: ソケットの動作を制御したり、拡張機能を取得したりするために使用されます。これは、Unix/Linuxにおけるioctl
システムコールに似ています。- 制御コード (Control Code):
WSAIoctl
は、実行する操作を指定するための制御コード(dwIoControlCode
パラメータ)を受け取ります。このコミットでは、SIO_KEEPALIVE_VALS
という制御コードが使用されます。 - 入力/出力バッファ: 制御コードに応じて、入力データや出力データを渡すためのバッファが指定されます。
SIO_KEEPALIVE_VALS
SIO_KEEPALIVE_VALS
は、WSAIoctl
で使用される特定の制御コードで、TCP Keep-Aliveのパラメータ(アイドル時間と再試行間隔)を設定するために使用されます。
- この制御コードを使用する場合、
TCPKeepalive
構造体(または同等のデータ構造)を入力バッファとして渡します。 TCPKeepalive
構造体には、Keep-Aliveの有効/無効、アイドル時間、再試行間隔がミリ秒単位で指定されます。
unsafe.Pointer
とunsafe.Sizeof
Go言語のunsafe
パッケージは、型安全性をバイパスして低レベルのメモリ操作を可能にするためのものです。
unsafe.Pointer
: 任意の型のポインタを保持できる汎用ポインタ型です。異なる型のポインタ間で変換を行う際に使用されます。C言語のvoid*
に似ています。unsafe.Sizeof
: 式の評価結果の型のサイズ(バイト単位)を返します。コンパイル時に評価されます。 これらの機能は、Goの型システムを迂回するため、注意して使用する必要があります。通常、システムコールやC言語との相互運用など、特定の低レベルな操作でのみ必要とされます。
技術的詳細
このコミットの核心は、WindowsのTCP Keep-Alive設定をGoのnet
パッケージから制御できるようにすることです。
-
TCPKeepalive
構造体の定義: Windows APIのTCP_KEEPALIVE
構造体に対応するGoの構造体syscall.TCPKeepalive
がsrc/pkg/syscall/ztypes_windows.go
に追加されます。この構造体は、Keep-Aliveの有効/無効(OnOff
)、最初のプローブまでのアイドル時間(Time
)、および後続のプローブ間隔(Interval
)をミリ秒単位で保持します。type TCPKeepalive struct { OnOff uint32 Time uint32 Interval uint32 }
-
SIO_KEEPALIVE_VALS
定数の追加:WSAIoctl
関数で使用される制御コードSIO_KEEPALIVE_VALS
がsrc/pkg/syscall/ztypes_windows.go
に追加されます。この定数は、TCP Keep-Aliveパラメータを設定するための特定の操作を識別します。IOC_VENDOR
は、ベンダー固有のIOCTLコードであることを示します。const ( // ... IOC_VENDOR = 0x18000000 // ... SIO_KEEPALIVE_VALS = IOC_IN | IOC_VENDOR | 4 )
-
setKeepAlivePeriod
の実装:src/pkg/net/tcpsockopt_windows.go
内のsetKeepAlivePeriod
関数が、実際のKeep-Alive設定ロジックを含むように変更されます。- Goの
time.Duration
型の期間d
を、Windowsが期待するミリ秒単位に変換します。この際、ミリ秒未満の端数は切り上げられます。 syscall.TCPKeepalive
構造体のインスタンスka
を作成し、OnOff
を1
(有効)、Time
とInterval
を計算されたミリ秒値で設定します。syscall.WSAIoctl
を呼び出します。fd.sysfd
: ソケットのファイルディスクリプタ(Windowsではソケットハンドル)。syscall.SIO_KEEPALIVE_VALS
: 実行する操作(Keep-Aliveパラメータの設定)。(*byte)(unsafe.Pointer(&ka))
:TCPKeepalive
構造体ka
へのポインタをバイトポインタにキャストして入力バッファとして渡します。unsafe.Pointer
がここで使用されます。size
:TCPKeepalive
構造体のサイズ。unsafe.Sizeof(ka)
で取得されます。nil, 0
: 出力バッファは不要なため。&ret
: 実際に転送されたバイト数を格納する変数。nil, 0
: オーバーラップI/O関連のパラメータ。
WSAIoctl
が返すエラーは、os.NewSyscallError
を使用してGoのエラー型に変換されます。
- Goの
この変更により、GoアプリケーションはWindows上でもnet.TCPConn.SetKeepAlivePeriod
を呼び出すことで、TCP Keep-Aliveの動作を細かく制御できるようになります。
コアとなるコードの変更箇所
src/pkg/net/tcpsockopt_windows.go
--- a/src/pkg/net/tcpsockopt_windows.go
+++ b/src/pkg/net/tcpsockopt_windows.go
@@ -7,7 +7,10 @@
package net
import (
+ "os"
+ "syscall"
"time"
+ "unsafe"
)
func setKeepAlivePeriod(fd *netFD, d time.Duration) error {
@@ -16,6 +19,16 @@ func setKeepAlivePeriod(fd *netFD, d time.Duration) error {
}
defer fd.decref()
- // We can't actually set this per connection. Act as a noop rather than an error.
- return nil
+ // Windows expects milliseconds so round to next highest millisecond.
+ d += (time.Millisecond - time.Nanosecond)
+ millis := uint32(d / time.Millisecond)
+ ka := syscall.TCPKeepalive{
+ OnOff: 1,
+ Time: millis,
+ Interval: millis,
+ }
+ ret := uint32(0)
+ size := uint32(unsafe.Sizeof(ka))
+ err := syscall.WSAIoctl(fd.sysfd, syscall.SIO_KEEPALIVE_VALS, (*byte)(unsafe.Pointer(&ka)), size, nil, 0, &ret, nil, 0)
+ return os.NewSyscallError("WSAIoctl", err)
}
src/pkg/syscall/ztypes_windows.go
--- a/src/pkg/syscall/ztypes_windows.go
+++ b/src/pkg/syscall/ztypes_windows.go
@@ -512,9 +512,11 @@ const (
IOC_OUT = 0x40000000
IOC_IN = 0x80000000
+ IOC_VENDOR = 0x18000000
IOC_INOUT = IOC_IN | IOC_OUT
IOC_WS2 = 0x08000000
SIO_GET_EXTENSION_FUNCTION_POINTER = IOC_INOUT | IOC_WS2 | 6
+ SIO_KEEPALIVE_VALS = IOC_IN | IOC_VENDOR | 4
// cf. http://support.microsoft.com/default.aspx?scid=kb;en-us;257460
@@ -1031,3 +1033,9 @@ type WSAProtocolChain struct {
ChainLen int32
ChainEntries [MAX_PROTOCOL_CHAIN]uint32
}\n
+type TCPKeepalive struct {
+ OnOff uint32
+ Time uint32
+ Interval uint32
+}
コアとなるコードの解説
src/pkg/net/tcpsockopt_windows.go
の変更点
- インポートの追加:
os
,syscall
,unsafe
パッケージが新しくインポートされています。これらは、システムコールへのアクセス、OS固有のエラー処理、および低レベルのメモリ操作のために必要です。 - 以前のno-op実装の削除: 以前の
setKeepAlivePeriod
関数は、単にnil
を返していましたが、これが削除され、実際のWindows API呼び出しに置き換えられました。 - 期間のミリ秒変換:
Goのd += (time.Millisecond - time.Nanosecond) millis := uint32(d / time.Millisecond)
time.Duration
はナノ秒単位ですが、Windows APIはミリ秒単位を期待します。time.Millisecond - time.Nanosecond
を加算することで、ミリ秒未満の端数がある場合に切り上げが行われ、常に次のミリ秒に丸められます。例えば、1ms + 1ns は 2ms になります。これは、Keep-Alive期間が短すぎると意図しない挙動になるのを防ぐための安全策と考えられます。 syscall.TCPKeepalive
構造体の初期化:ka := syscall.TCPKeepalive{ OnOff: 1, Time: millis, Interval: millis, }
TCPKeepalive
構造体が初期化されます。OnOff: 1
はKeep-Aliveを有効にすることを意味します。Time
とInterval
には、計算されたミリ秒値が設定されます。この実装では、最初のプローブまでのアイドル時間と、その後のプローブ間隔が同じ値に設定されています。syscall.WSAIoctl
の呼び出し:
これがWindows APIへの実際の呼び出しです。err := syscall.WSAIoctl(fd.sysfd, syscall.SIO_KEEPALIVE_VALS, (*byte)(unsafe.Pointer(&ka)), size, nil, 0, &ret, nil, 0)
fd.sysfd
: ネットワーク接続の基盤となるソケットのシステムファイルディスクリプタ(Windowsではソケットハンドル)。syscall.SIO_KEEPALIVE_VALS
: TCP Keep-Aliveパラメータを設定するための制御コード。(*byte)(unsafe.Pointer(&ka))
:ka
構造体のアドレスをunsafe.Pointer
を介して*byte
にキャストし、WSAIoctl
の入力バッファとして渡します。これにより、Goの構造体からC言語スタイルのポインタを渡すことができます。size
:ka
構造体のサイズ。unsafe.Sizeof(ka)
で取得されます。- 残りの引数は、出力バッファやオーバーラップI/Oに関するもので、この操作では使用されないため
nil
や0
が渡されます。
- エラーハンドリング:
return os.NewSyscallError("WSAIoctl", err)
WSAIoctl
が返すエラーは、os.NewSyscallError
によってGoの標準的なエラー型にラップされます。これにより、システムコールエラーがGoの慣用的な方法で扱えるようになります。
src/pkg/syscall/ztypes_windows.go
の変更点
IOC_VENDOR
定数の追加:IOC_VENDOR
は、IOCTLコードがベンダー固有の拡張であることを示すフラグです。SIO_KEEPALIVE_VALS
が標準的なIOCTLではなく、Microsoft固有の拡張であることを示唆しています。SIO_KEEPALIVE_VALS
定数の追加: この定数は、WSAIoctl
関数に渡される制御コードであり、TCP Keep-Aliveの設定操作を識別します。IOC_IN
は入力バッファがあることを、IOC_WS2
はWinsock2関連のコードであることを示します。TCPKeepalive
構造体の追加: Windows APIのTCP_KEEPALIVE
構造体に対応するGoの構造体です。この構造体は、WSAIoctl
に渡すKeep-Aliveパラメータを定義します。
これらの変更により、Goのnet
パッケージはWindows上でもTCP Keep-Alive機能を適切に利用できるようになり、クロスプラットフォームでのネットワークアプリケーション開発における一貫性が向上しました。
関連リンク
- Go言語の公式ドキュメント: https://golang.org/
- このコミットのGerritレビューページ: https://golang.org/cl/11393043
参考にした情報源リンク
- Microsoft Docs -
WSAIoctl
function: https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsaioclt - Microsoft Docs -
TCP_KEEPALIVE
structure: https://learn.microsoft.com/en-us/windows/win32/winsock/tcp-keepalive-structure - TCP Keepalive HOWTO: https://tldp.org/HOWTO/TCP-Keepalive-HOWTO/index.html
- Go
unsafe
パッケージのドキュメント: https://pkg.go.dev/unsafe