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

[インデックス 14300] ファイルの概要

このコミットは、Go言語の標準ライブラリであるnetパッケージにおけるWindows環境でのエラーメッセージの改善を目的としています。特に、ネットワーク操作が中断された際に、その原因がタイムアウトによるものなのか、それともネットワーク接続が閉じられたことによるものなのかをより正確に区別し、適切なエラーを返すように修正されています。これにより、Windows上でのネットワークアプリケーションのデバッグやエラーハンドリングが容易になります。

コミット

commit 84e20465fc33f1702d6dfc7ed1c05b457acb1b5b
Author: Alex Brainman <alex.brainman@gmail.com>
Date:   Fri Nov 2 11:07:22 2012 +1100

    net: use better error messages on windows
    
    Fixes #4320.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/6810064

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/84e20465fc33f1702d6dfc7ed1c05b457acb1b5b

元コミット内容

このコミットは、netパッケージのWindows固有のファイルディスクリプタ(fd_windows.go)において、非同期I/O操作がsyscall.ERROR_OPERATION_ABORTEDエラーで完了した場合の挙動を修正しています。以前は、このエラーが発生すると一律にsyscall.EWOULDBLOCK(非ブロッキング操作が完了しなかったことを示すエラー)に変換されていました。しかし、このエラーはタイムアウトや接続終了など、複数の原因で発生する可能性があり、一律の変換では正確なエラー情報が失われていました。

このコミットでは、syscall.ERROR_OPERATION_ABORTEDが発生した際に、その原因がタイムアウトによるものか、それとも接続が閉じられたことによるものかを判断し、それぞれerrTimeoutまたはerrClosingというより具体的なエラーを返すように変更されています。また、errClosingの定義がfd_unix.gofd_windows.goからnet.goに移動され、netパッケージ全体で共通のエラーとして利用できるようになりました。

変更の背景

この変更は、Go issue #4320("net: better error messages on windows")に対応するものです。このissueでは、Windows環境でネットワーク接続がタイムアウトした場合や、接続が閉じられた場合に、Goのnetパッケージが返すエラーメッセージが不明瞭であるという問題が報告されていました。具体的には、これらの状況でsyscall.ERROR_OPERATION_ABORTEDという汎用的なエラーが返され、アプリケーション側でその具体的な原因を特定することが困難でした。

開発者は、より詳細なエラー情報を提供することで、アプリケーションが適切なエラーハンドリングを行い、ユーザーに対してより分かりやすいフィードバックを提供できるようにすることを目的としていました。例えば、タイムアウトと接続切断では、アプリケーションが取るべき対応が異なるため、これらを区別できることは重要です。

前提知識の解説

Go言語のnetパッケージ

Go言語のnetパッケージは、ネットワークI/Oのプリミティブを提供します。TCP/IP、UDP、Unixドメインソケットなどのネットワークプロトコルを扱うためのインターフェースが含まれており、クライアントやサーバーアプリケーションを構築するために広く利用されます。このパッケージは、OSのネットワークAPIを抽象化し、クロスプラットフォームで一貫したインターフェースを提供します。

ファイルディスクリプタ(File Descriptor, FD)

ファイルディスクリプタは、Unix系OSにおいてファイルやソケットなどのI/Oリソースを識別するために使用される抽象的なハンドルです。Windowsでは、これに相当する概念として「ハンドル(Handle)」があります。Goのnetパッケージは、内部的にこれらのOS固有のハンドルを抽象化し、netFDという構造体で管理しています。fd_unix.gofd_windows.goは、それぞれのOSにおけるnetFDの実装や、OS固有のI/O操作をラップする役割を担っています。

非同期I/Oと完了ポート(Completion Port)

Windowsでは、高性能な非同期I/Oを実現するためにI/O完了ポート(IOCP: I/O Completion Port)というメカニズムがよく利用されます。これは、複数の非同期I/O操作の完了を効率的に待機するための仕組みです。ioSrvExecIOといった関数は、このIOCPのような非同期I/Oの仕組みをGoのnetパッケージ内で抽象化し、利用していると考えられます。

syscall.ERROR_OPERATION_ABORTED

これはWindows APIで定義されているエラーコードの一つで、I/O操作が中断されたことを示します。この中断は、様々な原因で発生する可能性があります。例えば、I/O操作がタイムアウトした、関連するハンドルが閉じられた、またはシステムがシャットダウンされた、などです。このエラーコードだけでは、具体的な中断の原因を特定することはできません。

syscall.EWOULDBLOCK

Unix系OSのerrno(エラー番号)の一つで、非ブロッキングI/O操作が即座に完了しなかったことを示します。Windowsでは、これに相当するエラーコードが存在し、通常はWSAEWOULDBLOCKとして知られています。これは、操作がブロックされることなく実行されたが、データがまだ利用可能でない、または書き込みバッファが満杯である、といった状況で返されます。

errTimeouterrClosing

これらはGoのnetパッケージ内で定義されているカスタムエラーです。

  • errTimeout: ネットワーク操作が指定された時間内に完了しなかった場合に返されるエラー。
  • errClosing: ネットワーク接続が閉じられたために操作が失敗した場合に返されるエラー。

これらのカスタムエラーを導入することで、アプリケーションはより具体的なエラーハンドリングを行うことができます。

技術的詳細

このコミットの核心は、Windows環境における非同期I/Oの完了処理の改善にあります。

src/pkg/net/fd_windows.goioSrv.ExecIO関数は、非同期I/O操作を実行し、その完了を待機する役割を担っています。この関数内で、selectステートメントを使用して、I/O操作の完了、タイマーの期限切れ、またはファイルディスクリプタのクローズイベントを監視しています。

変更前は、syscall.ERROR_OPERATION_ABORTEDエラーが発生した場合、無条件にsyscall.EWOULDBLOCKに変換されていました。これは、I/O操作が中断された原因が何であれ、一律に「操作がブロックされなかったが、完了しなかった」という曖昧なエラーを返していたことを意味します。

変更後、timeoutという新しいブール変数が導入されました。selectステートメント内でタイマーが期限切れになった場合、cancelledフラグに加えてtimeoutフラグもtrueに設定されます。

そして、syscall.ERROR_OPERATION_ABORTEDエラーが発生した場合の処理が以下のように変更されました。

if r.err == syscall.ERROR_OPERATION_ABORTED { // IO Canceled
    if timeout {
        r.err = errTimeout
    } else {
        r.err = errClosing
    }
}

このロジックにより、syscall.ERROR_OPERATION_ABORTEDが発生した際に、timeoutフラグがtrueであればerrTimeoutを返し、そうでなければerrClosingを返すようになりました。これにより、I/O操作が中断された具体的な原因(タイムアウトか、接続クローズか)を正確に区別できるようになります。

また、errClosingの定義がsrc/pkg/net/fd_unix.gosrc/pkg/net/fd_windows.goから削除され、src/pkg/net/net.goに移動されました。これは、errClosingが特定のOSの実装に依存するエラーではなく、netパッケージ全体で共通して使用されるべきエラーであることを示しています。net.gonetパッケージの主要な定義を含むファイルであり、ここに移動することで、netパッケージの他の部分からもerrClosingにアクセスしやすくなります。

コアとなるコードの変更箇所

src/pkg/net/fd_unix.go

--- a/src/pkg/net/fd_unix.go
+++ b/src/pkg/net/fd_unix.go
@@ -7,7 +7,6 @@
 package net
 
 import (
-"errors"
 	"io"
 	"os"
 	"runtime"
@@ -346,8 +345,6 @@ func (fd *netFD) connect(ra syscall.Sockaddr) error {
 	return err
 }
 
-var errClosing = errors.New("use of closed network connection")
-
 // Add a reference to this fd.
 // If closing==true, pollserver must be locked; mark the fd as closing.
 // Returns an error if the fd cannot be used.
  • errorsパッケージのインポートとerrClosing変数の定義が削除されました。

src/pkg/net/fd_windows.go

--- a/src/pkg/net/fd_windows.go
+++ b/src/pkg/net/fd_windows.go
@@ -196,11 +196,12 @@ func (s *ioSrv) ExecIO(oi anOpIface, deadline int64) (int, error) {
 	}
 	// Wait for our request to complete.
 	var r ioResult
-	var cancelled bool
+	var cancelled, timeout bool
 	select {
 	case r = <-o.resultc:
 	case <-timer:
 	\tcancelled = true
+\t\ttimeout = true
 	case <-o.fd.closec:
 	\tcancelled = true
 	}
@@ -220,7 +221,11 @@ func (s *ioSrv) ExecIO(oi anOpIface, deadline int64) (int, error) {
 \t\t// Wait for IO to be canceled or complete successfully.
 \t\tr = <-o.resultc
 \t\tif r.err == syscall.ERROR_OPERATION_ABORTED { // IO Canceled
-\t\t\tr.err = syscall.EWOULDBLOCK
+\t\t\tif timeout {
+\t\t\t\tr.err = errTimeout
+\t\t\t} else {
+\t\t\t\tr.err = errClosing
+\t\t\t}
 \t\t}
 	}\n \tif r.err != nil {
@@ -312,8 +317,6 @@ func (fd *netFD) connect(ra syscall.Sockaddr) error {
 	return syscall.Connect(fd.sysfd, ra)\n }\n \n-var errClosing = errors.New("use of closed network connection")\n-\n // Add a reference to this fd.\n // If closing==true, mark the fd as closing.\n // Returns an error if the fd cannot be used.\n```
- `timeout`変数が追加され、タイマー期限切れ時に`timeout = true`が設定されるようになりました。
- `syscall.ERROR_OPERATION_ABORTED`エラーのハンドリングロジックが変更され、`timeout`フラグに基づいて`errTimeout`または`errClosing`を返すようになりました。
- `errClosing`変数の定義が削除されました。

### `src/pkg/net/net.go`

```diff
--- a/src/pkg/net/net.go
+++ b/src/pkg/net/net.go
@@ -221,6 +221,8 @@ func (e *timeoutError) Temporary() bool { return true }\n \n var errTimeout error = &timeoutError{}\n \n+var errClosing = errors.New("use of closed network connection")\n+\n type AddrError struct {\n \tErr  string\n \tAddr string\n```
- `errClosing`変数が新しく定義されました。

## コアとなるコードの解説

このコミットの主要な変更は、`src/pkg/net/fd_windows.go`内の`ioSrv.ExecIO`関数に集中しています。

1.  **`timeout`変数の導入**:
    `var cancelled, timeout bool`
    これにより、I/O操作がキャンセルされた理由が、単にキャンセルされたのか(`cancelled`)、それとも特にタイムアウトによってキャンセルされたのか(`timeout`)を区別するためのフラグが追加されました。

2.  **タイマー期限切れ時の`timeout`フラグ設定**:
    `case <-timer: cancelled = true; timeout = true`
    `select`文の中で、I/O操作の完了を待つタイマーが期限切れになった場合、`cancelled`フラグだけでなく、新しく導入された`timeout`フラグも`true`に設定されます。これは、操作がタイムアウトによって中断されたことを明確に示します。

3.  **`syscall.ERROR_OPERATION_ABORTED`のより詳細なハンドリング**:
    ```go
    if r.err == syscall.ERROR_OPERATION_ABORTED { // IO Canceled
        if timeout {
            r.err = errTimeout
        } else {
            r.err = errClosing
        }
    }
    ```
    これが最も重要な変更点です。Windowsの非同期I/O操作が`syscall.ERROR_OPERATION_ABORTED`というエラーで完了した場合、このコードブロックが実行されます。
    *   もし`timeout`フラグが`true`であれば、操作はタイムアウトによって中断されたと判断し、`r.err`を`net`パッケージで定義されている`errTimeout`に設定します。
    *   `timeout`フラグが`false`であれば、操作はタイムアウト以外の理由(例えば、接続が閉じられたなど)で中断されたと判断し、`r.err`を`net`パッケージで定義されている`errClosing`に設定します。
    この変更により、`syscall.ERROR_OPERATION_ABORTED`という汎用的なエラーが、より具体的で意味のある`errTimeout`または`errClosing`に変換されるようになり、アプリケーション側でのエラーハンドリングが格段に向上します。

4.  **`errClosing`の定義の移動**:
    `src/pkg/net/fd_unix.go`と`src/pkg/net/fd_windows.go`から`var errClosing = errors.New("use of closed network connection")`の定義が削除され、`src/pkg/net/net.go`に移動されました。
    これは、`errClosing`が特定のOSの実装に閉じ込められたエラーではなく、`net`パッケージ全体で共通して利用されるべきエラーであることを示しています。`net.go`は`net`パッケージの公開APIや共通の定義を置く場所であるため、ここに移動することで、パッケージ全体での一貫性と再利用性が高まります。

これらの変更により、Windows環境でのネットワークエラーの診断が容易になり、より堅牢なネットワークアプリケーションの開発に貢献します。

## 関連リンク

*   Go issue #4320: [https://github.com/golang/go/issues/4320](https://github.com/golang/go/issues/4320)
*   Go Change-Id: `6810064` (Gerrit Code Review): [https://golang.org/cl/6810064](https://golang.org/cl/6810064)

## 参考にした情報源リンク

*   Go issue #4320の議論内容
*   Go言語の`net`パッケージのドキュメント(当時のバージョン)
*   Windows APIドキュメント(`ERROR_OPERATION_ABORTED`、I/O完了ポートなど)
*   Go言語のソースコード(`src/pkg/net/`ディレクトリ内の関連ファイル)
*   Go言語の`syscall`パッケージのドキュメント(当時のバージョン)
*   Go言語のエラーハンドリングに関する一般的なプラクティス