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

[インデックス 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言語では、エラーハンドリングに関して明確な哲学があります:

  1. 通常のエラー処理:期待される可能性のあるエラー(ネットワーク障害、入力エラー等)は、明示的なエラー戻り値として処理する
  2. panic処理:プログラムが継続できない例外的な状況(プログラミングエラー、不変条件の違反等)でのみ使用する

RPCとは

RPC(Remote Procedure Call)は、ネットワーク上の別のコンピューターにある関数を、ローカルの関数と同じように呼び出すことができる技術です。Go言語のRPCパッケージは、この機能を実装しています。

RPCにおいて、以下のような問題が発生する可能性があります:

  • ネットワーク接続の切断
  • サーバーの応答停止
  • データのエンコード/デコードエラー

技術的詳細

修正前の問題

修正前のコードでは、client.gosendメソッドで以下のような処理が行われていました:

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()
}

この変更により:

  1. エラーがCall構造体のErrorフィールドに保存される
  2. c.done()が呼ばれることで、呼び出し元にエラーが通知される
  3. アプリケーションがクラッシュすることなく、適切にエラーを処理できる

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

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型も定義されました。

コアとなるコードの解説

エラーハンドリングメカニズム

修正により、以下のエラーハンドリングフローが確立されました:

  1. エラーの発生client.codec.WriteRequestでエラーが発生
  2. エラーの記録c.Error = errでCall構造体にエラーを保存
  3. 完了通知c.done()でDoneチャンネルに通知を送信
  4. 呼び出し元への返却:呼び出し元でエラーを検出し、適切に処理

Call構造体の役割

Call構造体は、RPC呼び出しの状態を管理する重要な要素です:

  • ServiceMethod:呼び出すサービスとメソッド名
  • Args:引数
  • Reply:戻り値
  • Error:エラー情報
  • Done:完了を通知するチャンネル

テストケースの意義

追加されたテストケースは、書き込みエラーが発生した際の動作を検証します:

  1. writeCrasher型は、常に書き込みエラーを返すモックオブジェクト
  2. TestClientWriteErrorは、このモックを使用してエラーハンドリングを検証
  3. panicが発生せず、適切にエラーが返されることを確認

関連リンク

参考にした情報源リンク