[インデックス 14250] ファイルの概要
このコミットは、Go言語の標準ライブラリnet
パッケージにおいて、Windows環境でのAccept
操作における読み込みデッドライン(read deadline)の適用に関する修正と、それに関連するテストの追加を行っています。具体的には、net.Listener
のSetDeadline
メソッドがWindowsのAccept
呼び出しで正しく機能しない問題を解決し、ネットワーク接続のタイムアウト処理を改善します。
コミット
commit d12a7d39d1cfa7a0cd824673d7aacc4615b461cb
Author: Alexey Borzenkov <snaury@gmail.com>
Date: Wed Oct 31 09:58:05 2012 +1100
net: use read deadline in Accept on windows
Fixes #4296.
R=golang-dev, alex.brainman
CC=golang-dev
https://golang.org/cl/6815044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d12a7d39d1cfa7a0cd824673d7aacc4615b461cb
元コミット内容
net: use read deadline in Accept on windows
Fixes #4296.
このコミットは、Windows上でのnet.Accept
呼び出しにおいて、読み込みデッドラインを使用するように変更します。これにより、Issue #4296で報告された問題が修正されます。
変更の背景
この変更の背景には、Go言語のnet
パッケージにおけるnet.Listener
のAccept
メソッドが、Windows環境でSetDeadline
によって設定されたタイムアウトを尊重しないという問題がありました。具体的には、net.Listener
に対してSetDeadline
を設定しても、Accept
呼び出しが指定されたデッドラインを超えてブロックし続ける可能性がありました。これは、サーバーアプリケーションがクライアントからの接続を待機する際に、特定の時間内に接続が確立されない場合に処理を中断したり、リソースを解放したりするようなシナリオで問題となります。
Issue #4296("net: Accept on Windows ignores SetDeadline")では、この挙動が報告されており、WindowsのネットワークI/OモデルとGoのデッドライン処理の間の不整合が示唆されていました。Goのnet
パッケージは、クロスプラットフォームで一貫したネットワークI/Oを提供することを目指していますが、OS固有のI/Oメカニズム(特にWindowsのCompletion Portsなど)との統合において、デッドラインの適用が課題となることがありました。このコミットは、この特定のWindows環境でのAccept
のデッドライン問題を解決することを目的としています。
前提知識の解説
net.Listener
とAccept
: Go言語のnet
パッケージにおけるnet.Listener
インターフェースは、着信ネットワーク接続をリッスンするための一般的なインターフェースです。その主要なメソッドの一つであるAccept()
は、次の着信接続をブロックして待機し、接続が確立されると新しいnet.Conn
インターフェースを返します。- デッドライン(Deadlines): Goの
net
パッケージでは、SetDeadline
、SetReadDeadline
、SetWriteDeadline
といったメソッドを通じて、ネットワーク操作にタイムアウトを設定できます。これにより、特定の時間内に操作が完了しない場合にエラーを返すように設定でき、アプリケーションが無限にブロックされるのを防ぎます。 - WindowsのネットワークI/O: Windowsでは、ネットワークI/Oは通常、Winsock APIを通じて行われます。非同期I/Oには、I/O Completion Ports (IOCP) などのメカニズムが使用され、効率的な並行処理を可能にします。Goの
net
パッケージは、これらのOS固有のメカニズムを抽象化し、Goのgoroutineモデルと統合しています。 syscall.Sockaddr
:syscall
パッケージは、OSのシステムコールへの低レベルなインターフェースを提供します。syscall.Sockaddr
は、ソケットアドレスを表すインターフェースで、OS固有のソケットアドレス構造体(例:syscall.SockaddrInet4
、syscall.SockaddrInet6
)を抽象化します。iosrv.ExecIO
: Goの内部的なネットワークI/O処理において、iosrv
(I/Oサービス)は、OS固有の非同期I/O操作を管理するためのコンポーネントです。ExecIO
関数は、指定されたI/O操作(o
)を実行し、必要に応じてデッドライン(fd.rdeadline
など)を考慮に入れます。netFD
構造体:netFD
は、Goのnet
パッケージ内部で使用されるファイルディスクリプタ(またはソケットハンドル)を抽象化した構造体です。これには、読み込みデッドライン(rdeadline
)や書き込みデッドライン(wdeadline
)などの情報が含まれます。
技術的詳細
このコミットの技術的な核心は、Windows環境におけるnet.Listener
のAccept
メソッドが、設定された読み込みデッドラインを適切に尊重するように修正することです。
以前の実装では、src/pkg/net/fd_windows.go
内のnetFD.accept
メソッドにおいて、iosrv.ExecIO
がデッドラインなしで呼び出されていました(_, err = iosrv.ExecIO(&o, 0)
)。これは、Accept
操作がデッドラインに達してもタイムアウトせず、無限にブロックし続ける可能性を意味していました。
このコミットでは、iosrv.ExecIO
の呼び出しを_, err = iosrv.ExecIO(&o, fd.rdeadline)
に変更しています。ここで、fd.rdeadline
はnetFD
構造体に格納されている読み込みデッドラインの値です。この変更により、Accept
操作がnetFD
に設定された読み込みデッドラインを考慮するようになり、デッドラインを超過した場合には適切なエラー(通常はタイムアウトエラー)を返すようになります。
また、src/pkg/net/timeout_test.go
にTestTimeoutAccept
という新しいテストケースが追加されています。このテストは、net.Listener
に対して短いデッドラインを設定し、Accept
がそのデッドライン内でタイムアウトすることを確認します。これにより、修正が正しく機能していることが検証されます。特に、time.After
とselect
ステートメントを使用して、Accept
が指定された時間内にエラーを返すことを非同期的にチェックしています。
この修正は、WindowsのWinsock APIとGoの内部I/Oサービス層の間の連携を改善し、クロスプラットフォームでのnet
パッケージのデッドラインセマンティクスの一貫性を高めるものです。
コアとなるコードの変更箇所
このコミットによる主要なコード変更は以下の2つのファイルにあります。
-
src/pkg/net/fd_windows.go
:--- a/src/pkg/net/fd_windows.go +++ b/src/pkg/net/fd_windows.go @@ -544,7 +544,7 @@ func (fd *netFD) accept(toAddr func(syscall.Sockaddr) Addr) (*netFD, error) { var o acceptOp o.Init(fd, 'r') o.newsock = s - _, err = iosrv.ExecIO(&o, 0) + _, err = iosrv.ExecIO(&o, fd.rdeadline) if err != nil { closesocket(s) return nil, err
この変更は、
netFD
のaccept
メソッド内でiosrv.ExecIO
を呼び出す際に、第2引数に0
(デッドラインなし)を渡していたのを、fd.rdeadline
(netFD
に設定された読み込みデッドライン)を渡すように修正しています。 -
src/pkg/net/timeout_test.go
:--- a/src/pkg/net/timeout_test.go +++ b/src/pkg/net/timeout_test.go @@ -119,3 +119,30 @@ func TestDeadlineReset(t *testing.T) { t.Errorf("unexpected return from Accept; err=%v", err) } } + +func TestTimeoutAccept(t *testing.T) { + switch runtime.GOOS { + case "plan9": + t.Logf("skipping test on %q", runtime.GOOS) + return + } + ln, err := Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + defer ln.Close() + tl := ln.(*TCPListener) + tl.SetDeadline(time.Now().Add(100 * time.Millisecond)) + errc := make(chan error, 1) + go func() { + _, err := ln.Accept() + errc <- err + }() + select { + case <-time.After(1 * time.Second): + // Accept shouldn't block indefinitely + t.Errorf("Accept didn't return in an expected time") + case <-errc: + // Pass. + } +}
この変更は、
TestTimeoutAccept
という新しいテスト関数を追加しています。このテストは、TCPリスナーを作成し、短いデッドラインを設定した後、Accept
がタイムアウトすることを確認します。
コアとなるコードの解説
src/pkg/net/fd_windows.go
の変更
netFD.accept
メソッドは、Windows上で新しい接続を受け入れる際の低レベルな処理を担当します。このメソッド内で、iosrv.ExecIO
は非同期I/O操作を実行するためのGoランタイムの内部関数です。
変更前: _, err = iosrv.ExecIO(&o, 0)
変更後: _, err = iosrv.ExecIO(&o, fd.rdeadline)
この変更は非常に重要です。iosrv.ExecIO
の第2引数は、I/O操作のデッドラインを指定します。
0
を渡すことは、デッドラインが設定されていない、つまり操作が無限にブロックする可能性があることを意味します。fd.rdeadline
を渡すことは、netFD
(この場合はnet.Listener
の内部表現)に設定された読み込みデッドラインをAccept
操作に適用することを意味します。
これにより、net.Listener
に対してSetDeadline
が呼び出された場合、そのデッドラインがAccept
操作に伝播され、指定された時間内に接続が確立されない場合にAccept
がタイムアウトエラーを返すようになります。これは、Windows環境でのnet.Listener
のAccept
が、他のOSと同様にデッドラインを尊重するようになるための修正です。
src/pkg/net/timeout_test.go
の追加テスト
TestTimeoutAccept
は、この修正が正しく機能していることを検証するための統合テストです。
- OSチェック:
runtime.GOOS
が"plan9"
の場合はテストをスキップします。これは、この修正がWindows固有のものであるため、他のOSでの挙動は直接関係ないためです。 - リスナーの作成:
Listen("tcp", "127.0.0.1:0")
でTCPリスナーを作成します。"127.0.0.1:0"
は、利用可能なポートを自動的に割り当ててローカルホストでリッスンすることを意味します。 - デッドラインの設定:
tl.SetDeadline(time.Now().Add(100 * time.Millisecond))
で、リスナーに100ミリ秒の短いデッドラインを設定します。これは、Accept
がこの時間内に接続を受け入れない場合にタイムアウトすることを期待するものです。 Accept
の非同期呼び出し:go func() { _, err := ln.Accept(); errc <- err }()
というgoroutine内でln.Accept()
を呼び出します。これにより、Accept
がブロックしてもテストのメインスレッドがブロックされずに済みます。Accept
の結果(エラー)はerrc
チャネルに送信されます。- タイムアウトの検証:
select
ステートメントを使用して、Accept
がタイムアウトしたかどうかをチェックします。case <-time.After(1 * time.Second)
: もし1秒経ってもerrc
から何も受信されない場合、Accept
が期待以上に長くブロックしていると判断し、テストを失敗させます。case <-errc
:Accept
がエラーを返した場合(期待されるのはタイムアウトエラー)、テストは成功とみなされます(// Pass.
)。
このテストは、Accept
がデッドラインを尊重し、適切な時間内にタイムアウトエラーを返すことを保証します。
関連リンク
- Go Issue #4296: https://github.com/golang/go/issues/4296
- Go CL 6815044: https://golang.org/cl/6815044
参考にした情報源リンク
- Go Issue #4296のコメントと議論
- Go言語の
net
パッケージのドキュメント - Go言語の
syscall
パッケージのドキュメント - Go言語の
time
パッケージのドキュメント - Windows Winsock APIに関する一般的な情報