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

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

このコミットは、Go言語の実験的なWindowsファイルシステム通知パッケージ exp/winfsnotify におけるビルドエラーを修正するものです。具体的には、Windows API呼び出しからのエラーチェックのロジックを、Go言語のイディオムに沿った nil チェックに変更しています。

コミット

commit 1c50c32af07047e2df0cac7fa1e56fed33649e31
Author: Yasuhiro Matsumoto <mattn.jp@gmail.com>
Date:   Wed Dec 14 13:17:48 2011 +1100

    exp/winfsnotify: fix build.
    
    R=golang-dev, alex.brainman
    CC=golang-dev
    https://golang.org/cl/5483057
---
 src/pkg/exp/winfsnotify/winfsnotify.go | 22 +++++++++++-----------\n 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/src/pkg/exp/winfsnotify/winfsnotify.go b/src/pkg/exp/winfsnotify/winfsnotify.go
index d133740304..d47ffd1392 100644
--- a/src/pkg/exp/winfsnotify/winfsnotify.go
+++ b/src/pkg/exp/winfsnotify/winfsnotify.go
@@ -75,7 +75,7 @@ type Watcher struct {
 // NewWatcher creates and returns a Watcher.
 func NewWatcher() (*Watcher, error) {
 	port, e := syscall.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0)
-	if e != 0 {
+	if e != nil {
 		return nil, os.NewSyscallError("CreateIoCompletionPort", e)
 	}
 	w := &Watcher{
@@ -147,7 +147,7 @@ func (w *Watcher) RemoveWatch(path string) error {
 
 func (w *Watcher) wakeupReader() error {
 	e := syscall.PostQueuedCompletionStatus(w.port, 0, 0, nil)
-	if e != 0 {
+	if e != nil {
 		return os.NewSyscallError("PostQueuedCompletionStatus", e)
 	}
 	return nil
@@ -155,7 +155,7 @@ func (w *Watcher) wakeupReader() error {
 
 func getDir(pathname string) (dir string, err error) {
 	attr, e := syscall.GetFileAttributes(syscall.StringToUTF16Ptr(pathname))
-	if e != 0 {
+	if e != nil {
 		return "", os.NewSyscallError("GetFileAttributes", e)
 	}
 	if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
@@ -173,11 +173,11 @@ func getIno(path string) (ino *inode, err error) {
 		syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
 		nil, syscall.OPEN_EXISTING,
 		syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED, 0)
-	if e != 0 {
+	if e != nil {
 		return nil, os.NewSyscallError("CreateFile", e)
 	}
 	var fi syscall.ByHandleFileInformation
-	if e = syscall.GetFileInformationByHandle(h, &fi); e != 0 {
+	if e = syscall.GetFileInformationByHandle(h, &fi); e != nil {
 		syscall.CloseHandle(h)
 		return nil, os.NewSyscallError("GetFileInformationByHandle", e)
 	}
@@ -222,7 +222,7 @@ func (w *Watcher) addWatch(pathname string, flags uint64) error {
 	}
 	watchEntry := w.watches.get(ino)
 	if watchEntry == nil {
-		if _, e := syscall.CreateIoCompletionPort(ino.handle, w.port, 0, 0); e != 0 {
+		if _, e := syscall.CreateIoCompletionPort(ino.handle, w.port, 0, 0); e != nil {
 			syscall.CloseHandle(ino.handle)
 			return os.NewSyscallError("CreateIoCompletionPort", e)
 		}
@@ -295,7 +295,7 @@ func (w *Watcher) deleteWatch(watch *watch) {
 
 // Must run within the I/O thread.
 func (w *Watcher) startRead(watch *watch) error {
-	if e := syscall.CancelIo(watch.ino.handle); e != 0 {
+	if e := syscall.CancelIo(watch.ino.handle); e != nil {
 		w.Error <- os.NewSyscallError("CancelIo", e)
 		w.deleteWatch(watch)
 	}
@@ -304,7 +304,7 @@ func (w *Watcher) startRead(watch *watch) error {
 		mask |= toWindowsFlags(m)
 	}
 	if mask == 0 {
-		if e := syscall.CloseHandle(watch.ino.handle); e != 0 {
+		if e := syscall.CloseHandle(watch.ino.handle); e != nil {
 			w.Error <- os.NewSyscallError("CloseHandle", e)
 		}
 		delete(w.watches[watch.ino.volume], watch.ino.index)
@@ -312,7 +312,7 @@ func (w *Watcher) startRead(watch *watch) error {
 	}
 	e := syscall.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0],
 		uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0)
-	if e != 0 {
+	if e != nil {
 		err := os.NewSyscallError("ReadDirectoryChanges", e)
 		if e == syscall.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 {
 			// Watched directory was probably removed
@@ -354,7 +354,7 @@ func (w *Watcher) readEvents() {
 					}
 				}
 				var err error
-				if e := syscall.CloseHandle(w.port); e != 0 {
+				if e := syscall.CloseHandle(w.port); e != nil {
 					err = os.NewSyscallError("CloseHandle", e)
 				}
 				close(w.Event)
@@ -386,7 +386,7 @@ func (w *Watcher) readEvents() {
 		default:
 			w.Error <- os.NewSyscallError("GetQueuedCompletionPort", e)
 			continue
-		case 0:
+		case nil:
 		}
 
 		var offset uint32

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

https://github.com/golang/go/commit/1c50c32af07047e2df0cac7fa1e56fed33649e31

元コミット内容

このコミットは、exp/winfsnotify パッケージのビルドを修正することを目的としています。具体的には、syscall パッケージから返されるエラーのチェック方法を、Go言語の標準的なエラーハンドリングに合わせる変更が行われています。

変更の背景

Go言語では、エラーは error インターフェースを実装する型として表現されます。関数がエラーを返さない場合、そのエラー値は nil となります。しかし、このコミットが修正する前のコードでは、syscall パッケージから返されるエラー e を、C言語のような数値(0が成功、非0がエラー)として e != 0 と比較していました。

syscall パッケージは、Windows APIなどのOSの低レベルな関数をGoから呼び出すためのブリッジを提供します。これらのAPIは通常、成功/失敗を示す数値やハンドルを返し、エラー情報は別途 GetLastError() のような関数で取得されます。syscall パッケージはこれらの低レベルなエラーをGoの error インターフェースにラップして返します。

したがって、eerror 型であるにもかかわらず e != 0 と比較することは、型システム上の不整合や論理的な誤りにつながり、ビルドエラーや実行時エラーの原因となっていました。このコミットは、この誤ったエラーチェックをGoのイディオムである e != nil に修正することで、ビルドが通るようにし、かつ正しいエラーハンドリングを保証することを目的としています。

前提知識の解説

1. Go言語のエラーハンドリング

Go言語では、エラーは組み込みの error インターフェースによって表現されます。

type error interface {
    Error() string
}

関数がエラーを返す可能性がある場合、通常は最後の戻り値として error 型を返します。エラーが発生しなかった場合は nil を返し、エラーが発生した場合は nil ではない error 型の値を返します。エラーの有無をチェックするには、常に if err != nil { ... } という形式を使用します。

2. syscall パッケージ

syscall パッケージは、Goプログラムからオペレーティングシステム(OS)の低レベルなシステムコールを直接呼び出すための機能を提供します。Windowsの場合、これはWin32 APIの関数呼び出しに相当します。 syscall パッケージの関数は、OSのAPIが返す生の値(例: ハンドル、DWORD)と、Goの error 型を組み合わせて返すことがよくあります。例えば、syscall.CreateIoCompletionPort のような関数は、成功時には有効なハンドルと nil エラーを返し、失敗時には syscall.InvalidHandle と非nilのエラーを返します。

3. os.NewSyscallError

os.NewSyscallError は、syscall パッケージで発生したエラーを、よりGoの標準ライブラリに統合された os.SyscallError 型にラップするためのヘルパー関数です。これにより、システムコールエラーに関する追加情報(システムコール名と元のエラーコード)を提供できます。

4. Windows I/O Completion Ports (IOCP)

exp/winfsnotify パッケージは、Windowsのファイルシステム変更通知メカニズムである ReadDirectoryChangesW と、非同期I/Oを効率的に処理するための I/O Completion Ports (IOCP) を利用していると考えられます。

  • CreateIoCompletionPort: I/O完了ポートを作成または既存のポートにファイルハンドルを関連付けます。
  • PostQueuedCompletionStatus: I/O完了ポートにカスタムの完了パケットをポストします。
  • GetQueuedCompletionStatus: I/O完了ポートから完了パケットを取得します。
  • ReadDirectoryChanges: 指定されたディレクトリまたはサブツリー内の変更を監視します。
  • CancelIo: 指定されたファイルハンドルに対して発行された保留中のI/O操作をキャンセルします。
  • CloseHandle: 開いているオブジェクトハンドルを閉じます。

これらのAPIは、成功時には TRUE (非ゼロ) を返すか、有効なハンドルを返し、失敗時には FALSE (ゼロ) を返すか、INVALID_HANDLE_VALUE を返し、エラーコードは GetLastError() で取得するのが一般的です。syscall パッケージはこれらの挙動をGoのエラーインターフェースにマッピングしています。

技術的詳細

このコミットの技術的な核心は、Go言語におけるエラー値の正しい扱い方にあります。 Goの error インターフェースは、nil をエラーがない状態として定義します。これは、C言語や他の言語で 0NULL が成功を示す慣習とは異なります。

元のコードでは、syscall パッケージの関数呼び出しの結果として返される error 型の変数 e を、数値の 0 と比較していました (if e != 0)。これはGoの型システムでは不正な比較であり、コンパイルエラーまたは予期せぬ実行時動作を引き起こす可能性があります。Goのコンパイラは、error 型と整数型 0 の直接比較を許可しないため、このコードはビルドエラーの原因となっていました。

このコミットは、この誤った比較を、Goのイディオムである if e != nil に変更することで修正しています。これにより、e が実際にエラーを表す非nil値である場合にのみ、エラー処理ロジックが実行されるようになります。

また、readEvents 関数内の switch ステートメントで、case 0:case nil: に変更している箇所も同様の理由です。GetQueuedCompletionPort から返されるエラー enil であるかどうかをチェックするために、0 ではなく nil を使用するのが正しいGoの構文です。

この修正により、exp/winfsnotify パッケージは、Windows APIからのエラーをGoの標準的な方法で正しく検出し、処理できるようになり、堅牢性と可読性が向上します。

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

変更はすべて src/pkg/exp/winfsnotify/winfsnotify.go ファイル内で行われています。

主な変更パターンは以下の2種類です。

  1. if e != 0if e != nil に変更:

    • NewWatcher 関数内 (syscall.CreateIoCompletionPort の結果)
    • wakeupReader 関数内 (syscall.PostQueuedCompletionStatus の結果)
    • getDir 関数内 (syscall.GetFileAttributes の結果)
    • getIno 関数内 (syscall.CreateFile および syscall.GetFileInformationByHandle の結果)
    • addWatch 関数内 (syscall.CreateIoCompletionPort の結果)
    • startRead 関数内 (syscall.CancelIo, syscall.CloseHandle, syscall.ReadDirectoryChanges の結果)
    • readEvents 関数内 (syscall.CloseHandle の結果)
  2. case 0:case nil: に変更:

    • readEvents 関数内の switch e := <-w.Error: ステートメント内

コアとなるコードの解説

変更された各行は、Windows API呼び出しの戻り値としてGoの error 型を受け取り、そのエラーの有無をチェックする部分です。

例えば、NewWatcher 関数では、syscall.CreateIoCompletionPort がI/O完了ポートのハンドルとエラーを返します。

// 変更前
port, e := syscall.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0)
if e != 0 { // ここで e は error 型なのに 0 と比較している
    return nil, os.NewSyscallError("CreateIoCompletionPort", e)
}

// 変更後
port, e := syscall.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0)
if e != nil { // 正しく error 型と nil を比較
    return nil, os.NewSyscallError("CreateIoCompletionPort", e)
}

同様に、readEvents 関数内の switch ステートメントでは、w.Error チャンネルから受け取ったエラー e の値に基づいて処理を分岐しています。

// 変更前
switch e := <-w.Error:
// ...
case 0: // ここで e は error 型なのに 0 と比較している
// ...

// 変更後
switch e := <-w.Error:
// ...
case nil: // 正しく error 型と nil を比較
// ...

これらの変更は、Go言語の基本的なエラーハンドリングの原則に準拠させるためのものであり、コードの正確性と信頼性を向上させます。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • syscall パッケージのソースコードとドキュメント
  • Windows APIの公式ドキュメント
  • Go言語のエラーハンドリングに関する一般的な慣習とベストプラクティス