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

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

このコミットは、Go言語の net/rpc パッケージにおいて、接続終了時に発生する読み込みエラー(read error)を適切に抑制するための修正です。具体的には、クライアントが接続を閉じている最中に io.EOF エラーが発生した場合に、不必要なエラーログが出力されるのを防ぎます。

コミット

commit 91bdbf591fe08c394f5ea3924774968202cde07b
Author: Russ Cox <rsc@golang.org>
Date:   Thu Feb 23 22:45:44 2012 -0500

    net/rpc: silence read error on closing connection
    
    Fixes #3113.
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/5698056

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

https://github.com/golang/go/commit/91bdbf591fe08c394f5ea3924774968202cde07b

元コミット内容

このコミットは、Go言語の標準ライブラリである net/rpc パッケージのクライアント側で発生していた、接続終了時の不必要なエラーログ出力を修正するものです。具体的には、クライアントがRPC接続を正常に終了しようとしている際に、基盤となるネットワーク接続からの読み込みで io.EOF エラーが発生した場合に、それが「プロトコルエラー」として誤ってログに記録されてしまう問題を解決します。この修正により、接続が正常に閉じられる過程で発生する予期された io.EOF は、エラーとして扱われなくなります。

変更の背景

net/rpc パッケージは、Go言語におけるリモートプロシージャコール(RPC)を実装するための標準的な方法を提供します。クライアントとサーバー間で通信を行う際、クライアントがRPCセッションを終了し、接続を閉じることは一般的な操作です。

このコミットが修正しようとしている問題は、クライアントが接続を閉じている最中に、基盤となるネットワーク接続からデータを読み込もうとした際に io.EOF (End Of File) エラーが発生することに起因します。io.EOF は、通常、ストリームの終端に達したことを示す正常な状態であり、エラーとは異なる文脈で扱われるべきです。

しかし、修正前のコードでは、接続が閉じられている状態 (closing フラグが true) であっても、io.EOF エラーが log.Println("rpc: client protocol error:", err) によって「プロトコルエラー」としてログに出力されていました。これは、開発者にとって誤解を招く可能性のあるノイズの多いログとなり、実際の異常なプロトコルエラーと区別がつきにくくなるという問題がありました。

この修正の目的は、このような正常な接続終了プロセスの一部として発生する io.EOF を、エラーとして扱わないようにすることで、ログのノイズを減らし、真のプロトコルエラーの検出を容易にすることです。これは、Issue #3113 で報告された問題に対応しています。

前提知識の解説

net/rpc パッケージ

net/rpc はGo言語の標準ライブラリの一部で、ネットワーク経由でリモートプロシージャコール(RPC)を実装するための機能を提供します。これにより、異なるプロセスや異なるマシン上で実行されているプログラム間で、まるでローカル関数を呼び出すかのように関数を呼び出すことができます。

  • クライアント (Client): リモートの関数を呼び出す側。
  • サーバー (Server): リモートからの関数呼び出しを受け付け、実行する側。
  • エンコーディング: デフォルトではGoの encoding/gob パッケージを使用してデータのシリアライズ/デシリアライズを行います。
  • トランスポート: TCPなどのネットワークプロトコル上で動作します。

net/rpc クライアントは、通常、サーバーへの接続を確立し、その接続を通じてRPCリクエストを送信し、レスポンスを受信します。接続が不要になった場合、クライアントは接続を閉じます。

io.EOF

io.EOF は、Go言語の io パッケージで定義されている特別なエラー変数です。これは、入力ストリームの終端(End Of File)に達したことを示します。ファイル読み込み、ネットワークソケットからの読み込み、その他のストリーム処理において、読み込むべきデータがこれ以上存在しない場合に返されます。

io.EOF は、多くの場合、エラーというよりも「正常な終了条件」として扱われます。例えば、ファイルを最後まで読み込んだ場合や、ネットワーク接続が相手側から正常に閉じられた場合などに io.EOF が返されます。したがって、io.EOF が返されたからといって、必ずしも異常事態が発生したわけではありません。

Go言語におけるエラーハンドリングの一般的な考え方

Go言語では、エラーは関数の戻り値として明示的に扱われます。慣習として、関数は通常、最後の戻り値として error 型の値を返します。エラーが発生しなかった場合は nil を返し、エラーが発生した場合は nil ではない error 値を返します。

エラーハンドリングの基本的なパターンは以下のようになります。

result, err := someFunction()
if err != nil {
    // エラー処理
    // 例: log.Printf("Error: %v", err)
    return err
}
// 正常処理

このコミットの文脈では、io.EOF が返された場合に、それが本当に「プロトコルエラー」としてログに記録されるべきなのか、それとも正常な接続終了の一部として無視されるべきなのか、という判断が重要になります。

技術的詳細

このコミットの技術的な核心は、論理演算子の変更にあります。具体的には、src/pkg/net/rpc/client.go ファイル内の input() メソッドで、エラーログ出力の条件を制御する if 文が変更されました。

変更前:

if err != io.EOF || !closing {
    log.Println("rpc: client protocol error:", err)
}

変更後:

if err != io.EOF && !closing {
    log.Println("rpc: client protocol error:", err)
}

この変更は、論理OR演算子 (||) を論理AND演算子 (&&) に置き換えるものです。この変更がもたらす意味は以下の通りです。

  • 変更前 (||) の挙動:

    • err != io.EOFtrue の場合(つまり、io.EOF 以外のエラーが発生した場合)、if 文の条件全体が true となり、ログが出力されます。
    • err == io.EOF であっても、!closingtrue の場合(つまり、接続が閉じられていない場合)、if 文の条件全体が true となり、ログが出力されます。
    • 問題は、err == io.EOF かつ closingtrue の場合(つまり、接続が閉じられている最中に io.EOF が発生した場合)にのみ、ログが出力されないという点でした。それ以外の場合、特に io.EOF が発生しても closingfalse であればログが出力されていました。しかし、この文脈では closingtrue の時に io.EOF が発生した場合にログが出力されてしまうことが問題でした。
  • 変更後 (&&) の挙動:

    • if 文の条件が true となるのは、両方の条件が true の場合のみです。
    • つまり、err != io.EOF (エラーが io.EOF ではない) かつ !closing (接続が閉じられていない) の場合にのみ、ログが出力されます。
    • これにより、以下のシナリオではログが出力されなくなります。
      • err == io.EOF の場合(io.EOF エラーが発生した場合)。この場合、最初の条件 err != io.EOFfalse となるため、&& 演算子の性質上、条件全体が false となり、ログは出力されません。これは、接続が閉じられている最中であろうとなかろうと、io.EOF はプロトコルエラーではないという判断に基づいています。
      • closingtrue の場合(接続が閉じられている最中の場合)。この場合、二番目の条件 !closingfalse となるため、条件全体が false となり、ログは出力されません。

この修正により、接続が正常に閉じられる過程で発生する io.EOF エラーや、接続が閉じられている最中に発生するあらゆる読み込みエラーが、不必要な「プロトコルエラー」としてログに記録されることがなくなります。これは、ログのノイズを減らし、開発者が真の異常なプロトコルエラーに集中できるようにするために重要です。

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

--- a/src/pkg/net/rpc/client.go
+++ b/src/pkg/net/rpc/client.go
@@ -140,7 +140,7 @@ func (client *Client) input() {
 	}\n \tclient.mutex.Unlock()\n \tclient.sending.Unlock()\n-\tif err != io.EOF || !closing {\n+\tif err != io.EOF && !closing {\n \t\tlog.Println(\"rpc: client protocol error:\", err)\n \t}\n }\

コアとなるコードの解説

変更されたコードは、net/rpc クライアントの input() メソッド内にあります。このメソッドは、RPCサーバーからのレスポンスを非同期的に読み取る役割を担っています。

元のコード: if err != io.EOF || !closing { ... }

この条件文は、「エラーが io.EOF ではない」または「接続が閉じられていない」場合に、"rpc: client protocol error:" というメッセージをログに出力していました。 問題は、接続が閉じられている最中 (closingtrue) であっても、もし errio.EOF 以外の何らかのエラーであればログが出力される、という点でした。また、io.EOF が発生した場合でも、closingfalse であればログが出力されていました。特に、接続が正常に閉じられる過程で io.EOF が発生することは予期される動作であり、これを「プロトコルエラー」としてログに出力するのは適切ではありませんでした。

修正後のコード: if err != io.EOF && !closing { ... }

この条件文は、「エラーが io.EOF ではない」かつ「接続が閉じられていない」場合にのみ、ログを出力するように変更されました。 この変更により、以下のいずれかの条件が満たされれば、ログは出力されなくなります。

  1. err == io.EOF の場合: io.EOF はストリームの終端を示すものであり、接続が正常に閉じられたことを意味する場合があるため、これをプロトコルエラーとして扱うべきではありません。
  2. closing == true の場合: クライアントが意図的に接続を閉じている最中であれば、その過程で発生する読み込みエラー(io.EOF を含む)は、プロトコルエラーではなく、正常なシャットダウンプロセスの一部と見なされます。

したがって、この修正は、net/rpc クライアントが接続を正常に終了する際に発生する、予期された io.EOF や、接続終了プロセス中の読み込みエラーを「プロトコルエラー」として誤ってログに記録するのを防ぎます。これにより、ログのノイズが減り、開発者は真の異常なプロトコルエラーに集中できるようになります。

関連リンク

参考にした情報源リンク