LoginSignup
1
1

バグから学ぶSpring IntegrationのTcpConnectionInterceptorの仕組み

Last updated at Posted at 2023-05-06

お仕事でSpring Integrationを使う機会があり、古いバージョンから新しいバージョンへアップデートを試みた際に、旧バージョンで動いていた処理が新バージョンで動かない(=そもそも処理が呼び出されない)という事象にぶつかってしまいました。複数のマイナーバージョンを跨ぐアップデートだったのである程度破壊的な変更が含めれていることは覚悟していましたが、想定どおり動いているケースもあったので最初は理由がよくわかりませんでした。

謎事象が起こった仕組みは?

よくわからない事象が発生したのは、TCP通信処理に任意の処理を割り込ませることができる仕組み(TcpConnectionInterceptor)を使って処理を行っているところでした。

そもそもTCP通信処理ってどんな感じなの?

TcpConnectionInterceptorの話をする前にめっちゃ簡単にSpring IntegrationのTCP通信処理がどんな感じでメッセージの送信と受信を行っているのかを紹介しておきます。実際には複数の仕組みが存在しますが、ここでは代表的なパターンを一つ紹介しておきます。

以下はNIOを利用した時の処理フローの概要イメージになります。

image.png

メッセージの送信

メッセージの送信を行う際は、TcpSendingMessageHandler(TcpSenderを実装している)というコンポーネントを介してTcpConnectionsendメソッドを介してNIOのAPIを使ってデータを書き込みます。

メッセージの受信

メッセージの受信を行う際は、NIOのAPI経由で読み取ったデータをTcpListeneronMessageメソッドを介してTcpReceivingChannelAdapterへ渡され、その後はTcpReceivingChannelAdapterに紐付たチャネルへメッセージが連携されます。

TcpConnectionInterceptorって何もの?

TcpConnectionInterceptorは、ざっくりいうと・・・

  • 送信処理であればTcpSenderからTcpConnectionsendメソッドを呼び出した際に、TcpConnection本体のメソッドが呼び出される前に任意の処理を割り込みすることができる

  • 受信処理であればTcpConnectionからTcpListeneronMessageメソッドを呼び出した際に、TcpListener本体のメソッドが呼び出される前に任意の処理を割り込みすることができる

仕組みです。

NOTE:

正確にいうと・・・sendonMessage以外のメソッドにも割り込みが可能ですが、本投稿では意図的に割愛します。

image.png

どんな謎事象だった?

前提となる最低限の情報を共有したところで、今回発生した謎事象がどんなものだったかというと・・・・

  • TCPサーバ(=リッスンポートを立ち上げてクライアントからつないでもらうモード)で扱うTcpConnectionに複数のTcpConnectionInterceptorを適用していると送信処理にて最初のインタセプタしか適用されない

というものでした。具体的には以下のような処理フローになっていました。

NOTE:

この事象はSpring Integrationへバグレポート済み(#8609)で、次回の計画リリースでリリースされる予定です。

image.png

NOTE:

ここでは詳細は割愛しますが、TCPクライアント(=他のTCPサーバへつなぎにいくモード)の場合はTcpSenderで利用するTcpConnectionを決定する仕組みが異なるため本事象は発生しません。⇨ クライアントモード時は動いていたので・・・謎事象だと思ってしまいましたw

原因は?

通常はライブラリ利用者が気にする必要がない部分ですが、Spring Integrationはコネクション生成時に以下のような処理を行っています。

  • 接続処理を行いTcpConnection本体(例:TcpNioConnection)の生成
  • TcpConnectionInterceptorを生成し、オリジナルのTcpConnectionまたは他のインタセプタをラップ
  • 最後(=大外)のTcpConnectionInterceptorを介してTcpSenderを登録 → メソッドチェインにより各インタセプタにTcpSenderが登録される
  • 各インタセプタにTcpSenderを登録した後にTcpSenderで利用するTcpConnectionaddNewConnectionメソッドを介して(=コールバックして)伝播する

本来であれば、TcpSenderTcpSendingMessageHandler)には「Interceptor3」が伝播されるべきですが、「Interceptor1」が伝播される仕組みになっていたために本事象が発生していました。

image.png

修正方法は?

TcpSenderで利用するTcpConnectionaddNewConnectionメソッドを介して(=コールバックして)伝播する際に、チェイン先のaddNewConnectionメソッドにチェイン元のインスタンスを渡すように修正することで、TcpSenderTcpSendingMessageHandler)に「Interceptor3」を伝播することができます。

image.png

本事象の影響を受けるバージョン

おそらく以下のバージョンでこの事象が発生します。

  • Spring Integration 6.0.0 〜 6.0.5
  • Spring Integration 5.5.9 〜 5.5.17

修正されるバージョン

2023/5/17リリース予定の以下のバージョンで本事象が解消される予定です。

  • Spring Integration 6.1.0
  • Spring Integration 6.0.6
  • Spring Integration 5.5.18

バージョンアップせずに本事象を回避できないのか?

バージョンアップすることを推奨しますが・・・、修正版の正式バージョンリリース後であっても「何かしらの理由でSpring Integration自体のバージョンアップができない or したくない」が本事象は解消したい場合は、TcpSenderに「最後にインタセプタが適用されたコネクションを伝播するようなTcpConnectionInterceptor」を適用することで本事象を回避することができます。

image.png

NOTE:

具体的に以下のGiHubリポジトリにサンプル実装があるので参考にしてみてください。

参考

終わりに

久々にOSS活動して疲れましたw かなりサボっていたのでGWをきっかけにOSS活動も復活しよ〜

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1