SObjectizer-5.5.23の新機能:ウィッシュフルフィルメントまたはPandora's box?





この記事は1か月前に公開された「 古いフレームワークに新しい機能を追加するのは簡単ですか?SObjectizer開発の例に基づいた選択の苦痛 」のリフレクション記事の続きです 。 その記事では、SObjectizerの次のバージョンで解決したい問題について説明し、そのソリューションに対する2つのアプローチを検討し、各アプローチの長所と短所をリストしました。



時間が経つにつれて、アプローチの1つが実装され、 SObjectizerの新しいバージョンと、すでに「 深呼吸 」と呼ばれている付随プロジェクトso_5_extraが実装されました。 あなたは文字通り取り、試すことができます。



今日は、何が行われたのか、なぜ行われたのか、何につながったのかについてお話します。 C ++向けの数少ないライブ、クロスプラットフォーム、オープンアクターフレームワークの1つがどのように開発されているかを知りたい場合は、catを歓迎します。



どのようにすべてが始まりましたか?



それはすべて、タイマーの保証されたキャンセルの問題を解決する試みから始まりました。 問題の本質は、遅延または定期的なメッセージが送信されたときに、プログラマーがメッセージの配信をキャンセルできることです。 例:



auto timer_id = so_5::send_periodic<my_message>(my_agent, 10s, 10s, ...); ... // - . // ,    my_message    . timer_id.release(); //      my_message.
      
      





timer_id.release()を呼び出した後タイマーはmy_messageメッセージの新しいインスタンスを送信しなくなります。 ただし、すでに送信済みで受信者のキューにあるコピーはどこにも移動しません。 時間が経つにつれて、これらは同じキューから抽出され、処理のために受信者エージェントに転送されます。



この問題は、SObjectizer-5の動作の基本原理の結果であり、SObjectizerがキューからメッセージを抽出できないという事実のため、単純な解決策はありません。 SObjectizerのキューはディスパッチャに属し、ディスパッチャは異なるため、キューも異なるように編成されているため、できません。 SObjectizerの一部ではないディスパッチャがあり、SObjectizerは原則としてこれらのディスパッチャがどのように機能するかを知ることができません。



一般に、ネイティブSObjectizerタイマーにはそのような機能があります。 それが開発者の生活を過度に損なうわけではありません。 ただし、特別な注意が必要です。 特に、フレームワークに精通したばかりの初心者向けです。



そして最後に、この問題の解決策を提供するために手が届きました。



どのソリューションパスが選択されましたか?



前の記事では 、2つの可能なオプションが検討されました。 最初のオプションでは、SObjectizerのメッセージ配信メカニズムを変更する必要はありませんでしたが、プログラマーが送受信するメッセージのタイプを明示的に変更する必要がありました。



2番目のオプションでは、SObjectizerメッセージ配信メカニズムの変更が必要でした。 メッセージが特定の方法で送信されたという事実をメッセージの受信者から隠すことが可能になったため、このパスが選択されました。



SObjectizerの変更点は何ですか?



新しいコンセプト:メッセージが入った封筒



実装されたソリューションの最初のコンポーネントは、SObjectizerにエンベロープなどの概念を追加することです。 エンベロープは特別なメッセージであり、その中には現在のメッセージ(ペイロード)があります。 SObjectizerは、ほぼ通常の方法で受信者にメッセージ付きのエンベロープを配信します。 エンベロープ処理の基本的な違いは、配信の最後の段階でのみ検出されます。





ここには、メッセージエンベロープを使用できる理由と方法に大きな影響を与える2つの重要なポイントがあります。



最初の重要な点は、メッセージハンドラーが受信者で見つかった場合にのみ、エンベロープからメッセージが要求されることです。 つまり メッセージが実際に受信者に配信され、受信者がここにいる場合にのみ、このメッセージを処理します。



ここでの2番目の重要な点は、エンベロープがその中のメッセージを漏らさない可能性があることです。 つまり、たとえば、エンベロープは現在の時刻を確認し、すべての配信日付が欠落していると判断できるため、メッセージは関連性がなくなり、処理できなくなります。 したがって、エンベロープはメッセージを出力しません。 したがって、SObjectizerはこのエンベロープを単に無視し、追加のアクションを実行しません。



封筒はどのようなものですか?



エンベロープは、envelope_tインターフェイスの実装であり、次のように定義されます。



 class SO_5_TYPE envelope_t : public message_t { public: ... // -. //   ,       //    . virtual void handler_found_hook( handler_invoker_t & invoker ) noexcept = 0; //   ,      //     . virtual void transformation_hook( handler_invoker_t & invoker ) noexcept = 0; private : kind_t so5_message_kind() const noexcept override { return kind_t::enveloped_msg; } };
      
      





つまり 封筒は、基本的に他の全員と同じメッセージです。 ただし、so5_message_kind()メソッドによって返される特別な属性を使用します。



プログラマは、envelope_t(または、より便利なことに、 so_5 :: extra :: enveloped_msg :: just_envelope_t )から継承し、フックメソッドhandler_found_hook()およびtransformation_hook()をオーバーライドして、エンベロープを開発できます。



フックメソッド内で、エンベロープ開発者は、処理/変換のためにエンベロープ内のメッセージを提供するかどうかを決定します。 必要に応じて、開発者はinvoke()メソッドとinvokerオブジェクトを呼び出す必要があります。 望んでいない場合、彼は電話しません。この場合、封筒とその内容は無視されます。



エンベロープはタイマーをキャンセルする問題をどのように解決しますか?



so_5 :: extra :: revocable_timer名前空間の形式でso_5_extraに実装されたソリューションは非常に簡単です:遅延または定期的なメッセージが特別に送信されると、メッセージ自体だけでなくアトミックフラグも取り消される特別なエンベロープが作成されます。 このフラグがクリアされている場合、メッセージは関連があると見なされます。 設定すると、メッセージは取り消されたと見なされます。



フックメソッドがエンベロープで呼び出されると、エンベロープは失効したフラグの値をチェックします。 フラグが設定されている場合、エンベロープはメッセージを出力しません。 したがって、タイマーが既に受信者のキューにメッセージを入れることができたとしても、メッセージは処理されません。



インターフェース拡張abstract_message_box_t



envelope_tインターフェースの追加は、SObjectizerでのエンベロープの実装のほんの一部です。 2番目の部分では、SObjectizer内のメッセージ配信メカニズムにエンベロープが存在するという事実を考慮しています。



ここでは、残念ながら、ユーザーに変更を表示せずにはできませんでした。 特に、SObjectizerのすべてのメールボックスのインターフェースを定義するabstract_message_box_tクラスでは、別の仮想メソッドを追加する必要がありました。



 virtual void do_deliver_enveloped_msg( const std::type_index & msg_type, const message_ref_t & message, unsigned int overlimit_reaction_deep );
      
      





このメソッドは、タイプmsg_typeのメッセージを含むメッセージエンベロープを受信者に配信します。 このような配信は、mboxの種類によって実装の詳細が異なる場合があります。



do_deliver_enveloped_msg()をabstract_message_box_tに追加するときに、純粋な仮想メソッドにするか、何らかのデフォルトの実装を提供するかを選択しました。



do_deliver_enveloped_msg()を純粋な仮想メソッドにすると、ブランチ5.5のSObjectizerのバージョン間の互換性が失われます。 結局、独自のmbox実装を作成したユーザーは、SObjectizer-5.5.23に切り替えるときに独自のmboxを変更する必要があり、そうしないと、新しいバージョンのSObjectizerでコンパイルできませんでした。



これが欲しくなかったので、v.5.5.23ではdo_deliver_enveloped_msg()を純粋な仮想メソッドにしませんでした。 例外をスローするだけのデフォルトの実装があります。 したがって、カスタムユーザーmbox-sは通常のメッセージで正常に動作を継続できますが、封筒の受け入れを自動的に拒否します。 この振る舞いがより受け入れられることがわかりました。 さらに、初期段階では、メッセージを含むエンベロープが広く使用される可能性は低く、「ワイルドな」SObjectizer mboxesのカスタム実装が頻繁に見つかることはほとんどありません;)



さらに、ブランチ5.5との互換性を考慮しないSObjectizerの後続のメジャーバージョンでは、abstract_message_box_tインターフェイスが大幅に変更される可能性はほとんどありません。 しかし、私たちはすでに自分よりも先に進んでいます...



メッセージ付きの封筒を送信する方法



SObjectizer-5.5.23自体は、エンベロープを送信する簡単な手段を提供しません。 特定のタイプの封筒と便利なツールが、特定のタイプの封筒を便利に送信するための特定のタスクのために開発されていると想定されます。 この例はso_5 :: extra :: revocable_timerで見ることができます。ここでは、エンベロープを送信するだけでなく、ユーザーに特別なtimer_idも与える必要があります。



より簡単な状況では、 so_5 :: extra :: enveloped_msgのツールを使用できます。 たとえば、これは、配信時間に所定の制限を設けてメッセージを送信する方法です。



 // make     . so_5::extra::enveloped_msg::make<my_message>(... /*    */) // envelope         . //  5s        . .envelope<so_5::extra::enveloped_msg::time_limited_delivery_t>(5s) //        . .send_to(destination);
      
      





楽しくするために:封筒の中の封筒



エンベロープは、内部にメッセージを運ぶように設計されています。 しかし、どれですか?



どれでも。



そして、これは興味深い質問につながります。別の封筒の中に封筒を入れることは可能ですか?



はい、できます。 好きなだけ。 ネストの深さは、開発者の常識と再帰呼び出しhandler_found_hook / transformation_hookのスタックの深さによってのみ制限されます。



同時に、SObjectizerは独自のエンベロープの開発者に向かっています。エンベロープは、その内部にあるもの(特定のメッセージまたは別のエンベロープ)について考えてはなりません。 エンベロープでフックメソッドが呼び出され、エンベロープがその内容を提供できると判断すると、エンベロープはhandler_invoker_tでinvoke()を呼び出し、invoke()でその内容へのリンクを渡します。 そして、invoke()insideは、それが何を扱っているかを理解します。 これが別のエンベロープである場合、invoke()自体がこのエンベロープで必要なフックメソッドを呼び出します。



上記のso_5 :: extra :: enveloped_msgのツールキットを使用すると、ユーザーは次のようにいくつかのネストされたエンベロープを作成できます。



 so_5::extra::enveloped_msg::make<my_message>(...) // ,        my_message. .envelope<inner_envelope_type>(...) // ,      inner_envelope_type. .envelope<outer_envelope_type>(...) .send_to(destination);
      
      





封筒の使用例



さて、SObjectizer-5.5.23の内部を調べた後、よりユーザーフレンドリーなアプリケーションの部分に移りましょう。 以下は、so_5_extraで既に実装されているものに基づいているか、so_5_extraのツールを使用するいくつかの例です。



取り消し可能なタイマー



エンベロープ付きのこのキッチン全体は、タイマーメッセージのリコールの保証の問題を解決するために考案されたので、最後に何が起こったのかを見てみましょう。 新しいso_5 :: extra :: revocable_timer名前空間のツールを使用するso_5_extra-1.2.0の例を使用します。



取り消し可能なタイマーを使用したコード例
 #include <so_5_extra/revocable_timer/pub.hpp> #include <so_5/all.hpp> namespace timer_ns = so_5::extra::revocable_timer; class example_t final : public so_5::agent_t { //  ,       //    . struct first_delayed final : public so_5::signal_t {}; struct second_delayed final : public so_5::signal_t {}; struct last_delayed final : public so_5::signal_t {}; struct periodic final : public so_5::signal_t {}; //    . timer_ns::timer_id_t m_first; timer_ns::timer_id_t m_second; timer_ns::timer_id_t m_last; timer_ns::timer_id_t m_periodic; public : example_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } { so_subscribe_self() .event( &example_t::on_first_delayed ) .event( &example_t::on_second_delayed ) .event( &example_t::on_last_delayed ) .event( &example_t::on_periodic ); } void so_evt_start() override { using namespace std::chrono_literals; //      ... m_first = timer_ns::send_delayed< first_delayed >( *this, 100ms ); m_second = timer_ns::send_delayed< second_delayed >( *this, 200ms ); m_last = timer_ns::send_delayed< last_delayed >( *this, 300ms ); // ...    . m_periodic = timer_ns::send_periodic< periodic >( *this, 75ms, 75ms ); //    220ms.       //    first_delaye, second_delayed  //    periodic. std::cout << "hang the agent..." << std::flush; std::this_thread::sleep_for( 220ms ); std::cout << "done" << std::endl; } private : void on_first_delayed( mhood_t<first_delayed> ) { std::cout << "first_delayed received" << std::endl; //   second_delayed  periodic. //          ,  //       . m_second.revoke(); m_periodic.revoke(); } void on_second_delayed( mhood_t<second_delayed> ) { std::cout << "second_delayed received" << std::endl; } void on_last_delayed( mhood_t<last_delayed> ) { std::cout << "last_delayed received" << std::endl; so_deregister_agent_coop_normally(); } void on_periodic( mhood_t<periodic> ) { std::cout << "periodic received" << std::endl; } }; int main() { so_5::launch( [](so_5::environment_t & env) { env.register_agent_as_coop( "example", env.make_agent<example_t>() ); } ); return 0; }
      
      







ここには何がありますか?



最初にいくつかのタイマーメッセージを開始し、次に作業スレッドをしばらくブロックするエージェントがあります。 この間、タイマーは、トリガーされたタイマーの結果として、エージェントのいくつかの要求をキューに入れます:複数の定期的なインスタンス、first_delayedとsecond_delayedの1つのインスタンス。



したがって、エージェントがそのスレッドのロックを解除するとき、最初の定期的およびfirst_delayedを受信する必要があります。 first_delayedを処理するとき、エージェントは定期的およびsecond_delayedの配信をキャンセルします。 したがって、これらの信号は、すでにエージェントのキューにあるかどうかに関係なく、エージェントに到達しないようにする必要があります。



例の結果を見てみましょう。



 hang the agent...done periodic received first_delayed received last_delayed received
      
      





はい、そうです。 最初の定期的およびfirst_delayedを取得しました。 次に、periodicもsecond_delayedもありません。



ただし、この例でso_5 :: extra :: revocable_timerの「タイマー」をSObjectizerの通常のタイマーに置き換えた場合、結果は異なります。すでにエージェントのキューに到達した定期的信号とsecond_delayed信号のインスタンスはエージェントに到達します。



配信時間制限付きメッセージ



so_5_extra-1.2.0で利用できるようになるもう1つの便利な機能は、時間制限付きのメッセージ配信です。 たとえば、request_handlerエージェントはverify_signatureメッセージをcrypto_masterエージェントに送信します。 同時に、request_handlerはverify_signatureを5秒以内に配信することを望んでいます。 これが発生しない場合、verity_signature処理には意味がありません。request_handlerエージェントはすでにその作業を停止します。



また、crypto_masterエージェントは、「ボトルネック」であることが判明するのが好きな同志です。 そのような瞬間に、メッセージはキューに蓄積されます。たとえば、前述のverify_signatureは、crypto_masterが解放されるまで待機できます。



request_handlerがverify_signatureメッセージをcrypto_masterエージェントに送信したが、crypto_masterが動かなくなり、10秒間スタックしたとします。 request_handlerエージェントはすでに「フォールオフ」しています。 すでにすべての人にサービス拒否を送信し、作業を完了しました。 ただし、verify_signatureメッセージはcrypto_masterキューに残ります! したがって、crypto_masterが「スティック」するとき、このメッセージを受け取り、このメッセージを処理します。 これはもはや必要ではありませんが。



新しいエンベロープso_5 :: extra :: enveloped_msg :: time_limited_delivery_tを使用すると、この問題を解決できます。request_handlerエージェントは、配信に制限時間を設定したエンベロープに囲まれたverify_signature time_limited_delivery_tを送信します。



 so_5::extra::enveloped_msg::make<verify_signature>(...) .envelope<so_5::extra::enveloped_msg::time_limited_delivery_t>(5s) .send_to(crypto_master_mbox);
      
      





これで、crypto_masterが「固定」され、5秒でverify_signatureに到達できない場合、エンベロープは処理のためにこのメッセージを送信しません。 また、crypto_masterは、他の誰も必要としない作業を行いません。



受信者配信レポート



そして最後に、SObjectizerまたはso_5_extraで定期的に実装されていないが、独立して実行できる好奇心の強い例です。



SObjectizerから受信者への「配信レポート」メッセージのようなものを受け取りたい場合があります。 結局、メッセージが受信者に届いたのは1つのことですが、何らかの理由で受信者がそれに反応しませんでした。 もう1つは、メッセージが受信者にまったく届かなかった場合です。 たとえば、エージェントの過負荷保護メカニズムによってブロックされまし 。 最初のケースでは、回答を待たなかったメッセージは省略できます。 ただし、2番目のケースでは、しばらくしてからメッセージを再送信するのが理にかなっています。



次に、エンベロープを使用して、「配信レポート」の最も単純なメカニズムを実装する方法を見ていきます。



そのため、最初に必要な準備手順を実行します。



 #include <so_5_extra/enveloped_msg/just_envelope.hpp> #include <so_5_extra/enveloped_msg/send_functions.hpp> #include <so_5/all.hpp> using namespace std::chrono_literals; namespace envelope_ns = so_5::extra::enveloped_msg; using request_id_t = int;
      
      





これで、例で使用されるメッセージを定義できます。 最初のメッセージは、必要なアクションを実行するためのリクエストです。 2番目のメッセージは、最初のメッセージが受信者に届いたことの確認です。



 struct request_t final { request_id_t m_id; std::string m_data; }; struct delivery_receipt_t final { //   request_t::m_id   request_t. request_id_t m_id; };
      
      





次に、request_t型のメッセージを処理するエージェントprocessor_tを定義できます。 ただし、処理は「スティッキング」の模倣になります。 つまり request_tを処理した後、状態をst_normalからst_busyに変更します。 st_busy状態では、何もせず、到着するすべてのメッセージを無視します。



これは、processor_tエージェントが3つの連続したrequest_tメッセージを連続して送信する場合、最初の1つを処理し、他の2つがスローされることを意味します。 最初のメッセージを処理するとき、エージェントはst_busyに移動し、st_busyにある間に何が来るかを無視します。



st_busyでは、processor_tエージェントは2秒を費やし、その後再びst_normalに戻り、新しいメッセージを処理する準備が整います。



processor_tエージェントは次のようになります。



 class processor_t final : public so_5::agent_t { //   .     //   . state_t st_normal{this, "normal"}; //  " ".   . state_t st_busy{this, "busy"}; public: processor_t(context_t ctx) : so_5::agent_t{std::move(ctx)} { this >>= st_normal; st_normal.event(&processor_t::on_request); //     ,    . //  2   ,    st_normal. st_busy.time_limit(2s, st_normal); } private: void on_request(mhood_t<request_t> cmd) { std::cout << "processor: on_request(" << cmd->m_id << ", " << cmd->m_data << ")" << std::endl; this >>= st_busy; } };
      
      





これで、requests_generator_tというエージェントを定義できます。これには、processor_tに配信する必要がある一連のリクエストがあります。 request_generator_tエージェントは3秒ごとにパケット全体を送信し、delivery_receipt_tの形式で配信確認を待ちます。



delivery_recept_tが到着すると、requests_generator_tエージェントはバンドルから配信されたリクエストをスローします。 パックが完全に空の場合、例は完了しています。 他に何かが残っている場合は、次の転送時間が来るときに残りのパケットが再度送信されます。



これがrequest_generator_tエージェントコードです。 それは非常に膨大ですが、原始的です。 send_requests()メソッドの内部にのみ注意を払うことができます。このメソッドでは、request_tメッセージが送信され、特別なエンベロープで囲まれています。



Requests_generator_tエージェントコード
 class requests_generator_t final : public so_5::agent_t { //    . const so_5::mbox_t m_processor; //  ,       . std::map<request_id_t, std::string> m_requests; struct resend_requests final : public so_5::signal_t {}; public: requests_generator_t(context_t ctx, so_5::mbox_t processor) : so_5::agent_t{std::move(ctx)} , m_processor{std::move(processor)} { so_subscribe_self() .event(&requests_generator_t::on_delivery_receipt) .event(&requests_generator_t::on_resend); } void so_evt_start() override { //    . m_requests.emplace(0, "First"); m_requests.emplace(1, "Second"); m_requests.emplace(2, "Third"); m_requests.emplace(3, "Four"); //  . send_requests(); } private: void on_delivery_receipt(mhood_t<delivery_receipt_t> cmd) { std::cout << "request delivered: " << cmd->m_id << std::endl; m_requests.erase(cmd->m_id); if(m_requests.empty()) //    .  . so_deregister_agent_coop_normally(); } void on_resend(mhood_t<resend_requests>) { std::cout << "time to resend requests, pending requests: " << m_requests.size() << std::endl; send_requests(); } void send_requests() { for(const auto & item : m_requests) { std::cout << "sending request: (" << item.first << ", " << item.second << ")" << std::endl; envelope_ns::make<request_t>(item.first, item.second) .envelope<custom_envelope_t>(so_direct_mbox(), item.first) .send_to(m_processor); } //       3 . so_5::send_delayed<resend_requests>(*this, 3s); } };
      
      





これで、これらのメッセージを使用して通信する必要があるメッセージとエージェントができました。少しだけ残っていました-request_tをprocessor_tに配信するときに、delivery_receipt_tメッセージを何らかの形で到着させました。



これは、このエンベロープを使用して行われます。



 class custom_envelope_t final : public envelope_ns::just_envelope_t { //     . const so_5::mbox_t m_to; // ID  . const request_id_t m_id; public: custom_envelope_t(so_5::message_ref_t payload, so_5::mbox_t to, request_id_t id) : envelope_ns::just_envelope_t{std::move(payload)} , m_to{std::move(to)} , m_id{id} {} void handler_found_hook(handler_invoker_t & invoker) noexcept override { //    ,     . //     . so_5::send<delivery_receipt_t>(m_to, m_id); //      . envelope_ns::just_envelope_t::handler_found_hook(invoker); } };
      
      





一般的に、複雑なことは何もありません。so_5 :: extra :: enveloped_msg :: just_envelope_tから継承します。これは、添付されたメッセージを保存し、

handler_found_hook()およびtransformation_hook()フックの基本的な実装を提供する補助的なタイプのエンベロープですしたがって、custom_envelope_t内で必要な属性のみを保存し、handler_found_hook()フック内でdelivery_receipt_tを送信できます。



実際、それがすべてです。この例を実行すると、次の結果が得られます。



 sending request: (0, First) sending request: (1, Second) sending request: (2, Third) sending request: (3, Four) processor: on_request(0, First) request delivered: 0 time to resend requests, pending requests: 3 sending request: (1, Second) sending request: (2, Third) sending request: (3, Four) processor: on_request(1, Second) request delivered: 1 time to resend requests, pending requests: 2 sending request: (2, Third) sending request: (3, Four) processor: on_request(2, Third) request delivered: 2 time to resend requests, pending requests: 1 sending request: (3, Four) processor: on_request(3, Four) request delivered: 3
      
      





さらに、実際には、配信レポートを生成するためのこのような単純なcustom_envelope_tは機能しない可能性が高いと言わなければなりません。しかし、誰かがこのトピックに興味を持っている場合、コメントで議論することができ、記事の量を増やすことはできません。



封筒で他に何ができるでしょうか?



いい質問です!私たち自身には包括的な答えがありません。おそらく、可能性はユーザーの想像力によってのみ制限されます。さて、SObjectizerで空想を実現するために何かが足りない場合、それを私たちに伝えることができます。私たちはいつも耳を傾けます。そして、重要なことは、時々私たちもやる:)



エージェントとmchainの統合



もう少し真面目に言えば、それは私が時々持っておきたい別の機能であり、so_5_extra-1.2.0でも計画されていました。しかし、ほとんどの場合、これはリリース1.2.0には含まれません。mchainsとエージェントの



統合を簡素化することです実際、エージェントなしで記述されたアプリケーションの他の部分とエージェントの通信を簡素化するために、最初はmchainsがSObjectizerに追加されました。たとえば、ユーザーがGUIを使用して対話するアプリケーションのメインスレッドがあります。また、バックグラウンドで「ハード」な作業を行うエージェントワーカーもいます。メインスレッドからエージェントにメッセージを送信することは問題ではありません。通常の送信を呼び出すだけです。しかし、情報を戻す方法は?







このために、mchain-sが追加されました。



しかし、時間が経つにつれて、mchainsがはるかに大きな役割を果たすことが判明しました。原則として、mchain上でのみ、エージェントなしでSObjectizer上でマルチスレッドアプリケーションを作成することができます(詳細はこちら)。また、エージェントの負荷を分散する手段としてmchain-sを使用できます。生産者と消費者の問題を解決するメカニズムとして。



プロデューサーとコンシューマーの問題は、プロデューサーがコンシューマーが処理できるよりも速くメッセージを生成する場合、問題が発生することです。メッセージキューが増加したり、時間の経過とともにパフォーマンスが低下したり、メモリ不足によりアプリケーションが完全にクラッシュしたりします。



この場合に使用することを提案した通常の解決策は、コレクターとパフォーマンスのエージェントのペアメッセージの制限を使用することもできます(主な保護メカニズムとして、またはコレクターパフォーマンスへの追加として)。ただし、コレクターパフォーマンスを記述するには、プログラマーの追加作業が必要です。



しかし、mchainsは、開発者側の最小限の労力でこれらの目的に使用できます。したがって、プロデューサは次のメッセージをmchainに入れ、コンシューマはこのmchainからメッセージを受け取ります。



しかし、問題は、消費者がエージェントである場合、エージェントが利用可能なreceive()およびselect()関数を介してmchainを操作することはあまり便利ではないことです。そして、この不便さは、エージェントとmchain-sを統合するためのいくつかのツールの助けを借りて解消することができます。



このようなツールを開発する場合、いくつかの問題を解決する必要があります。たとえば、メッセージがmchainに到着した場合、mchainからどの時点で抽出する必要がありますか?消費者が無料で何も処理しない場合は、mchainからメッセージをすぐに受け取り、消費者エージェントに渡すことができます。メッセージがmchainからコンシューマーに既に送信されている場合、彼はまだこのメッセージを処理することができていませんが、新しいメッセージがmchainに既に到着しています...この場合はどうすればよいですか?



この場合、エンベロープが役立つ可能性があるという推測があります。したがって、mchainから最初のメッセージを取得してコンシューマーに送信するとき、このメッセージを特別なエンベロープでラップします。エンベロープは、メッセージが配信および処理されたことを確認すると、mchainに次のメッセージ(ある場合)を要求します。



もちろん、ここではすべてがそれほど単純ではありません。しかし、これまでのところかなり解決可能に見えます。そして、so_5_extraの次のバージョンの1つで同様のメカニズムが登場することを願っています。



Pandoraの箱を開けますか?



私たちにとって、追加された機能自体が二重の感情を引き起こすことに注意してください。



一方で、封筒は既に言及されたものを実行することを許可/許可しました(しかし、何かを夢見ただけです)。たとえば、これはタイマーのキャンセルの保証であり、配信時間、配信レポート、以前に送信されたメッセージをリコールする機能の制限です。



他方、これがその後何につながるかは明らかではありません。確かに、この機会を利用して必要な場所と必要でない場所を悪用する場合、どんな機会からでも問題を起こすことができます。パンドラの箱を開けても、まだ何が待っているのか想像できませんか?



辛抱強く、これが私たちをどこに導くかを見るだけです。



結論ではなく、SObjectizerの当面の開発計画について



結論ではなく、SObjectizerの非常に近い(そしてそれだけではない)未来をどのように見ているかについてお話したいと思います。誰かが私たちの計画で何かに満足していないなら、あなたは声を出して、SObjectizer-5がどのように発展するかに影響を与えることができます。



SObjectizer-5.5.23およびso_5_extra-1.2.0の最初のベータバージョンはすでに修正されており、ダウンロードおよび実験用に利用できます。ドキュメントと使用例の領域では、このリリースに関連する多くの作業が残っています。したがって、公式リリースは11月の最初の10年に計画されています。それが以前にうまくいく場合、私たちは早くそれを行います。



SObjectizer-5.5.23のリリースは、ブランチ5.5の進化が終わりに近づいていることを意味するようです。それはされ、このブランチの最初のリリースは2014年10月には、4年前に開催されました。それ以来、SObjectizer-5は5.5ブランチ内で進化し、バージョン間の大きな変更はありません。簡単ではありませんでした。特に、C ++ 11の理想的なサポートとはほど遠いコンパイラを振り返る必要があったという事実を考慮してください。



現在、5.5ブランチ内の互換性、特に古いC ++コンパイラの互換性を振り返る理由はありません。 2014年、C ++ 14が正式に採用される準備が整い、C ++ 17がまだ水平線になかったため、正当化できることは、今ではまったく異なっています。



さらに、SObjectizer-5.5自体には、これと同じ互換性のために出現し、SObjectizerのさらなる開発を複雑にする、かなりの量のレーキとバックアップがすでに蓄積されています。



したがって、今後数か月で、次のシナリオに従って行動する予定です



。1. so_5_extraの次のバージョンの開発。エージェントのテストの作成を簡素化するツールを追加します。 so_5_extra-1.3.0(つまり、1.2.0に対する変更を壊す)になるか、so_5_extra-1.2.1(つまり、変更を壊さない)になるかはまだ明確ではありません。それがどうなるか見てみましょう。 so_5_extraの次のバージョンがSObjectizer-5.5に基づいていることは明らかです。



1a。so_5_extraの次のバージョンでSObjectizer-5.5で何か追加の作業を行う必要がある場合、次のバージョン5.5.24がリリースされます。 so_5_extraの場合、SObjectizerのカーネルを改善する必要がない場合、バージョン5.5.23はブランチ5.5のフレームワークの最後の重要なバージョンであることが判明します。マイナーなバグ修正リリースがリリースされます。ただし、ブランチ5.5自体の開発は、バージョン5.5.23または5.5.24で停止します。



2.その後、SObjectizer-5.6.0のバージョンがリリースされ、新しいブランチが開きます。ブランチ5.6では、蓄積されたすべての松葉杖とバックアップ、および非推奨として長くマークされていた古いゴミからSObjectizerコードを消去します。いくつかのものはリファクタリングを受ける可能性がありますが(たとえば、abstract_message_box_tは変更できます)、ほとんど基本的ではありません。作業の基本原則とSObjectizer-5.6のSObjectizer-5.5の特徴的な機能は、同じ形式のままです。



SObjectizer-5.6では、すでにC ++ 14が必要です(少なくともGCC-5.5レベル)。 VC ++ 15(Visual Studio 2017のもの)以下のVisual C ++コンパイラはサポートされません。



ブランチ5.6はSObjectizerの安定したブランチと見なされます。これは、SObjectizer-5.7の最初のバージョンが登場するまで関連します。



2019年の初め、暫定的に2月にバージョン5.6.0をリリースしたいと思います。



3.ブランチ5.6を安定化させた後、ブランチ5.7で作業を開始したいと思います。ブランチ5.7では、SObjectizerの動作の基本原則を修正できます。たとえば、パブリックディスパッチャを完全に放棄し、プライベートディスパッチャのみを残します。協同組合のメカニズムとその親子関係をやり直し、協同組合の登録/登録解除のボトルネックを解消します。メッセージ/シグナルによる除算を削除します。 send / send_delayed / send_periodicのみがメッセージを送信できるようにし、「内部」でdeliver_messageおよびschedule_timerメソッドを非表示にします。メッセージをディスパッチするメカニズムを変更して、このプロセスからdynamic_castsを完全に削除するか、それらを最小限に抑えます。



一般に、方向転換する場所があります。同時に、SObjectizer-5.7では、C ++ 14に関係なく、すでにC ++ 17が必要です。



ピンクのメガネをかけずに物事を見る場合、リリース5.7.0が2019年秋の終わりに行われると便利です。 2019年のSObjectizerの主な作業バージョンはブランチ5.6です。



4.これと並行して、so_5_extraが開発されます。おそらく、so_5_extra-2バージョンはSObjectizer-5.6と一緒にリリースされ、2019年にはSObjectizer-5.6に基づいて新しい機能が組み込まれます。



したがって、SObjectizer-5の基本原則の一部が徐々に改訂され、SObjectizer-5の進歩的な進化が見られます。同時に、最小限の苦痛であるバージョンから別のバージョンに切り替えることができるように、できる限りスムーズにそれを行うようにします。



ただし、SObjectizerからより劇的で重要な変更が必要な場合は、これについていくつかの考えがあります。つまり、SObjectizerを好きなように作り直して、別のプログラミング言語用にSObjectizer-6を実装できます。ただし、これはSObjectizer-5の進化に伴い発生するため、私たち自身の費用でこれを完全には行いません。



おそらくそれだけです。前の記事へのコメントでそれは良い建設的な議論であることが判明しました。同様の議論が今回行われた場合、私たちにとって有用でしょう。いつものように、私たちは質問に答える準備ができていますが、賢明な質問には喜んで答えます。



そして、これらの行に達したほとんどの患者の読者に、記事を読んでくれた時間に感謝します。



All Articles