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

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

このコミットは、Go言語の標準ライブラリ net/rpc パッケージにおけるクライアント側の挙動に関する改善です。具体的には、RPC (Remote Procedure Call) の応答が、呼び出し元の Done チャネルの容量不足により破棄される場合に、その事象をログに出力するように変更されています。

コミット

commit eaa8b30d5a73a1406b7be12346dd67f013ac8221
Author: Dmitriy Vyukov <dvyukov@google.com>
Date:   Thu Jan 26 20:09:09 2012 +0400

    net/rpc: log Call reply discard
    It means serious user error that can lead to
    hard to debug issues under load, log entry
    will not harm.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/5574075

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

https://github.com/golang/go/commit/eaa8b30d5a73a1406b7be12346dd67f013ac8221

元コミット内容

net/rpc: log Call reply discard Call の応答が破棄された場合にログを出力する。 これは、負荷がかかった状況でデバッグが困難な問題を引き起こす可能性のある、深刻なユーザーエラーを意味する。ログエントリは害にはならないだろう。

変更の背景

Go言語の net/rpc パッケージは、Goプログラム間でRPCを介した通信を可能にするための標準ライブラリです。クライアントがサーバーにRPCを呼び出し、その結果を非同期的に受け取るために Call オブジェクトを使用します。Call オブジェクトには Done というチャネルがあり、RPCの完了時にそのチャネルに Call オブジェクト自身が送信されます。

このコミットが導入される以前は、もしクライアントが Call.Done チャネルから結果を受け取らない、あるいはチャネルのバッファが満杯であるために Call オブジェクトを送信できない場合、そのRPCの応答はサイレントに破棄されていました。これは、特に高負荷時や、クライアントがRPCの結果を適切に処理しない場合に発生しうる問題です。応答が破棄されても、呼び出し元にはその事実が通知されないため、デバッグが非常に困難になる可能性がありました。

コミットメッセージにある「深刻なユーザーエラー」とは、通常、クライアントコードが Call.Done チャネルを適切に監視していない、またはチャネルのバッファサイズが不適切であるといった状況を指します。このような状況は、アプリケーションのデッドロック、リソースリーク、あるいは予期せぬ動作につながる可能性があります。この変更は、このような潜在的な問題を早期に発見し、デバッグを容易にすることを目的としています。ログ出力は、問題が発生していることを開発者に明示的に通知する手段となります。

前提知識の解説

  • RPC (Remote Procedure Call): ネットワーク上の別のコンピュータにあるプログラムのサブルーチンや関数を、あたかもローカルにあるかのように呼び出すための技術です。net/rpc パッケージは、Go言語におけるこのRPCの仕組みを提供します。
  • Go言語の net/rpc パッケージ: Goの標準ライブラリの一部で、ネットワーク経由でGoの関数を呼び出すためのシンプルなRPCメカニズムを提供します。クライアントとサーバーのコンポーネントが含まれます。
  • rpc.Client.Go メソッド: net/rpc クライアントにおいて、非同期RPC呼び出しを行うためのメソッドです。このメソッドは *rpc.Call オブジェクトを返します。
  • rpc.Call オブジェクト: 個々のRPC呼び出しの状態を保持する構造体です。これには、呼び出されたサービスメソッド、引数、応答、エラー情報などが含まれます。
  • rpc.Call.Done チャネル: rpc.Call オブジェクトのフィールドの一つで、RPC呼び出しが完了したときに *rpc.Call オブジェクト自身が送信されるチャネルです。クライアントはこのチャネルを監視することで、非同期呼び出しの完了を待つことができます。
  • Go言語のチャネル (Channel): Goにおけるゴルーチン間の通信手段です。チャネルは値を送受信するためのパイプのようなもので、ゴルーチン間の同期にも使用されます。チャネルにはバッファリングされたものとされていないものがあります。バッファリングされていないチャネルは、送信側と受信側が同時に準備ができていないとブロックします。バッファリングされたチャネルは、指定された数の要素を格納でき、バッファが満杯でない限り送信はブロックされません。
  • select ステートメント: 複数のチャネル操作を待機し、準備ができた最初の操作を実行するためのGoの制御構造です。default ケースを持つ select は、どのチャネル操作も準備ができていない場合にすぐに実行されます。
  • log.Println: Goの標準 log パッケージの関数で、メッセージを標準エラー出力(または設定された出力先)にログとして出力します。

技術的詳細

この変更は、src/pkg/net/rpc/client.go ファイル内の (*Call) done() メソッドに影響を与えます。done() メソッドは、RPC呼び出しが完了したときに Call オブジェクトを Call.Done チャネルに送信する役割を担っています。

元の実装では、done() メソッド内で select ステートメントが使用されており、Call.Done チャネルへの送信を試みていました。この select ステートメントには default ケースが含まれていました。default ケースは、Call.Done チャネルへの送信がすぐにできない場合(つまり、チャネルがバッファリングされておらず、受信側が準備できていない場合、またはバッファリングされたチャネルが満杯である場合)に実行されます。

変更前は、default ケースでは何も処理が行われていませんでした。これは、RPCの応答が Call.Done チャネルに送信されずに「破棄」されることを意味します。コミットメッセージにもあるように、これは「呼び出し元の責任でチャネルに十分なバッファスペースを確保すること」という設計思想に基づいています。しかし、このサイレントな破棄は、デバッグを困難にするという副作用がありました。

今回の変更では、この default ケースに log.Println を追加することで、応答が破棄された場合にログメッセージが出力されるようになりました。これにより、開発者はRPC応答が失われていることを検知し、Call.Done チャネルの処理方法やバッファサイズを見直すきっかけを得ることができます。ログメッセージは「rpc: discarding Call reply due to insufficient Done chan capacity」と具体的に問題の原因を示唆しています。

この変更は、net/rpc のクライアントが Go() メソッドで Call オブジェクトを返す際に、Call.Done チャネルの容量が不足している場合に、その Call オブジェクトが Done チャネルに送信されずに破棄されるという挙動を明示的に通知するものです。これは、RPCの信頼性やデバッグの容易性を向上させるための、小さなしかし重要な改善と言えます。

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

--- a/src/pkg/net/rpc/client.go
+++ b/src/pkg/net/rpc/client.go
@@ -145,6 +145,7 @@ func (call *Call) done() {
 	default:
 		// We don't want to block here.  It is the caller's responsibility to make
 		// sure the channel has enough buffer space. See comment in Go().
+		log.Println("rpc: discarding Call reply due to insufficient Done chan capacity")
 	}
 }

コアとなるコードの解説

上記の差分は、src/pkg/net/rpc/client.go ファイル内の (*Call) done() メソッドに対する変更を示しています。

  • func (call *Call) done() { ... }: これは Call 型のメソッドで、RPC呼び出しが完了した際に内部的に呼び出されます。このメソッドの主な目的は、完了した Call オブジェクトを call.Done チャネルに送信することです。
  • select { ... default: ... }: Goの select ステートメントは、複数のチャネル操作のうち、準備ができたものを待機します。default ケースが存在する場合、どのチャネル操作もすぐに実行できない場合に default ケース内のコードが実行されます。
  • case call.Done <- call:: このケースは、call.Done チャネルに call オブジェクトを送信しようとします。もしチャネルがバッファリングされておらず、受信側が準備できていない場合、またはバッファリングされたチャネルが満杯の場合、この送信操作はブロックされます。
  • // We don't want to block here. ...: これは既存のコメントで、done() メソッドがブロックされることを望まないという設計意図を示しています。つまり、Call.Done チャネルへの送信がすぐにできない場合でも、done() メソッドはすぐに制御を返す必要があります。
  • log.Println("rpc: discarding Call reply due to insufficient Done chan capacity"): これが追加された行です。select ステートメントの default ケース内に配置されています。これにより、call.Done チャネルへの送信がブロックされ、かつ default ケースが実行された場合(すなわち、RPC応答が破棄された場合)に、このログメッセージが標準エラー出力に記録されます。メッセージは、応答が破棄された理由が「Done チャネルの容量不足」であることを明確に示しています。

この変更により、net/rpc クライアントの利用者は、RPC応答が予期せず失われている状況をログを通じて検知できるようになり、デバッグと問題解決が大幅に容易になります。

関連リンク

参考にした情報源リンク