Amqp: チャネルには操作ごとのタイムアウトがありません

作成日 2017年07月13日  ·  22コメント  ·  ソース: streadway/amqp

やあ、

Connection.Channel()バグを見つけたと思います。通話が無期限にブロックされることがあります。

私のシナリオ:
バーチャルホストにキューがあります。 私はこのキューで消費し始めます。
次に、RabbitMQWebインターフェイスから仮想ホストを削除します。 明らかに、チャネルは閉じています。

私のコードでは、無限ループで再試行して新しいチャネルを開き、このチャネルで消費します。
Consume()はエラーを返します:
Exception (403) Reason: "ACCESS_REFUSED - access to queue 'xxxx' in vhost 'xxxx' refused for user 'guest'" (予想される)
数秒後、私のプログラムは無期限にブロックされます。

Webインターフェイスに接続が表示されなくなったため、RabbitMQサーバーがその側から接続を閉じたと思います。
ログには何も関係ありません。

ゴルーチンのスタックトレースは次のとおりです。

goroutine 34 [select]:
github.com/streadway/amqp.(*Channel).call(0xc42025b8c0, 0xa43020, 0xc4202690b0, 0xc42044bd70, 0x1, 0x1, 0x1, 0xc4203074a0)
    github.com/streadway/amqp/channel.go:176 +0x198
github.com/streadway/amqp.(*Channel).open(0xc42025b8c0, 0xc42025b8c0, 0x0)
    github.com/streadway/amqp/channel.go:165 +0xb3
github.com/streadway/amqp.(*Connection).openChannel(0xc420295a40, 0xc420024600, 0x77e7a0, 0x8a2480)
    github.com/streadway/amqp/connection.go:624 +0x55
github.com/streadway/amqp.(*Connection).Channel(0xc420295a40, 0xa42820, 0xc42033b4c0, 0xc420295a40)
    github.com/streadway/amqp/connection.go:646 +0x2b
bug

最も参考になるコメント

こんにちは! 私はほとんど同じ問題を抱えています。 しかし、それは仮想ホストの削除とは関係がないようです。
これが私の環境です:RabbitMQ 3.6.10、Erlang 19.3、トピック交換。

メッセージを公開したいのですが、connection.Channel()を呼び出すたびに、プログラムがgithub.com/streadway/amqp/channel.go:176 +0x44aハングします
このバグは不安定です。調査してください。 176行目は修正が必要な場所ではなく、他の場所でエラーが発生している可能性があります。

ちなみにRabbitMQ 3.6.6, Erlang 19.2ドライバーは問題なく動作します。

全てのコメント22件

より詳しい情報:

  • 1.8.3 /1.9ベータ2に移動
  • streadway / amqpの最新バージョン
  • RabbitMQ 3.6.10

RabbitMQ 3.6.xは、削除された仮想ホストへの接続を閉じません(3.7.0は閉じます)。 したがって、操作を実行しようとしない限りクライアントに通知されないので、同様のことが予想されます。
どの程度正確にエラーが発生するかはまだわかりません。

コードまたは複製に使用できる小さなスニペットを投稿してください。
トラフィックキャプチャは非常に役立ちます。

クライアントはサーバーによって開始された接続の閉鎖を監視するため、これは3.7.0では問題になりません。

クライアントは<-ch.errorsからの選択でブロックされます。この場合、通知は送信されません。

func (ch *Channel) call(req message, res ...message) error {
    if err := ch.send(req); err != nil {
        return err
    }

    if req.wait() {
        select {
        case e, ok := <-ch.errors:

他のクライアント(Java、.NET、Ruby)には、このようなRPC操作のタイムアウトがあります。 @gerhardここでタイムアウトをどのように導入する必要があると思いますか?

selectに3番目の句を追加できますか?

後でコードを送信します。

私は一緒に行きます:

func (ch *Channel) call(req message, res ...message) error {
    if err := ch.send(req); err != nil {
        return err
    }

    if req.wait() {
        select {
        case e, ok := <-ch.errors:
            if ok {
                return e
            }
            return ErrClosed

        // If there is no response within a sensible time period, consider the channel closed
        case <-time.After(time.Second * 30):
            return ErrClosed

@pierrre上記の@gerhardのアイデアを使用したPRを提出することに興味がありますか? できます
賢明なタイムアウトを提案します。このライブラリで使用されるデフォルトのハートビートタイムアウト+ 1〜5秒。 他のクライアントでは30秒から60秒まで変化します(デフォルトのハートビート間隔は60秒です)が、最終的にはハートビートメカニズムがそれを決定するので、最終的には他のクライアントにぶつかると思います。

@michaelklishinに同意しました:

  • タイムアウトをハートビート間隔に設定します
  • ErrChannelOperationTimeoutまたはOperationTimeout導入するかもしれません。 RubyとJavaではTimeoutExceptionを使用します

こんにちは! 私はほとんど同じ問題を抱えています。 しかし、それは仮想ホストの削除とは関係がないようです。
これが私の環境です:RabbitMQ 3.6.10、Erlang 19.3、トピック交換。

メッセージを公開したいのですが、connection.Channel()を呼び出すたびに、プログラムがgithub.com/streadway/amqp/channel.go:176 +0x44aハングします
このバグは不安定です。調査してください。 176行目は修正が必要な場所ではなく、他の場所でエラーが発生している可能性があります。

ちなみにRabbitMQ 3.6.6, Erlang 19.2ドライバーは問題なく動作します。

@shilkinが他の人に調査を依頼するのは、オープンソースソフトウェアがどのように機能するかではありません。 この特定の問題とは何か、他のクライアントがどのように対処するかについて説明しました。 仮想ホストの削除は、1つの特定のシナリオにすぎません。 このクライアントで同じことができるおおよそのスニペットさえあります。 これが差し迫った問題である場合は、遠慮なくPRを提出してください。

これは、今日のメンテナコールで@gerhardと@streadwayと話し合われました。 私たちは同意しました

  • 一時的な解決策は、上記の@gerhardのスニペットのように強制されるタイムアウトです。
  • 最終的にcontextはこれに対処する正しい方法です

ハートビートとチャネル操作の両方に構成可能なタイムアウトが1つあるか、2つのタイムアウト(ある意味で相互に関連している)があるかは未定です。 FWIW他のいくつかのクライアントは、個別のタイムアウトを使用します。

@michaelklishin返信ありがとうございます。 何が悪いのか理解しようと思います。

本番環境でも同じ問題が発生していますが、この問題がいつ解決されるかについての見積もりはありますか?

@coyle誰かが修正を提供し、私たちがそれを受け入れるとき。 上記のスニペットは、大まかに何をすべきか(または少なくとも他のいくつかのクライアントが何をするか)を示しています。 それをプルリクエストに自由に変換して、ワークロードでテストしてください。

やあ、
このためのPRを作成しました(基本的に、チャネルrpc操作のタイムアウトを追加します)。 しかし、これを単独で行うと、他の副作用が発生する可能性があるのではないかと今考えています。

タイムアウトのために上記の選択ループを終了し、この時点の直後に応答が到着した場合、 channel:rpcで選択が再度行われるまで(別の呼び出しが原因で)、 channel:dispatch ()を呼び出すgoルーチンがハングします。今は古い応答を取得します)。 ですから、ここでやらなければならないことはおそらくもっとあると思います。 コードをもう一度調べます。 それまでの間、これについてコメント/提案があれば、共有してください。

@ponumapathyこれを調べてくれてありがとう。 他のクライアントは基本的に遅い応答を破棄します(必ずしも適切にではありません)。 コンテキストを使用しないプロジェクトの場合、Goでこれを行うための最良の方法を判断するのに十分な経験があるとは限りません。

@streadwayと私は、今日のメンテナのたまり場でこれについて話し合いました。 https://github.com/streadway/amqp/issues/278#issuecomment -315144664のスニペットは見栄えが良いですが、明示的なタイマー処理が必要です。そうしないと、ホットコードパスで微妙なメモリリークが発生します。

PRのまとめを見ていきます。

もちろん。 私はこれらの行で何かを考えていました(これは現在私のフォークにあります。必要に応じてPRを作成できます):
(ここに差分を貼り付けるとフォーマットに問題があります。そのため、できるだけ読みやすいようにフォーマットします)

\-\-\- a/channel.go
+++ b/channel.go
@@ -9,7 +9,6 @@ import (
"reflect"
"sync"
"sync/atomic"
- "time"
)

@@ \-30,7 +29,6 @@ type Channel struct {
m sync.Mutex // struct field mutex
confirmM sync.Mutex // publisher confirms state mutex
notifyM sync.RWMutex
- rpcM sync.Mutex

@@ \-170,11 +168,6 @@ func (ch *Channel) open() error {
// Performs a request/response call for when the message is not NoWait and is
// specified as Synchronous.
func (ch *Channel) call(req message, res ...message) error {
- if req.wait() {
- ch.rpcM.Lock()
- defer ch.rpcM.Unlock()
- }
-
if err := ch.send(req); err != nil {
return err
}
@@ -204,15 +197,6 @@ func (ch *Channel) call(req message, res ...message) error {
// error on the Connection. This indicates we have already been
// shutdown and if were waiting, will have returned from the errors chan.
return ErrClosed
- case <-time.After(ch.connection.Config.Heartbeat):
- ch.transition((*Channel).recvMethod)
- // drain any stale response that might have arrived before the state transition happened
- select {
- case <-ch.rpc:
- case <-time.After(time.Millisecond):
- }
-
- return ErrChannelOpTimeout
}
}

#304で対処。

ありがとう ! 👍

こんにちは、この修正後のセットアップに関するいくつかの所見に言及したいと思います。

このクライアントのデフォルトのハートビート値は10秒であるため、チャネル操作は断続的なネットワークのラグ/エラーや、rabbitmqサーバーがビジーである可能性がある時間帯により敏感であることがわかります。

具体的には、多数のチャネルを作成しているときにチャネルタイムアウトが発生することがあります(公開ごとにチャネルを作成して閉じるのではなく、公開中に再利用可能なチャネルのプールを使用するように最適化しようとしています)。

この動作はこの修正後の新しいものであるため、これを取り上げたかっただけです(頻繁に発生することではありませんが)。 この目的のために、別のより高いタイムアウト値を使用する必要があるかどうか疑問に思っています。

引数の反対側は、クライアントがユースケース/環境に適切なハートビート値を設定することです(これは、これから行うことです)。 それが前進する方法である場合、ドキュメントはおそらくそれを明示的に呼び出す必要があります。

これについてのあなたの考えを教えてください。

やあ、
上記の動作のコンテキストで、マージされた修正を確認していましたが、マージされた修正には、最初に説明した修正と同じ問題があると思います-https://github.com/streadway/amqp/issues/278 # issuecomment

マージされた修正では、RPC応答フレームタイプが「メソッド」ではなく「ヘッダー」タイプになるという誤った仮定をしました。 そのため、タイムアウトの場合、状態遷移は、そのチャネルの「メソッド」フレームのみを予期するように行われました(ヘッダーフレームがそれより遅く到着した場合、ヘッダーフレームをドロップする効果があります)。 ただし、RPC応答フレームタイプも「メソッド」フレームになります。 したがって、そのコードは本質的にその点で何の操作もしません。

最初に説明したタイマーリークが存在し、デフォルトのタイムアウト値が10秒であるため、マージを元に戻す必要があるかどうか疑問に思っています。これは、より頻繁に発生する可能性があります。

ところで、これについては申し訳ありません。 でも、後でではなく今、これに気づいたのは良かったと思います。 :)

こんにちは仲間、この問題に何か改善はありますか? 私は同じ状況で立ち往生しているので。

このページは役に立ちましたか?
0 / 5 - 0 評価