[インデックス 10047] RPCクライアントのエラーハンドリング改善
コミット
コミットハッシュ: 4c56c30b78199c3313c1ee0042f0adcc685bae33
作成者: Rob Pike r@golang.org
日付: 2011年10月18日 15:52:49 -0700
メッセージ: rpc: don't panic on write error.
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4c56c30b78199c3313c1ee0042f0adcc685bae33
元コミット内容
commit 4c56c30b78199c3313c1ee0042f0adcc685bae33
Author: Rob Pike <r@golang.org>
Date: Tue Oct 18 15:52:49 2011 -0700
rpc: don't panic on write error.
The mechanism to record the error in the call is already in place.
Fixes #2382.
R=golang-dev, dsymonds, bradfitz, r
CC=golang-dev
https://golang.org/cl/5307043
変更の背景
このコミットは、Go言語のRPCパッケージにおける重要な問題を修正しています。Go言語が開発初期段階にあった2011年、RPCクライアントがサーバーからの切断やネットワークエラーに遭遇した際、プログラム全体をクラッシュさせるpanic()を発生させていました。
具体的には、以下のような状況でpanicが発生していました:
- サーバーがハングアップした場合
- ネットワーク接続が切断された場合
- 書き込みエラーが発生した場合
これらは分散システムにおいて避けられない通常の障害であり、panicではなく適切なエラーハンドリングで対処すべき問題でした。
前提知識の解説
Go言語におけるpanicとエラーハンドリング
Go言語では、エラーハンドリングに関して明確な哲学があります:
- 通常のエラー処理:期待される可能性のあるエラー(ネットワーク障害、入力エラー等)は、明示的なエラー戻り値として処理する
- panic処理:プログラムが継続できない例外的な状況(プログラミングエラー、不変条件の違反等)でのみ使用する
RPCとは
RPC(Remote Procedure Call)は、ネットワーク上の別のコンピューターにある関数を、ローカルの関数と同じように呼び出すことができる技術です。Go言語のRPCパッケージは、この機能を実装しています。
RPCにおいて、以下のような問題が発生する可能性があります:
- ネットワーク接続の切断
- サーバーの応答停止
- データのエンコード/デコードエラー
技術的詳細
修正前の問題
修正前のコードでは、client.go
のsend
メソッドで以下のような処理が行われていました:
if err := client.codec.WriteRequest(&client.request, c.Args); err != nil {
panic("rpc: client encode error: " + err.String())
}
このコードは、WriteRequestでエラーが発生した場合、即座にpanicを発生させ、アプリケーション全体をクラッシュさせていました。
修正後の改善
修正後は、以下のような適切なエラーハンドリングに変更されました:
if err := client.codec.WriteRequest(&client.request, c.Args); err != nil {
c.Error = err
c.done()
}
この変更により:
- エラーがCall構造体のErrorフィールドに保存される
c.done()
が呼ばれることで、呼び出し元にエラーが通知される- アプリケーションがクラッシュすることなく、適切にエラーを処理できる
コアとなるコードの変更箇所
1. client.go:85-88行の修正(src/pkg/rpc/client.go)
変更前:
if err := client.codec.WriteRequest(&client.request, c.Args); err != nil {
panic("rpc: client encode error: " + err.String())
}
変更後:
if err := client.codec.WriteRequest(&client.request, c.Args); err != nil {
c.Error = err
c.done()
}
2. client.go:252-267行の変数名リファクタリング
変更前:
c := new(Call)
c.ServiceMethod = serviceMethod
c.Args = args
c.Reply = reply
変更後:
call := new(Call)
call.ServiceMethod = serviceMethod
call.Args = args
call.Reply = reply
3. テストケースの追加(src/pkg/rpc/server_test.go)
新しいテストケースTestClientWriteError
が追加され、書き込みエラーをシミュレートするwriteCrasher
型も定義されました。
コアとなるコードの解説
エラーハンドリングメカニズム
修正により、以下のエラーハンドリングフローが確立されました:
- エラーの発生:
client.codec.WriteRequest
でエラーが発生 - エラーの記録:
c.Error = err
でCall構造体にエラーを保存 - 完了通知:
c.done()
でDoneチャンネルに通知を送信 - 呼び出し元への返却:呼び出し元でエラーを検出し、適切に処理
Call構造体の役割
Call構造体は、RPC呼び出しの状態を管理する重要な要素です:
ServiceMethod
:呼び出すサービスとメソッド名Args
:引数Reply
:戻り値Error
:エラー情報Done
:完了を通知するチャンネル
テストケースの意義
追加されたテストケースは、書き込みエラーが発生した際の動作を検証します:
writeCrasher
型は、常に書き込みエラーを返すモックオブジェクトTestClientWriteError
は、このモックを使用してエラーハンドリングを検証- panicが発生せず、適切にエラーが返されることを確認
関連リンク
- Go言語 Issue #2382
- Go言語公式ドキュメント - Defer, Panic, and Recover
- Go言語公式ドキュメント - Errors are values
- Go言語 net/rpc パッケージ