[インデックス 15145] ファイルの概要
このコミットは、Go言語の実験的なexp/inotify
パッケージにおける、ファイルディスクリプタを閉じる際のパフォーマンス問題を解決するためのものです。特に、NFS(Network File System)環境下でのinotify
ウォッチャーの終了処理が遅延する問題に対処しています。
コミット
- コミットハッシュ:
da35d425214763d7d51d74e0410dc3a431c4a13d
- Author: Ian Lance Taylor iant@golang.org
- Date: Tue Feb 5 06:11:10 2013 -0800
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/da35d425214763d7d51d74e0410dc3a431c4a13d
元コミット内容
exp/inotify: close event channel before file descriptor
Closing the inotify file descriptor can take over a second
when running on Ubuntu Precise in an NFS directory, leading to
the test error in issue 3132. Closing the event channel first
lets a client that does not care about the error channel move
on.
Fixes #3132.
R=golang-dev, dave, rsc
CC=golang-dev
https://golang.org/cl/7300045
変更の背景
このコミットは、Go言語のexp/inotify
パッケージ(Linuxのinotify機能へのGoラッパー)において、ウォッチャーを終了する際のパフォーマンス問題を解決するために行われました。具体的には、inotify
ファイルディスクリプタ(w.fd
)を閉じるsyscall.Close
呼び出しが、特定の環境(Ubuntu Precise上のNFSディレクトリ)で1秒以上かかる場合があるという問題が報告されていました(Issue 3132)。
この遅延は、inotify
ウォッチャーのテストがタイムアウトしたり、アプリケーションが予期せずハングアップしたりする原因となっていました。特に、inotify
イベントを待機しているreadEvents
ゴルーチンが、syscall.Close
が完了するまでブロックされ、その間、クライアントがイベントチャネルからの読み取りを待機し続けるという状況が発生していました。
この問題に対処するため、ファイルディスクリプタを閉じる前に、イベントチャネルを先に閉じることで、クライアントがイベントの受信を待つことなく、ウォッチャーの終了処理を進められるようにする変更が導入されました。これにより、syscall.Close
の遅延が全体の終了処理に与える影響を軽減し、テストの失敗やアプリケーションの応答性低下を防ぐことが目的です。
前提知識の解説
- inotify: Linuxカーネルが提供するファイルシステムイベント監視メカニズムです。ファイルやディレクトリの作成、削除、変更などのイベントをリアルタイムでアプリケーションに通知します。Go言語の
exp/inotify
パッケージは、このinotify
システムコールをGoから利用するための実験的なラッパーを提供します。 - ファイルディスクリプタ (File Descriptor, FD): Unix系OSにおいて、ファイルやソケット、パイプなどのI/Oリソースを識別するためにカーネルが割り当てる非負の整数です。
inotify
もまた、inotify_init
システムコールによってファイルディスクリプタを返します。このFDを通じてイベントの読み取りや監視対象の追加/削除を行います。 syscall.Read
: Go言語のsyscall
パッケージに含まれる関数で、指定されたファイルディスクリプタからデータを読み取ります。inotify
の場合、この関数を使ってinotify
イベントを読み取ります。syscall.Close
: Go言語のsyscall
パッケージに含まれる関数で、指定されたファイルディスクリプタを閉じます。これにより、そのFDに関連付けられたリソースが解放されます。- Goのチャネル (Channel): Go言語におけるゴルーチン間の通信手段です。チャネルを通じてデータを送受信することで、並行処理における安全なデータ共有を実現します。
inotify
パッケージでは、イベント通知やエラー通知のためにチャネルが使用されます。 close(channel)
: Goのチャネルを閉じます。閉じられたチャネルから読み取ろうとすると、チャネルにデータが残っていればそれを読み取り、データがなければゼロ値が即座に返されます。これにより、受信側はチャネルが閉じられたことを検知し、それ以上のデータが送られてこないことを知ることができます。- NFS (Network File System): ネットワーク経由でファイルシステムを共有するためのプロトコルです。NFS上のファイル操作は、ローカルファイルシステムに比べてネットワークの遅延やサーバーの負荷によってパフォーマンスが低下する可能性があります。
- Issue 3132: Goのバグトラッカーで報告された特定の不具合です。このコミットの変更がこの問題の解決を目的としていることを示します。
技術的詳細
このコミットの核心は、inotify
ウォッチャーの終了処理におけるリソース解放の順序を変更することにあります。
従来のコードでは、readEvents
ゴルーチンが終了する際に、まずsyscall.Close(w.fd)
を呼び出してinotify
ファイルディスクリプタを閉じ、その後にclose(w.Event)
とclose(w.Error)
を呼び出してイベントチャネルとエラーチャネルを閉じていました。
問題は、syscall.Close(w.fd)
がNFS環境下で非常に遅くなる場合があることでした。syscall.Close
がブロックされている間、readEvents
ゴルーチンは停止し、w.Event
チャネルは閉じられないままでした。これにより、w.Event
チャネルからイベントを読み取ろうとしているクライアントゴルーチンは、syscall.Close
が完了するまで無限にブロックされる可能性がありました。これは、クライアントがウォッチャーの終了を待つ際に、不必要な遅延を引き起こす原因となります。
このコミットでは、この順序を反転させました。syscall.Close(w.fd)
を呼び出す前に、まずclose(w.Event)
を呼び出すように変更されています。
この変更の技術的な利点は以下の通りです。
- クライアントのブロック解除:
w.Event
チャネルが先に閉じられることで、w.Event
からの読み取りを待機しているクライアントゴルーチンは、syscall.Close
の完了を待つことなく、すぐにチャネルが閉じられたことを検知し、処理を続行できます。これにより、クライアント側での不必要な遅延やハングアップが回避されます。 - リソース解放の分離:
inotify
ファイルディスクリプタのクローズ(カーネルレベルのリソース解放)と、Goのチャネルのクローズ(Goランタイムレベルのリソース解放とゴルーチン間の通知)を論理的に分離します。これにより、遅延が発生しやすいsyscall.Close
の影響を、チャネルを介した通信に与えないようにできます。 - 堅牢性の向上: NFSのようなネットワークファイルシステムは、その性質上、I/O操作に予測不能な遅延が発生する可能性があります。この変更は、そのような外部要因による遅延が、アプリケーションの内部的な終了ロジックに与える影響を最小限に抑えることで、より堅牢な
inotify
ウォッチャーの実装に貢献します。
syscall.Read(w.fd, buf[0:])
からsyscall.Read(w.fd, buf[:])
への変更は、スライス操作の簡略化であり、機能的な違いはありません。buf[0:]
はbuf
全体のスライスを意味し、buf[:]
も同様にbuf
全体のスライスを意味します。これはコードの可読性をわずかに向上させるための変更と考えられます。
コアとなるコードの変更箇所
変更はsrc/pkg/exp/inotify/inotify_linux.go
ファイルにあります。
--- a/src/pkg/exp/inotify/inotify_linux.go
+++ b/src/pkg/exp/inotify/inotify_linux.go
@@ -153,7 +153,7 @@ func (w *Watcher) readEvents() {
var buf [syscall.SizeofInotifyEvent * 4096]byte
for {
- n, err := syscall.Read(w.fd, buf[0:])
+ n, err := syscall.Read(w.fd, buf[:])
// See if there is a message on the "done" channel
var done bool
select {
@@ -163,11 +163,13 @@ func (w *Watcher) readEvents() {
// If EOF or a "done" message is received
if n == 0 || done {
+\t\t\t// The syscall.Close can be slow. Close
+\t\t\t// w.Event first.
+\t\t\tclose(w.Event)
\t\t\terr := syscall.Close(w.fd)
\t\t\tif err != nil {\
\t\t\t\tw.Error <- os.NewSyscallError("close", err)
\t\t\t}\
-\t\t\tclose(w.Event)
\t\t\tclose(w.Error)
\t\t\treturn
\t\t}
コアとなるコードの解説
変更の主要な部分は、readEvents
ゴルーチンが終了する際のチャネルとファイルディスクリプタのクローズ順序です。
-
syscall.Read(w.fd, buf[0:])
からsyscall.Read(w.fd, buf[:])
への変更: これは、syscall.Read
に渡すバッファのスライス表現を簡潔にしたものです。buf[0:]
もbuf[:]
も、配列buf
全体を指すスライスを作成します。機能的な違いはなく、コードのスタイルを改善するための変更です。 -
close(w.Event)
の移動: 変更前は、syscall.Close(w.fd)
の後にclose(w.Event)
が呼び出されていました。 変更後は、syscall.Close(w.fd)
の前にclose(w.Event)
が呼び出されるようになりました。// 変更前 // err := syscall.Close(w.fd) // if err != nil { ... } // close(w.Event) // ここにあった // close(w.Error) // 変更後 // close(w.Event) // ここに移動 // err := syscall.Close(w.fd) // if err != nil { ... } // close(w.Error)
この順序の変更が、このコミットの最も重要な点です。コメントにもあるように、
syscall.Close
が遅い可能性があるため、先にw.Event
チャネルを閉じることで、イベントを待機しているクライアントがブロックされるのを防ぎます。クライアントはw.Event
チャネルが閉じられたことを検知し、それ以上のイベントが来ないことを認識して、自身の処理を続行できます。これにより、syscall.Close
の遅延がクライアント側の応答性に影響を与えることを防ぎます。 -
エラーチャネル
w.Error
のクローズは変更なし:w.Error
チャネルのクローズは、引き続きsyscall.Close(w.fd)
の後に実行されます。これは、syscall.Close
自体がエラーを返す可能性があるため、そのエラーをw.Error
チャネルを通じて報告する必要があるためです。
この変更により、inotify
ウォッチャーの終了処理がより堅牢になり、特にNFSのような遅延が発生しやすい環境下でのパフォーマンスと信頼性が向上しました。
関連リンク
- Go CL: https://golang.org/cl/7300045
- Go Issue 3132: https://code.google.com/p/go/issues/detail?id=3132 (古いGoのIssueトラッカーのリンクですが、関連する問題です)
参考にした情報源リンク
- inotify(7) - Linux man page
- Go by Example: Channels
- Go by Example: Channel Buffering
- Go by Example: Channel Directions
- Go by Example: Closing Channels
- Network File System (NFS) - Wikipedia
- syscall - The Go Programming Language
- os - The Go Programming Language
- Go issue 3132 discussion (if available via web search, as the original link is old) (GitHubのGoリポジトリのIssueトラッカーで検索すると、関連する議論が見つかる可能性があります)# [インデックス 15145] ファイルの概要
このコミットは、Go言語の実験的なexp/inotify
パッケージにおける、ファイルディスクリプタを閉じる際のパフォーマンス問題を解決するためのものです。特に、NFS(Network File System)環境下でのinotify
ウォッチャーの終了処理が遅延する問題に対処しています。
コミット
- コミットハッシュ:
da35d425214763d7d51d74e0410dc3a431c4a13d
- Author: Ian Lance Taylor iant@golang.org
- Date: Tue Feb 5 06:11:10 2013 -0800
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/da35d425214763d7d51d74e0410dc3a431c4a13d
元コミット内容
exp/inotify: close event channel before file descriptor
Closing the inotify file descriptor can take over a second
when running on Ubuntu Precise in an NFS directory, leading to
the test error in issue 3132. Closing the event channel first
lets a client that does not care about the error channel move
on.
Fixes #3132.
R=golang-dev, dave, rsc
CC=golang-dev
https://golang.org/cl/7300045
変更の背景
このコミットは、Go言語のexp/inotify
パッケージ(Linuxのinotify機能へのGoラッパー)において、ウォッチャーを終了する際のパフォーマンス問題を解決するために行われました。具体的には、inotify
ファイルディスクリプタ(w.fd
)を閉じるsyscall.Close
呼び出しが、特定の環境(Ubuntu Precise上のNFSディレクトリ)で1秒以上かかる場合があるという問題が報告されていました(Issue 3132)。
この遅延は、inotify
ウォッチャーのテストがタイムアウトしたり、アプリケーションが予期せずハングアップしたりする原因となっていました。特に、inotify
イベントを待機しているreadEvents
ゴルーチンが、syscall.Close
が完了するまでブロックされ、その間、クライアントがイベントチャネルからの読み取りを待機し続けるという状況が発生していました。
この問題に対処するため、ファイルディスクリプタを閉じる前に、イベントチャネルを先に閉じることで、クライアントがイベントの受信を待つことなく、ウォッチャーの終了処理を進められるようにする変更が導入されました。これにより、syscall.Close
の遅延が全体の終了処理に与える影響を軽減し、テストの失敗やアプリケーションの応答性低下を防ぐことが目的です。
前提知識の解説
- inotify: Linuxカーネルが提供するファイルシステムイベント監視メカニズムです。ファイルやディレクトリの作成、削除、変更などのイベントをリアルタイムでアプリケーションに通知します。Go言語の
exp/inotify
パッケージは、このinotify
システムコールをGoから利用するための実験的なラッパーを提供します。 - ファイルディスクリプタ (File Descriptor, FD): Unix系OSにおいて、ファイルやソケット、パイプなどのI/Oリソースを識別するためにカーネルが割り当てる非負の整数です。
inotify
もまた、inotify_init
システムコールによってファイルディスクリプタを返します。このFDを通じてイベントの読み取りや監視対象の追加/削除を行います。 syscall.Read
: Go言語のsyscall
パッケージに含まれる関数で、指定されたファイルディスクリプタからデータを読み取ります。inotify
の場合、この関数を使ってinotify
イベントを読み取ります。syscall.Close
: Go言語のsyscall
パッケージに含まれる関数で、指定されたファイルディスクリプタを閉じます。これにより、そのFDに関連付けられたリソースが解放されます。- Goのチャネル (Channel): Go言語におけるゴルーチン間の通信手段です。チャネルを通じてデータを送受信することで、並行処理における安全なデータ共有を実現します。
inotify
パッケージでは、イベント通知やエラー通知のためにチャネルが使用されます。 close(channel)
: Goのチャネルを閉じます。閉じられたチャネルから読み取ろうとすると、チャネルにデータが残っていればそれを読み取り、データがなければゼロ値が即座に返されます。これにより、受信側はチャネルが閉じられたことを検知し、それ以上のデータが送られてこないことを知ることができます。- NFS (Network File System): ネットワーク経由でファイルシステムを共有するためのプロトコルです。NFS上のファイル操作は、ローカルファイルシステムに比べてネットワークの遅延やサーバーの負荷によってパフォーマンスが低下する可能性があります。
- Issue 3132: Goのバグトラッカーで報告された特定の不具合です。このコミットの変更がこの問題の解決を目的としていることを示します。
技術的詳細
このコミットの核心は、inotify
ウォッチャーの終了処理におけるリソース解放の順序を変更することにあります。
従来のコードでは、readEvents
ゴルーチンが終了する際に、まずsyscall.Close(w.fd)
を呼び出してinotify
ファイルディスクリプタを閉じ、その後にclose(w.Event)
とclose(w.Error)
を呼び出してイベントチャネルとエラーチャネルを閉じていました。
問題は、syscall.Close(w.fd)
がNFS環境下で非常に遅くなる場合があることでした。syscall.Close
がブロックされている間、readEvents
ゴルーチンは停止し、w.Event
チャネルは閉じられないままでした。これにより、w.Event
チャネルからイベントを読み取ろうとしているクライアントゴルーチンは、syscall.Close
が完了するまで無限にブロックされる可能性がありました。これは、クライアントがウォッチャーの終了を待つ際に、不必要な遅延を引き起こす原因となります。
このコミットでは、この順序を反転させました。syscall.Close(w.fd)
を呼び出す前に、まずclose(w.Event)
を呼び出すように変更されています。
この変更の技術的な利点は以下の通りです。
- クライアントのブロック解除:
w.Event
チャネルが先に閉じられることで、w.Event
からの読み取りを待機しているクライアントゴルーチンは、syscall.Close
の完了を待つことなく、すぐにチャネルが閉じられたことを検知し、処理を続行できます。これにより、クライアント側での不必要な遅延やハングアップが回避されます。 - リソース解放の分離:
inotify
ファイルディスクリプタのクローズ(カーネルレベルのリソース解放)と、Goのチャネルのクローズ(Goランタイムレベルのリソース解放とゴルーチン間の通知)を論理的に分離します。これにより、遅延が発生しやすいsyscall.Close
の影響を、チャネルを介した通信に与えないようにできます。 - 堅牢性の向上: NFSのようなネットワークファイルシステムは、その性質上、I/O操作に予測不能な遅延が発生する可能性があります。この変更は、そのような外部要因による遅延が、アプリケーションの内部的な終了ロジックに与える影響を最小限に抑えることで、より堅牢な
inotify
ウォッチャーの実装に貢献します。
syscall.Read(w.fd, buf[0:])
からsyscall.Read(w.fd, buf[:])
への変更は、スライス操作の簡略化であり、機能的な違いはありません。buf[0:]
はbuf
全体のスライスを意味し、buf[:]
も同様にbuf
全体のスライスを意味します。これはコードの可読性をわずかに向上させるための変更と考えられます。
コアとなるコードの変更箇所
変更はsrc/pkg/exp/inotify/inotify_linux.go
ファイルにあります。
--- a/src/pkg/exp/inotify/inotify_linux.go
+++ b/src/pkg/exp/inotify/inotify_linux.go
@@ -153,7 +153,7 @@ func (w *Watcher) readEvents() {
var buf [syscall.SizeofInotifyEvent * 4096]byte
for {
- n, err := syscall.Read(w.fd, buf[0:])
+ n, err := syscall.Read(w.fd, buf[:])
// See if there is a message on the "done" channel
var done bool
select {
@@ -163,11 +163,13 @@ func (w *Watcher) readEvents() {
// If EOF or a "done" message is received
if n == 0 || done {
+\t\t\t// The syscall.Close can be slow. Close
+\t\t\t// w.Event first.
+\t\t\tclose(w.Event)
\t\t\terr := syscall.Close(w.fd)
\t\t\tif err != nil {\
\t\t\t\tw.Error <- os.NewSyscallError("close", err)
\t\t\t}\
-\t\t\tclose(w.Event)
\t\t\tclose(w.Error)
\t\t\treturn
\t\t}
コアとなるコードの解説
変更の主要な部分は、readEvents
ゴルーチンが終了する際のチャネルとファイルディスクリプタのクローズ順序です。
-
syscall.Read(w.fd, buf[0:])
からsyscall.Read(w.fd, buf[:])
への変更: これは、syscall.Read
に渡すバッファのスライス表現を簡潔にしたものです。buf[0:]
もbuf[:]
も、配列buf
全体を指すスライスを作成します。機能的な違いはなく、コードのスタイルを改善するための変更です。 -
close(w.Event)
の移動: 変更前は、syscall.Close(w.fd)
の後にclose(w.Event)
が呼び出されていました。 変更後は、syscall.Close(w.fd)
の前にclose(w.Event)
が呼び出されるようになりました。// 変更前 // err := syscall.Close(w.fd) // if err != nil { ... } // close(w.Event) // ここにあった // close(w.Error) // 変更後 // close(w.Event) // ここに移動 // err := syscall.Close(w.fd) // if err != nil { ... } // close(w.Error)
この順序の変更が、このコミットの最も重要な点です。コメントにもあるように、
syscall.Close
が遅い可能性があるため、先にw.Event
チャネルを閉じることで、イベントを待機しているクライアントがブロックされるのを防ぎます。クライアントはw.Event
チャネルが閉じられたことを検知し、それ以上のイベントが来ないことを認識して、自身の処理を続行できます。これにより、syscall.Close
の遅延がクライアント側の応答性に影響を与えることを防ぎます。 -
エラーチャネル
w.Error
のクローズは変更なし:w.Error
チャネルのクローズは、引き続きsyscall.Close(w.fd)
の後に実行されます。これは、syscall.Close
自体がエラーを返す可能性があるため、そのエラーをw.Error
チャネルを通じて報告する必要があるためです。
この変更により、inotify
ウォッチャーの終了処理がより堅牢になり、特にNFSのような遅延が発生しやすい環境下でのパフォーマンスと信頼性が向上しました。
関連リンク
- Go CL: https://golang.org/cl/7300045
- Go Issue 3132: https://code.google.com/p/go/issues/detail?id=3132 (古いGoのIssueトラッカーのリンクですが、関連する問題です)
参考にした情報源リンク
- inotify(7) - Linux man page
- Go by Example: Channels
- Go by Example: Closing Channels
- Network File System (NFS) - Wikipedia
- syscall - The Go Programming Language
- os - The Go Programming Language
- Go issue 3132 discussion (if available via web search, as the original link is old) (GitHubのGoリポジトリのIssueトラッカーで検索すると、関連する議論が見つかる可能性があります)