[インデックス 16629] ファイルの概要
このコミットは、Go言語の標準ライブラリ net/rpc
パッケージにおけるバグ修正に関するものです。具体的には、rpc.RegisterName
関数を使用してサービスを登録する際に、サービス名にドット(.
)文字が含まれていると、そのサービス内のメソッド呼び出しが正しく解決されない問題を修正しています。
コミット
commit 3eaaed5030a531d11a228353b3f964e4b0227ba7
Author: ChaiShushan <chaishushan@gmail.com>
Date: Mon Jun 24 13:23:02 2013 -0700
net/rpc: fix RegisterName rejects "." character.
Fixes #5617.
R=r, rsc
CC=gobot, golang-dev
https://golang.org/cl/10370043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3eaaed5030a531d11a228353b3f964e4b0227ba7
元コミット内容
net/rpc: fix RegisterName rejects "." character.
このコミットは、net/rpc
パッケージにおいて、RegisterName
関数がサービス名にドット(.
)文字を含むことを拒否する問題を修正します。これはIssue #5617を解決するものです。
変更の背景
Goの net/rpc
パッケージは、リモートプロシージャコール(RPC)を実装するためのメカニズムを提供します。クライアントはサービス名とメソッド名をドットで区切った文字列(例: Service.Method
)として指定してRPCを呼び出します。
このコミットが修正する問題は、rpc.RegisterName
を使ってサービスを登録する際に、サービス名自体にドットが含まれている場合に発生していました。例えば、"net.rpc.Arith"
のようなサービス名を登録しようとすると、内部のメソッド解決ロジックがこのドットをサービス名とメソッド名の区切りと誤解し、正しいサービスやメソッドを見つけられなくなるというバグがありました。
元の実装では、req.ServiceMethod
(クライアントから送られてくる Service.Method
形式の文字列)を単純に strings.Split(req.ServiceMethod, ".")
で分割していました。これにより、サービス名自体にドットが含まれていると、分割結果の配列の要素数が2ではなくなり、エラーとなっていました。
この問題は、特に既存の命名規則や他のシステムとの連携において、サービス名にドットを使用したい場合に制約となっていました。Issue #5617で報告され、このコミットによって解決されました。
前提知識の解説
Go言語の net/rpc
パッケージ
net/rpc
パッケージは、Goプログラム間でRPCを実装するための標準ライブラリです。これは、ネットワーク越しに別のGoプログラムの関数を呼び出すことを可能にします。
- サービス (Service): RPCで公開される一連のメソッドを持つGoの構造体(またはポインタ)。
- メソッド (Method): サービス内で公開される関数。特定のシグネチャ(
func (t *T) Method(argType T1, replyType *T2) error
)を持つ必要があります。 rpc.Register(rcvr interface{})
: 指定されたレシーバ(構造体のインスタンス)を、その型名(例:Arith
)をサービス名としてRPCシステムに登録します。rpc.RegisterName(name string, rcvr interface{})
: 指定されたレシーバを、name
で指定された文字列をサービス名としてRPCシステムに登録します。この関数が、サービス名にドットを含む場合に問題を引き起こしていました。- クライアントからの呼び出し: クライアントは
client.Call("Service.Method", args, reply)
の形式でRPCを呼び出します。ここで"Service.Method"
は、サービス名とメソッド名をドットで区切った文字列です。
サービスとメソッドの解決
net/rpc
サーバーは、クライアントから送られてきた Service.Method
形式の文字列を受け取ると、それを解析して適切なサービスとメソッドを特定します。
- サービス名の抽出:
Service.Method
文字列からサービス名を抽出します。 - メソッド名の抽出:
Service.Method
文字列からメソッド名を抽出します。 - サービスマップの検索: サーバーが内部で保持している登録済みサービスのマッピング(
server.serviceMap
)から、抽出したサービス名に対応するサービスを探します。 - メソッドマップの検索: 見つかったサービス内のメソッドのマッピングから、抽出したメソッド名に対応するメソッドを探します。
この一連の解決プロセスにおいて、サービス名とメソッド名の区切り文字としてドットが使用されるため、サービス名自体にドットが含まれると、この解析ロジックが誤動作する可能性がありました。
技術的詳細
このコミットの技術的な核心は、クライアントから送られてくる req.ServiceMethod
文字列の解析方法の変更にあります。
元の実装では、strings.Split(req.ServiceMethod, ".")
を使用して、文字列をドットで分割していました。例えば、"Arith.Add"
は ["Arith", "Add"]
となります。しかし、"net.rpc.Arith.Add"
のようなケースでは、["net", "rpc", "Arith", "Add"]
となり、配列の長さが2ではなくなってしまうため、サービス名とメソッド名の区別が不可能になり、エラーとなっていました。
修正後のコードでは、strings.LastIndex(req.ServiceMethod, ".")
を使用しています。これは、文字列内で最後のドットの位置を探す関数です。
dot := strings.LastIndex(req.ServiceMethod, ".")
- これにより、
"net.rpc.Arith.Add"
の場合、dot
は最後のドット(Arith
とAdd
の間のドット)のインデックスを指します。
- これにより、
serviceName := req.ServiceMethod[:dot]
- 文字列の先頭から最後のドットの直前までをサービス名とします。
- 例:
"net.rpc.Arith.Add"
の場合、serviceName
は"net.rpc.Arith"
となります。
methodName := req.ServiceMethod[dot+1:]
- 最後のドットの直後から文字列の末尾までをメソッド名とします。
- 例:
"net.rpc.Arith.Add"
の場合、methodName
は"Add"
となります。
この変更により、サービス名自体にドットが含まれていても、最後のドットがサービス名とメソッド名の区切りとして正しく機能するようになり、サービスとメソッドの解決が正常に行われるようになりました。
この修正は、net/rpc
の柔軟性を高め、より複雑なサービス命名規則を許容するようになります。
コアとなるコードの変更箇所
変更は src/pkg/net/rpc/server.go
と src/pkg/net/rpc/server_test.go
の2つのファイルにわたります。
src/pkg/net/rpc/server.go
--- a/src/pkg/net/rpc/server.go
+++ b/src/pkg/net/rpc/server.go
@@ -560,20 +560,23 @@ func (server *Server) readRequestHeader(codec ServerCodec) (service *service, mt
// we can still recover and move on to the next request.
keepReading = true
- serviceMethod := strings.Split(req.ServiceMethod, ".")
- if len(serviceMethod) != 2 {
+ dot := strings.LastIndex(req.ServiceMethod, ".")
+ if dot < 0 {
err = errors.New("rpc: service/method request ill-formed: " + req.ServiceMethod)
return
}
+ serviceName := req.ServiceMethod[:dot]
+ methodName := req.ServiceMethod[dot+1:]
+
// Look up the request.
server.mu.RLock()
- service = server.serviceMap[serviceMethod[0]]
+ service = server.serviceMap[serviceName]
server.mu.RUnlock()
if service == nil {
err = errors.New("rpc: can't find service " + req.ServiceMethod)
return
}
- mtype = service.method[serviceMethod[1]]
+ mtype = service.method[methodName]
if mtype == nil {
err = errors.New("rpc: can't find method " + req.ServiceMethod)
}
src/pkg/net/rpc/server_test.go
--- a/src/pkg/net/rpc/server_test.go
+++ b/src/pkg/net/rpc/server_test.go
@@ -84,6 +84,7 @@ func listenTCP() (net.Listener, string) {
func startServer() {
Register(new(Arith))
+ RegisterName("net.rpc.Arith", new(Arith))
var l net.Listener
l, serverAddr = listenTCP()
@@ -97,6 +98,7 @@ func startServer() {
func startNewServer() {
newServer = NewServer()
newServer.Register(new(Arith))
+ newServer.RegisterName("net.rpc.Arith", new(Arith))
var l net.Listener
l, newServerAddr = listenTCP()
@@ -234,6 +236,17 @@ func testRPC(t *testing.T, addr string) {
if reply.C != args.A*args.B {
t.Errorf("Mul: expected %d got %d", reply.C, args.A*args.B)
}\n
+\n // ServiceName contain "." character
+\targs = &Args{7, 8}
+\treply = new(Reply)
+\terr = client.Call("net.rpc.Arith.Add", args, reply)
+\tif err != nil {
+\t\tt.Errorf("Add: expected no error but got string %q", err.Error())
+\t}
+\tif reply.C != args.A+args.B {
+\t\tt.Errorf("Add: expected %d got %d", reply.C, args.A+args.B)
+\t}
}\n
func TestHTTP(t *testing.T) {
コアとなるコードの解説
src/pkg/net/rpc/server.go
の変更
このファイルは net/rpc
サーバーの主要なロジックを含んでいます。変更は readRequestHeader
関数内で行われています。この関数は、クライアントからのRPCリクエストヘッダーを読み込み、サービスとメソッドを解決する役割を担っています。
-
変更前:
serviceMethod := strings.Split(req.ServiceMethod, ".") if len(serviceMethod) != 2 { err = errors.New("rpc: service/method request ill-formed: " + req.ServiceMethod) return } // ... service = server.serviceMap[serviceMethod[0]] // ... mtype = service.method[serviceMethod[1]]
ここでは、
req.ServiceMethod
をドットで分割し、その結果が必ず2つの要素を持つことを期待していました。サービス名にドットが含まれるとこの条件を満たさなくなり、エラーが発生していました。 -
変更後:
dot := strings.LastIndex(req.ServiceMethod, ".") if dot < 0 { err = errors.New("rpc: service/method request ill-formed: " + req.ServiceMethod) return } serviceName := req.ServiceMethod[:dot] methodName := req.ServiceMethod[dot+1:] // ... service = server.serviceMap[serviceName] // ... mtype = service.method[methodName]
strings.Split
の代わりにstrings.LastIndex
を使用することで、req.ServiceMethod
文字列の最後のドットをサービス名とメソッド名の区切りとして扱います。req.ServiceMethod[:dot]
は、文字列の先頭から最後のドットの直前までを抽出し、これをserviceName
とします。req.ServiceMethod[dot+1:]
は、最後のドットの直後から文字列の末尾までを抽出し、これをmethodName
とします。 このロジックにより、サービス名自体にドットが含まれていても、正しくサービス名とメソッド名を分離できるようになりました。
src/pkg/net/rpc/server_test.go
の変更
このファイルは net/rpc
パッケージのテストコードを含んでいます。変更は、新しいテストケースの追加と、既存のテストセットアップの修正です。
-
startServer()
およびstartNewServer()
関数への追加:RegisterName("net.rpc.Arith", new(Arith))
RegisterName
を使用して、サービス名にドットを含む"net.rpc.Arith"
という名前でArith
サービスを登録しています。これは、修正されたロジックが正しく機能するかを検証するための準備です。 -
testRPC()
関数への新しいテストケースの追加:// ServiceName contain "." character args = &Args{7, 8} reply = new(Reply) err = client.Call("net.rpc.Arith.Add", args, reply) if err != nil { t.Errorf("Add: expected no error but got string %q", err.Error()) } if reply.C != args.A+args.B { t.Errorf("Add: expected %d got %d", reply.C, args.A+args.B) }
この新しいテストケースでは、サービス名にドットを含む
"net.rpc.Arith.Add"
を使用してAdd
メソッドを呼び出しています。このテストがエラーなく成功することで、server.go
で行われた修正が意図通りに機能し、サービス名にドットが含まれていてもRPC呼び出しが正しく解決されることが検証されます。
これらの変更により、net/rpc
パッケージは、より柔軟なサービス命名規則に対応できるようになり、特定の命名パターンに依存する既存システムとの連携が容易になりました。
関連リンク
- Go CL (Code Review) ページ: https://golang.org/cl/10370043
- Go Issue #5617: https://github.com/golang/go/issues/5617
参考にした情報源リンク
- Go言語の
net/rpc
パッケージのドキュメント - Go言語の
strings
パッケージのドキュメント - GitHub上のGoリポジトリのコミット履歴
- Go Issue #5617 の議論内容