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

[インデックス 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)を呼び出すように変更されています。

この変更の技術的な利点は以下の通りです。

  1. クライアントのブロック解除: w.Eventチャネルが先に閉じられることで、w.Eventからの読み取りを待機しているクライアントゴルーチンは、syscall.Closeの完了を待つことなく、すぐにチャネルが閉じられたことを検知し、処理を続行できます。これにより、クライアント側での不必要な遅延やハングアップが回避されます。
  2. リソース解放の分離: inotifyファイルディスクリプタのクローズ(カーネルレベルのリソース解放)と、Goのチャネルのクローズ(Goランタイムレベルのリソース解放とゴルーチン間の通知)を論理的に分離します。これにより、遅延が発生しやすいsyscall.Closeの影響を、チャネルを介した通信に与えないようにできます。
  3. 堅牢性の向上: 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ゴルーチンが終了する際のチャネルとファイルディスクリプタのクローズ順序です。

  1. syscall.Read(w.fd, buf[0:]) から syscall.Read(w.fd, buf[:]) への変更: これは、syscall.Readに渡すバッファのスライス表現を簡潔にしたものです。buf[0:]buf[:]も、配列buf全体を指すスライスを作成します。機能的な違いはなく、コードのスタイルを改善するための変更です。

  2. 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の遅延がクライアント側の応答性に影響を与えることを防ぎます。

  3. エラーチャネル w.Error のクローズは変更なし: w.Errorチャネルのクローズは、引き続きsyscall.Close(w.fd)の後に実行されます。これは、syscall.Close自体がエラーを返す可能性があるため、そのエラーをw.Errorチャネルを通じて報告する必要があるためです。

この変更により、inotifyウォッチャーの終了処理がより堅牢になり、特にNFSのような遅延が発生しやすい環境下でのパフォーマンスと信頼性が向上しました。

関連リンク

参考にした情報源リンク

このコミットは、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)を呼び出すように変更されています。

この変更の技術的な利点は以下の通りです。

  1. クライアントのブロック解除: w.Eventチャネルが先に閉じられることで、w.Eventからの読み取りを待機しているクライアントゴルーチンは、syscall.Closeの完了を待つことなく、すぐにチャネルが閉じられたことを検知し、処理を続行できます。これにより、クライアント側での不必要な遅延やハングアップが回避されます。
  2. リソース解放の分離: inotifyファイルディスクリプタのクローズ(カーネルレベルのリソース解放)と、Goのチャネルのクローズ(Goランタイムレベルのリソース解放とゴルーチン間の通知)を論理的に分離します。これにより、遅延が発生しやすいsyscall.Closeの影響を、チャネルを介した通信に与えないようにできます。
  3. 堅牢性の向上: 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ゴルーチンが終了する際のチャネルとファイルディスクリプタのクローズ順序です。

  1. syscall.Read(w.fd, buf[0:]) から syscall.Read(w.fd, buf[:]) への変更: これは、syscall.Readに渡すバッファのスライス表現を簡潔にしたものです。buf[0:]buf[:]も、配列buf全体を指すスライスを作成します。機能的な違いはなく、コードのスタイルを改善するための変更です。

  2. 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の遅延がクライアント側の応答性に影響を与えることを防ぎます。

  3. エラーチャネル w.Error のクローズは変更なし: w.Errorチャネルのクローズは、引き続きsyscall.Close(w.fd)の後に実行されます。これは、syscall.Close自体がエラーを返す可能性があるため、そのエラーをw.Errorチャネルを通じて報告する必要があるためです。

この変更により、inotifyウォッチャーの終了処理がより堅牢になり、特にNFSのような遅延が発生しやすい環境下でのパフォーマンスと信頼性が向上しました。

関連リンク

参考にした情報源リンク