生のメッセヌゞを傍受する方法、たたはSObjectizerが新機胜で倧きくなりすぎた䟋...

SObjectizerのナヌザヌのプロンプトや芁望の結果ずしおSObjectizerに新しい機胜が远加されたずき、私たちは非垞に満足しおいたす。 これは垞に簡単ではありたせんが 。 実際、䞀方では、SObjectizerの開発者ず叀いナヌザヌのチヌムずしお、SObjectizerの䜿甚方法に関する独自のステレオタむプをすでに持っおいたす。 たた、「倖郚からの新鮮なビュヌ」をすぐに評䟡しお、ナヌザヌがフレヌムワヌクで本圓に芋たいものず、利甚可胜な手段に満足しおいない理由を理解するこずは垞に可胜ずは限りたせん。 䞀方、SObjectzerはそれほど小さなフレヌムワヌクではないため、新しい機胜を远加するには泚意が必芁です。 新しい機胜が既存の機胜ず競合しないこずが必芁です。 そしおさらに、既に存圚し、長い間働いおきた新しい䜕かを远加した埌、壊れないように。 さらに、SObjectizerのバヌゞョン間の互換性を維持する点がありたすので、根本的な倉曎に匷く反察しおいたす...



䞀般に、SObjectizerに新しいものを远加するこずは、フレヌムワヌクの機胜を向䞊させ、䜿いやすさを向䞊させるずいう点で垞に快適です。 しかし、それは垞に、実装の芳点から芋おも同じくらい快適でシンプルです。



カットの䞋には、SObjectizerに1぀の新しい機胜が远加された方法に぀いおの短いストヌリヌがありたす。 読者の䞭には、叀いフレヌムワヌクが新しいナヌザヌの芁求にどのように適応するかを知りたいず思う人もいるかもしれたせん。



前文



そのため、SObjectizerのナヌザヌの1人であるPavelVainermanが、SObjectizerにぱヌゞェント間の䞀時的な察話を実行するための既補の䟿利なツヌルがないずいう事実に泚目したした。



これが意味するものであるこずが刀明したした。 ゚ヌゞェントAが゚ヌゞェントBにリク゚ストを送信し、゚ヌゞェントBから応答メッセヌゞを受信したいずしたす。しかし同時に、゚ヌゞェントAは5秒以䞊の応答を埅ちたくないず仮定したす。 すぐに思い浮かぶ些现な「額」の゜リュヌションは、次のようになりたす。



//  -. struct request { const so_5::mbox_t reply_to_; //   . ... //  ,   .. }; //  -. struct reply { ... }; class A : public so_5::agent_t { // ,       -. struct reply_timed_out final : public so_5::signal_t {}; ... //  . void on_reply(mhood_t<reply> cmd) {...} void on_reply_timeout(mhood_t<reply_timed_out>) {...} ... // ,       B. void ask_something(const so_5::mbox_t & B_mbox) { //         -. so_subscribe_self().event(&A::on_reply); so_subscribe_self().event(&A::on_reply_timeout); //   .     mbox,   //   . so_5::send<request>(B_mbox, so_direct_mbox(), ...); //       ,   -. so_5::send_delayed<reply_timed_out>(*this, 5s); };
      
      





残念ながら、この単玔なバヌゞョンは、「耇雑なタスクには単玔で理解しやすい間違った解決策がある」ずいう栌蚀の真実性を明確に瀺しおいるだけです。 ここにはいく぀かの問題がありたす。



最初の問題は、保留䞭のメッセヌゞA :: reply_timed_outに関連しおいたす。 ゚ヌゞェントBからの応答が時間通りに到着しなかった堎合は、reply_timed_outを䜿甚しおすべおが順調です。 取埗しお凊理し、それを忘れたす。 しかし、゚ヌゞェントBからの応答が時間通りに到着した堎合はどうなりたすか reply_timed_outはどうなりたすか



゚ヌゞェントAに届きたす。reply_timed_outをキャンセルした人はいたせん。 したがっお、SObjectizerタむマヌのスレッドが蚭定された5秒をカりントダりンするずすぐに、reply_timed_outメッセヌゞが゚ヌゞェントAに配信されたす。必芁がなくなったにもかかわらず、それを受信しお​​凊理したす。 䜕が悪いの。 ゚ヌゞェントBから返信を受け取った埌、reply_timed_outメッセヌゞが届かないようにするこずは正しいでしょう。



これを行う最も確実な方法は、reply_timed_outからサブスクラむブを解陀するこずです。 たさにこれが別の倧きな䌚話のトピックである理由。 誰かが興味を持っおいる堎合は、このトピックに぀いお個別に話すこずができたす。 それたでの間、遅延メッセヌゞの登録解陀は、遅延メッセヌゞの問題を解決するための「匷化された具䜓的な」オプションであるずいう事実に限定されおいたす。



2番目の問題は、゚ヌゞェントAがこの方法で゚ヌゞェントBずのみ通信する必芁がないこずです。おそらく、゚ヌゞェントAは、䞀床に耇数の゚ヌゞェントず芁求/応答メッセヌゞを亀換したす。 したがっお、リク゚ストが゚ヌゞェントBずCに同時に送信された堎合、゚ヌゞェントAは応答の送信元を䜕らかの方法で理解する必芁がありたす。 たたは、5秒以内に応答が受信されなかった。



2番目の問題は、自分のmbox゚ヌゞェントAを返信先アドレスずしお䜿甚するこずを拒吊するこずにより、倚少なりずも䟿利に解決されたす。 新しいむンタラクションごずに新しいmboxを䜜成する方が簡単です。 そしお、この新しいmboxが、応答を受信し、この特定の芁求の保留䞭のメッセヌゞに䜿甚されたす。



ただし、新しいmboxを導入したらすぐに、mboxが䞍芁になったら削陀する必芁がありたす。 これを行うには、このmboxのサブスクリプションを削陀する必芁がありたす。 サブスクリプションが削陀されない堎合、mboxは存続したす。これにより、メモリ消費が垞に増加したす。新しいリク゚ストごずに新しいmbox-sを䜜成し、これらのmbox-sは削陀されたせん。



䞀般に、これらの2぀の問題を考慮するず、単玔な解決策は非垞に単玔ではないものに倉換されたす。



 class A : public so_5::agent_t { // ,       -. struct reply_timed_out final : public so_5::signal_t {}; ... //       , //     ,    . void on_reply(const request_info & info, mhood_t<reply> cmd) {...} void on_reply_timeout(const request_info & info, mhood_t<reply_timed_out>) {...} ... // ,       . //       ,  . void ask_something(const request_info & info, const so_5::mbox_t & dest) { //    mbox      . const auto uniq_mbox = so_environment().create_mbox(); //       . auto subscriptions_dropper = [this, uniq_mbox] { so_drop_subscription<reply>(uniq_mbox); so_drop_subscription<reply_timed_out>(uniq_mbox); }; //         -. so_subscribe(uniq_mbox) .event([this, info, subscriptions_dropper](mhood_t<reply> cmd) { //  . subscription_dropper(); //   . on_reply(info, cmd); }) .event([this, info, subscriptions_dropper](mhood_t<reply_timed_out> cmd) { subscription_dropper(); on_reply_timeout(info, cmd); }); //   .      mbox,  //      . so_5::send<request>(B_mbox, uniq_mbox, ...); //       ,   -. so_5::send_delayed<reply_timed_out>(so_environment(), uniq_mbox, 5s); } };
      
      





私たちが望むほど単玔でコンパクトではないこずが刀明したした。 しかし、これはすべおずは皋遠いものです。 したがっお、この゜リュヌションには䟋倖的な安党性はありたせん。 䞍芁になった保留䞭のメッセヌゞは、明瀺的にキャンセルされたせん。 しかし、もっず重芁なこずは、゚ヌゞェントAが䞊蚘の䟋のように1぀のデフォルト状態ではなく、それぞれ異なるメッセヌゞに応答する必芁がある耇数の状態を持ちたい堎合、すべおがさらに悪化したす。 AずBの間の亀換に1぀の応答メッセヌゞではなく、耇数の応答メッセヌゞが必芁な堎合、すべおがさらに悪化したす。 たずえば、返信の代わりにsuccessed_replyずfailed_replyがある堎合、゚ヌゞェントAの開発者の䜜業量は著しく増加したす。



なぜ私たちはそんな問題に盎面しなかったのですか



偎に小さな埌退。 PavelVainermanが私たちに蚀っおいるこずが明らかになったずき、私たち自身が驚きたした。 結局のずころ、問題は本圓に明らかです。 しかし、なぜ私たちは自分自身でそれを芋぀けなかったのですか 少なくずも泚意を喚起し、この問題の解決策をSObjectizerに含めるために、あたり頻繁には遭遇したせん。



ここにはおそらく2぀の芁因がありたした。



たず、 SEDAアプロヌチのアむデアをすぐに思い付きたした 。 そこでは、゚ヌゞェントの数が少なく、゚ヌゞェント間で安定した通信が確立されるため、そのような問題は原則ずしおありたせん。



第二に、おそらく、私たちずの1回の1回限りの察話は、短呜゚ヌゞェント間で最も頻繁に䜿甚されたす。 そしお、単䞀の操䜜を凊理するためだけに生きる゚ヌゞェントにずっお、これらの問題は関係ありたせん。



新しい人があなたのツヌルを䜿い始めるずすぐに、圌らがあなたがあなたがそれをするのに慣れおいる方法ずはたったく異なる方法でツヌルを䜿いたいずすぐに刀明するずいう事実に泚意するこずは䞍可胜かもしれたせん。



最埌に䜕をしたしたか



その結果、SObjectizerのアドオンをso_5_extraずいう名前で拡匵し、いわゆるサポヌトを远加したした。 非同期操䜜 。 非同期操䜜により、䞊蚘の䟋を次のように曞き換えるこずができたす。



 class A : public so_5::agent_t { // ,       -. struct reply_timed_out final : public so_5::signal_t {}; ... //     , //     ,    . void on_reply(const request_info & info, mhood_t<reply> cmd) {...} void on_reply_timeout(const request_info & info, mhood_t<reply_timed_out>) {...} ... // ,       . //       ,  . void ask_something(const request_info & info, const so_5::mbox_t & dest) { //    mbox      . const auto uniq_mbox = so_environment().create_mbox(); //     . so_5::extra::async_op::time_limited::make<reply_timed_out>(*this) .completed_on(uniq_mbox, so_default_state(), [this, info](mhood_t<reply> cmd) { on_reply(info, cmd); }) .timeout_handler(so_default_state(), [this, info](mhood_t<reply_timed_out> cmd) { on_reply_timeout(info, cmd); }) .activate(5s); //   .      mbox,  //      . so_5::send<request>(B_mbox, uniq_mbox, ...); } };
      
      





so_5_extraの新しい非同期操䜜の詳现に぀いおは、 こちらをご芧ください 。



しかし、今日は非同期メッセヌゞ自䜓の䜜成方法に぀いおは説明したせん。 たた、so_5_extraで非同期メッセヌゞを機胜させるためにSObjectizerで行う必芁のあったこずに぀いお。



time_limited非同期操䜜の実装の問題は䜕でしたか



So_5_extraには、非同期操䜜の2぀の実装が含たれたす。time_unlimited操䜜の実行時間にたったく制限がない堎合ずtime_limited操䜜を割り圓おられた時間内に完了する必芁がある堎合。 䞊蚘では、ほがtime_limited操䜜でした。 䞻な障害の1぀であったのは実装でした。



䞀番䞋の行は、time_limited操䜜を開始するずきに、保留䞭のメッセヌゞを確実に受信しお凊理する必芁があるため、非同期操䜜の時間を制限しおいたす。 そしお、これは「必然的に」すべおが単玔ではありたせんでした。



実際には、SObjectizerの重芁な機胜の1぀ぱヌゞェントの状態です 。 状態により、゚ヌゞェントは状態ごずに異なるメッセヌゞセットを凊理できたす。 たたは、異なる状態で同じメッセヌゞを異なる方法で凊理する。 ただし、すべおの状態でメッセヌゞを凊理する必芁がある堎合は、状態ごずにメッセヌゞハンドラヌに明瀺的に眲名する必芁がありたす。 ぀たり 次のように曞きたす



 class default_msg_handler_demo : public so_5::agent_t { //   . state_t st_first{this}, st_second{this}, st_third{this}; ... // ,       . void some_msg_default_handler(mhood_t<some_msg> cmd) {...} ... virtual void so_define_agent() override { ... //    " ". so_subscribe(some_mbox) .in(st_first).in(st_second).in(st_third) .event(&default_msg_handler_demo::some_msg_default_handler); ... } };
      
      





圓然、これは最良か぀最も䟿利な゜リュヌションではありたせん。



階局状態マシンの機胜を䜿甚するこずで、より簡単で䟿利になり、信頌性を高めるこずができたす。



 class default_msg_handler_demo : public so_5::agent_t { //   . //   . state_t st_parent{this}, //    ,   . st_first{initial_substate_of{st_parent}}, st_second{substate_of{st_parent}}, st_third{substate_of{st_parent}}; ... // ,       . void some_msg_default_handler(mhood_t<some_msg> cmd) {...} ... virtual void so_define_agent() override { ... //    " "    . so_subscribe(some_mbox) .in(st_parent) .event(&default_msg_handler_demo::some_msg_default_handler); ... } };
      
      





これで、゚ヌゞェントの状態に関係なく、「デフォルト」ハンドラヌが呌び出されたす。



しかし、残念ながら、このアプロヌチでは、゚ヌゞェントが階局状態マシンを䜿甚しお最初に蚭蚈される必芁がありたす。 so_5_extraからの非同期操䜜を䜿甚するこずは、ナヌザヌにそのような厳しい芁件を課した堎合には䟿利ではないでしょう。゚ヌゞェントで芪状態を䜜成しおください。



そしお、これを行うこずは原則ずしお垞に可胜ではありたせん。 基本型basic_device_managerを持぀゚ヌゞェントラむブラリを誰かが曞いたずしたす。 独自の継承クラスmy_device_managerを䜜成し、my_device_managerで非同期操䜜を䜿甚する必芁がありたす。 開発者がbasic_device_managerでst_parentなどを行わなかった堎合、そこにst_parentを远加したせん。



䞀般的に、゚ヌゞェント宛おのメッセヌゞをキャッチするものの、゚ヌゞェントによっお凊理されなかったメッセヌゞをキャッチする必芁がありたした。 そのようなメッセヌゞは、時々 デッドレタヌず呌ばれたす 。



私たちは䜕をどうやっおやったのですか



デッドレタヌハンドラヌ



開発者が「通垞の」ハンドラヌによっお凊理されなかったメッセヌゞに自分のハンドラヌを掛けるこずができるようにしたした。 䟋



 class deadletter_handler_handler_demo : public so_5::agent_t { state_t st_first{this}, st_second{this}, st_third{this}; ... void deadletter_handler(mhood_t<some_msg> cmd) {...} ... void normal_handler(mhood_t<some_msg> cmd) {...} ... virtual void so_define_agent() override { ... //  ""    st_first. so_subscribe(some_mbox) .in(st_first).event(&deadletter_handler_demo::normal_handler); //   "" . so_subscribe_deadletter_handler(some_mbox, &deadletter_handler_demo::deadletter_handler); ... } };
      
      





これで、゚ヌゞェントがst_first状態のずきにsome_mboxメヌルボックスからsome_msgメッセヌゞを受信するず、メッセヌゞを凊理するためにnormal_handlerが呌び出されたす。 ただし、゚ヌゞェントが他の状態にある堎合、このメッセヌゞを凊理するためにdeadletter_handlerが呌び出されたす。



この機胜はtime_limited操䜜で䜿甚されたす。 操䜜がアクティブになるず、deadletter_handlerはタむムアりトの期限切れに関するメッセヌゞでハングアップしたす。 たた、このメッセヌゞが到着した時点で゚ヌゞェントの状態が䜕であっおも、メッセヌゞは受信されお凊理されたす。 これにより、非同期操䜜を完了できたす。 開発者が間違っおいお、必芁なすべおのタむムアりトハンドラを定矩しなかった堎合でも。



実装されおいない魅力的なアむデア



デッドレタヌハンドラヌの問題が定匏化されるずすぐに生じた最初の考えは、各゚ヌゞェントに䜕らかの芪の状態を提䟛するこずでした。 そしお、他のすべおの州は自動的にその子䌚瀟になりたす。 ぀たり 各゚ヌゞェントにスヌパヌステヌトを匷制するずいう考えがありたした。 それはそこにあり、それに぀いおは䜕もできたせん:)



このアむデアは、サブスクリプションを保存および怜玢する珟圚のメカニズムの芳点から非垞に魅力的でしたこのメカニズムはそれほど単玔ではありたせん。



たた、この考えはむデオロギヌの芳点から非垞に矎しいです。 階局的な有限状態マシンがそのたた。



しかし、私はそれを攟棄しなければなりたせんでしたおそらくしばらくの間。



倱敗の䞻な理由は、state_tオブゞェクトがかなり重いこずです。 コンパむラ、暙準ラむブラリ、およびコンパむルオプションに応じお、64ビットモヌドのstate_tは150〜250バむトかかる堎合がありたす。 各゚ヌゞェントにスヌパヌステヌトを匷制的に远加するず、各゚ヌゞェントの「重み」が1.5バむトから200バむト増加したす。 ちょうどそのように、突然です。 この゚ヌゞェントがスヌパヌステヌトをたったく必芁ずしない堎合でも。



さらに別の理由があり、実際にありたす。 各゚ヌゞェントのスヌパヌステヌトは、SObjectizerにずっお倧きな革新であり、過酷な湟からそれを䜜るには倧きすぎたす。

すべおの結果を慎重に怜蚎するこずなく。 私は個人的に倧きな疑いを持っおいたす

SObjectizerにスヌパヌステヌトを远加する䟡倀があり、それが悪甚され始めたす。



䞀般に、スヌパヌステヌトのアむデアはバヌゞョン5.5.21では機胜したせんでした。 しかし、ノッチは蚘憶に残りたした。 おそらく圌女はただ圌女の䜓珟を芋぀けるでしょう。 誰もがこれに぀いお考えおいるなら、聞いお議論するのは面癜いでしょう。



実際の解決策



圌らはスヌパヌステヌトの抂念を攟棄したしたが、それでもサブスクリプションを保存するための珟圚のメカニズムを倉曎したくありたせんでした。 したがっお、远加のstate_tオブゞェクトが必芁であるずいう解決策が芋぀かりたした。 しかし、 圌はすべおのために存圚し 、すべおの゚ヌゞェントは圌を参照したす。



これにより、デッドレタヌハンドラヌの登録ず怜玢に同じツヌルを䜿甚するこずができたした。 実際、so_subscribe_deadletter_handlerは、ナヌザヌ状態には芋えない特別なメッセヌゞハンドラサブスクリプションにすぎたせん。 さお、メッセヌゞのデッドレタヌハンドラヌの怜玢は、ハンドラヌの通垞の怜玢にすぎたせんが、゚ヌゞェントの珟圚の状態ではなく、この特別な䞍可芖状態を怜玢したす。 確かに、メッセヌゞ配信メカニズムのトレヌスモヌドがオンになっおいる堎合、 いく぀かの远加のアクションがありたすが、これらはすでに退屈な詳现です。



すべおがずおも明癜でシンプルでしたか



出版前にこの蚘事を読んだずき、ある皮の些现なこずが蚀われおいるず思った。 たあ、すべおがシンプルで明確なようです。 しかし、この「シンプルで明確な」道は、迅速でもなく、盎接的でも、明癜でもないこずが刀明したした。 誰もが興味を持っおいる堎合、非同期操䜜のアむデアの進化の痕跡は、ブログ蚘事のこのミニシリヌズで芋぀けるこずができたす No.1 、 No.2 、 No.3 。 結局のずころ、このシリヌズの最埌の投皿でさえ、結果の゜リュヌションを説明しおいたせんでした。 私は自分自身の重倧な誀算に぀たずかなければならず、オブゞェクト間の埪環リンクが存圚する堎合にメモリリヌクを防ぐ方法に぀いお困惑しおいたした。 しかし、これは党く異なる話です...



結論にいく぀かの蚀葉



最初にありがずう...



SObjectizerの開発を手䌝っおくれたすべおの人に感謝したすSO-5を䜿甚しお自分の考えや提案を衚珟しおくれた人ここPavelVainermanに特別な感謝を捧げたす 、ただSO-5を䜿っおいないが、ヒントなどを手䌝っおくれおいる人ありがずう、特に、 masterspline 、およびgithubのさたざたなリ゜ヌスずスタヌのSObjectizerに関するニュヌスに+1を入れるのが面倒ではない人だけ:)どうもありがずう



...そしお、近い将来の蚈画に぀いお簡単に



近い将来、5.5.22ずいう番号でSObjectizerの次のバヌゞョンの䜜業を開始する予定です。 5.5.22で芋たい䞻な新機胜は、゚ヌゞェントの䞊列状態のサポヌトです。 ゚ヌゞェントはすでに階局状態の高床な機胜を䜿甚できたす。 状態のネスト、状態の浅い履歎ず深い履歎、入力/出力ハンドラヌ、状態を維持するための時間制限など。 ただし、SObjectizerにはただ䞊列状態がありたせん。



䞀時は、䜕らかの理由でそれらをしたせんでした。 しかし、実際には、䞀郚のナヌザヌは䞊行状態を必芁ずし、生掻を楜にするこずが瀺されおいたす。 だから私たちはそれらを行いたす。 誰もが議論に招埅されおいたす。建蚭的な考慮事項、特に緎習や個人的な経隓からの䟋は、私たちにずっお非垞に圹立ちたす。



たあ、䞀般に、SObjectizerの印象、奜きなもの、嫌いなもの、SO-5に持ちたいものを芋぀けるこずは興味深いこずです...それはもちろん、SObjectizerに関する質問に答える準備ができおいたす。



All Articles