Nouveau dans SObjectizer-5.5.23: réalisation des souhaits ou boîte de Pandore?



Cet article fait suite à un article de réflexion publié il y a un mois, " Est-il facile d'ajouter de nouvelles fonctionnalités à l'ancien framework? L'agonie de choix basée sur l'exemple du développement de SObjectizer ". Cet article décrit la tâche que nous voulions résoudre dans la prochaine version de SObjectizer, examine deux approches de sa solution et énumère les avantages et les inconvénients de chacune des approches.

Au fil du temps, l'une des approches a été mise en œuvre et de nouvelles versions de SObjectizer , ainsi que le projet d' accompagnement so_5_extra , déjà appelé « respirer profondément ». Vous pouvez littéralement prendre et essayer.

Aujourd'hui, nous allons parler de ce qui a été fait, pourquoi cela a été fait, à quoi cela a conduit. Si quelqu'un est intéressé à suivre la façon dont l'un des rares cadres d'acteurs en direct, multiplateforme et ouverts pour C ++ se développe, vous êtes le bienvenu sur cat.

Comment tout a commencé?


Tout a commencé par une tentative de résoudre le problème de l'annulation garantie des minuteries. L'essence du problème est que lorsqu'un message retardé ou périodique est envoyé, le programmeur peut annuler la remise du message. Par exemple:

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

Après avoir appelé timer_id.release (), le temporisateur n'enverra plus de nouvelles instances du message my_message. Mais ces copies qui ont déjà été envoyées et qui sont dans la file d'attente des destinataires n'iront nulle part. Au fil du temps, ils seront extraits de ces mêmes files d'attente et transférés aux agents destinataires pour traitement.

Ce problème est une conséquence des principes de base du fonctionnement de SObjectizer-5 et n'a pas de solution simple car SObjectizer ne peut pas extraire les messages des files d'attente. Cela ne peut pas parce que les files d'attente dans SObjectizer appartiennent aux répartiteurs, les répartiteurs sont différents, leurs files d'attente sont également organisées différemment. Y compris il y a des répartiteurs qui ne font pas partie du SObjectizer, et SObjectizer, en principe, ne peut pas savoir comment ces répartiteurs fonctionnent.

En général, il existe une telle fonctionnalité dans les temporisateurs natifs de SObjectizer. Non pas que cela gâche trop les développeurs. Mais des précautions supplémentaires doivent être prises. Surtout pour les débutants qui se familiarisent avec le framework.

Et puis, finalement, les mains sont allées jusqu'à proposer une solution à ce problème.

Quel chemin de solution a été choisi?


Dans un article précédent , deux options possibles ont été envisagées. La première option n'a pas nécessité de modifications du mécanisme de remise des messages dans SObjectizer, mais elle a demandé au programmeur de changer explicitement le type de message envoyé / reçu.

La deuxième option nécessitait une modification du mécanisme de remise des messages SObjectizer. C'est ce chemin qui a été choisi, car il permettait de cacher au destinataire du message le fait que le message avait été envoyé d'une manière spécifique.

Qu'est-ce qui a changé dans SObjectizer?


Nouveau concept: enveloppe avec message à l'intérieur


Le premier composant de la solution implémentée est l'ajout d'un tel concept comme enveloppe à SObjectizer. Une enveloppe est un message spécial, à l'intérieur duquel se trouve le message actuel (charge utile). SObjectizer remet l'enveloppe avec le message au destinataire presque de la manière habituelle. La différence fondamentale dans le traitement des enveloppes n'est détectée qu'au tout dernier stade de livraison:

  • lors de la livraison d'un message régulier, l'agent destinataire recherche simplement un gestionnaire pour ce type de message et, si un tel gestionnaire est trouvé, le gestionnaire trouvé est appelé et le message remis est renvoyé en tant que paramètre;
  • et lors de la remise de l'enveloppe avec le message une fois que le gestionnaire est trouvé, une tentative est d'abord effectuée pour extraire le message de l'enveloppe. Et seulement si l'enveloppe a donné le message qui y est stocké, alors seulement le gestionnaire est appelé.

Il y a deux points clés ici qui ont un impact majeur sur pourquoi et comment les enveloppes de message peuvent être utilisées.

Le premier point clé est qu'un message est demandé à partir d'une enveloppe uniquement lorsqu'un gestionnaire de message est trouvé chez le destinataire. C'est-à-dire uniquement lorsque le message a vraiment été remis au destinataire et que le destinataire sera ici et traitera maintenant ce message.

Le deuxième point clé ici est que l'enveloppe ne peut pas contenir le message qu'elle contient. C'est-à-dire, par exemple, qu'une enveloppe peut vérifier l'heure actuelle et décider que toutes les dates de livraison ont été manquées et, par conséquent, le message a cessé d'être pertinent et ne peut pas être traité. Par conséquent, l'enveloppe ne transmettra pas le message. Par conséquent, SObjectizer ignorera simplement cette enveloppe et ne prendra aucune action supplémentaire.

À quoi ressemble une enveloppe?


Une enveloppe est une implémentation de l'interface d'enveloppe_t, qui est définie comme suit:

 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; } }; 

C'est-à-dire Une enveloppe est essentiellement le même message que tout le monde. Mais avec un attribut spécial, qui est retourné par la méthode so5_message_kind ().

Le programmeur peut développer ses enveloppes héritant d'enveloppe_t (ou, plus commodément, de so_5 :: extra :: envelopped_msg :: just_envelope_t ) et remplaçant les méthodes de hook handler_found_hook () et transformation_hook ().

À l'intérieur des méthodes de hook, le développeur de l'enveloppe décide s'il souhaite ou non transmettre le message à l'intérieur de l'enveloppe pour traitement / transformation. S'il le souhaite, le développeur doit appeler la méthode invoke () et l'objet invoker. S'il ne veut pas, il n'appelle pas, dans ce cas l'enveloppe et son contenu seront ignorés.

Comment les enveloppes résolvent-elles le problème de l'annulation des minuteries?


La solution, qui est maintenant implémentée dans so_5_extra sous la forme de l'espace de noms so_5 :: extra :: revocable_timer, est très simple: l'envoi spécial d'un message en attente ou périodique crée une enveloppe spéciale, à l'intérieur de laquelle se trouve non seulement le message lui-même, mais aussi le drapeau atomique révoqué. Si cet indicateur est effacé, le message est alors considéré comme pertinent. S'il est défini, le message est considéré comme retiré.

Lorsque la méthode hook est appelée sur l'enveloppe, l'enveloppe vérifie la valeur de l'indicateur révoqué. Si le drapeau est activé, l'enveloppe ne donne pas de message. Ainsi, le message n'est pas traité même si le temporisateur a déjà réussi à mettre le message dans la file d'attente du récepteur.

Extension d'interface abstract_message_box_t


L'ajout de l'interface enveloppe_t n'est qu'une partie de l'implémentation des enveloppes dans SObjectizer. La deuxième partie prend en compte le fait de l'existence d'enveloppes dans le mécanisme de remise des messages à l'intérieur du SObjectizer.

Ici, malheureusement, ne pouvait se passer de rendre les modifications visibles pour l'utilisateur. En particulier, dans la classe abstract_message_box_t, qui définit l'interface de toutes les boîtes aux lettres dans SObjectizer, il était nécessaire d'ajouter une autre méthode virtuelle:

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

Cette méthode est responsable de la remise d'une enveloppe de message avec un message de type msg_type au récepteur. Cette livraison peut différer dans les détails de mise en œuvre en fonction du type de mbox.

Lors de l'ajout de do_deliver_enveloped_msg () à abstract_message_box_t, nous avions le choix: en faire une méthode virtuelle pure ou proposer une implémentation par défaut.

Si nous faisions de do_deliver_enveloped_msg () une méthode virtuelle pure, nous briserions la compatibilité entre les versions de SObjectizer dans la branche 5.5. Après tout, les utilisateurs qui ont écrit leurs propres implémentations mbox devraient modifier leurs propres mbox lors du passage à SObjectizer-5.5.23, sinon ils ne pourraient pas compiler avec la nouvelle version de SObjectizer.

Nous ne voulions pas cela, nous n'avons donc pas fait de do_deliver_enveloped_msg () une méthode virtuelle pure dans la v.5.5.23. Il a une implémentation par défaut qui lève juste une exception. Ainsi, l'utilisateur mbox-s personnalisé pourra continuer à travailler normalement avec des messages normaux, mais refusera automatiquement d'accepter les enveloppes. Nous avons trouvé ce comportement plus acceptable. De plus, au stade initial, il est peu probable que les enveloppes contenant des messages soient largement utilisées, et il est peu probable que, dans la nature, des implémentations personnalisées de SObjectizer mbox se trouvent souvent;)

De plus, il y a loin d'être une chance nulle que dans les versions majeures ultérieures de SObjectizer, où nous ne regarderons pas la compatibilité avec la branche 5.5, l'interface abstract_message_box_t subira des changements majeurs. Mais nous prenons déjà de l'avance sur nous-mêmes ...

Comment envoyer des enveloppes avec des messages


SObjectizer-5.5.23 lui-même ne fournit pas un moyen simple d'envoyer des enveloppes. Il est supposé qu'un type spécifique d'enveloppe et des outils appropriés sont en cours de développement pour une tâche spécifique afin d'envoyer facilement des enveloppes d'un type spécifique. Un exemple de ceci peut être vu dans so_5 :: extra :: revocable_timer , où vous devez non seulement envoyer l'enveloppe, mais aussi donner à l'utilisateur un timer_id spécial.

Pour les situations plus simples, vous pouvez utiliser les outils de so_5 :: extra :: envelopped_msg . Par exemple, voici comment un message est envoyé avec une restriction donnée sur le moment de sa livraison:

 // 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); 

Pour que tout soit amusant: des enveloppes dans des enveloppes


Les enveloppes sont conçues pour transporter certains messages en elles-mêmes. Mais lesquels?

Tout.

Et cela nous amène à une question intéressante: est-il possible de mettre une enveloppe à l'intérieur d'une autre enveloppe?

Oui tu peux. Autant que vous voulez. La profondeur d'imbrication n'est limitée que par le bon sens du développeur et la profondeur de la pile pour l'appel récursif handler_found_hook / transformation_hook.

Dans le même temps, SObjectizer va vers les développeurs de ses propres enveloppes: l'enveloppe ne doit pas penser à ce qu'il contient - un message spécifique ou une autre enveloppe. Lorsque la méthode hook est appelée sur l'enveloppe et que l'enveloppe décide qu'elle peut donner son contenu, l'enveloppe appelle simplement invoke () sur handler_invoker_t et passe un lien vers son contenu dans invoke (). Et déjà invoke () inside comprendra de quoi il s'agit. Et s'il s'agit d'une autre enveloppe, invoke () appellera elle-même la méthode de hook requise sur cette enveloppe.

En utilisant la boîte à outils montrée ci-dessus de so_5 :: extra :: envelopped_msg, l'utilisateur peut créer plusieurs enveloppes imbriquées comme ceci:

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

Quelques exemples d'utilisation d'enveloppes


Maintenant, après avoir parcouru les composants internes de SObjectizer-5.5.23, il est temps de passer à la partie d'application la plus utile pour les utilisateurs. Vous trouverez ci-dessous quelques exemples basés sur ce qui est déjà implémenté dans so_5_extra ou utilisant les outils de so_5_extra.

Minuteries révocables


Puisque toute cette cuisine avec des enveloppes a été conçue pour résoudre le problème du rappel garanti des messages de la minuterie, voyons ce qui s'est finalement passé. Nous utiliserons l'exemple de so_5_extra-1.2.0, qui utilise les outils du nouvel espace de noms so_5 :: extra :: revocable_timer:

Exemple de code avec des minuteries révocables
 #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; } 


Qu'avons-nous ici?

Nous avons un agent qui initie d'abord plusieurs messages de temporisation, puis bloque son thread de travail pendant un certain temps. Pendant ce temps, le temporisateur parvient à mettre en file d'attente l'agent plusieurs demandes à la suite des temporisateurs déclenchés: plusieurs instances périodiques, une première retardée et une deuxième retardée chacune.

Par conséquent, lorsqu'un agent déverrouille son thread, il doit recevoir le premier périodique et le premier retardé. Lors du traitement du premier délai, l'agent annule la livraison des délais périodiques et des seconds délais. Par conséquent, ces signaux ne doivent pas atteindre l'agent, qu'ils soient déjà dans la file d'attente de l'agent ou non (et ils le sont).

Nous regardons le résultat de l'exemple:

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

Oui, ça l'est. Vous avez le premier périodique et le premier retardé. Il n'y a alors ni périodique ni délai différé.

Mais si dans l'exemple nous remplaçons les "temporisateurs" de so_5 :: extra :: revocable_timer par les temporisateurs standard de SObjectizer, le résultat sera différent: toutes les instances de signal périodiques et second_delayed qui sont déjà entrées dans la file d'attente de l'agent atteindront l'agent.

Messages à délai de livraison limité


Une autre chose utile, parfois, qui deviendra disponible dans so_5_extra-1.2.0 est la livraison de messages avec une limite de temps. Par exemple, l'agent request_handler envoie un message verify_signature à l'agent crypto_master. Dans le même temps, request_handler souhaite que verify_signature soit livré dans les 5 secondes. Si cela ne se produit pas, le traitement verity_signature n'aura aucun sens, l'agent request_handler arrêtera déjà son travail.

Et l'agent crypto_master est un camarade qui aime se révéler être un «goulot d'étranglement»: il commence parfois à ralentir. À un tel moment, les messages sont accumulés dans la file d'attente, comme la vérification_signature ci-dessus, qui peut attendre que crypto_master soit libéré.

Supposons que request_handler ait envoyé un message verify_signature à l'agent crypto_master, mais que crypto_master s'est enlisé et qu'il est resté bloqué pendant 10 secondes. L'agent request_handler est déjà "tombé", c'est-à-dire a déjà envoyé à tous un déni de service et a terminé son travail. Mais le message verify_signature reste dans la file d'attente crypto_master! Ainsi, lorsque crypto_master "décolle", il prendra ce message et traitera ce message. Bien que ce ne soit plus nécessaire.

En utilisant la nouvelle enveloppe so_5 :: extra :: envelopped_msg :: time_limited_delivery_t, nous pouvons résoudre ce problème: l'agent request_handler enverra verify_signature time_limited_delivery_t enfermé dans l'enveloppe avec un délai de livraison:

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

Maintenant, si crypto_master «colle» et ne parvient pas à arriver à verify_signature en 5 secondes, l'enveloppe n'enverra tout simplement pas ce message pour traitement. Et crypto_master ne fera pas le travail dont personne d'autre n'a besoin.

Rapports de livraison des destinataires


Et enfin, un exemple d'une chose curieuse qui n'est pas régulièrement implémentée dans SObjectizer ou so_5_extra, mais qui peut être effectuée indépendamment.

Parfois, vous voulez recevoir du SObjectizer quelque chose comme un message de «remise de rapport» au destinataire. Après tout, c'est une chose lorsque le message a atteint le destinataire, mais le destinataire pour une raison quelconque n'y a pas répondu. Une autre chose est lorsque le message n'a pas du tout atteint le destinataire. Par exemple, il a été bloqué par un mécanisme de protection contre les surcharges d'agent . Dans le premier cas, un message auquel nous n'avons pas attendu de réponse peut être omis. Mais dans le deuxième cas, il peut être judicieux de renvoyer le message après un certain temps.

Nous allons maintenant examiner comment le mécanisme le plus simple de «rapports de livraison» peut être mis en œuvre à l'aide d'enveloppes.

Donc, nous faisons d'abord les étapes préparatoires nécessaires:

 #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; 

Nous pouvons maintenant définir les messages qui seront utilisés dans l'exemple. Le premier message est une demande pour effectuer certaines actions dont nous avons besoin. Et le deuxième message est une confirmation que le premier message a atteint le destinataire:

 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; }; 

Ensuite, nous pouvons définir un agent processor_t qui traitera les messages de type request_t. Mais le traitement se fera avec une imitation de «collage». C'est-à-dire il traite request_t, après quoi il change son état de st_normal à st_busy. Dans l'état st_busy, il ne fait rien et ignore tous les messages qui lui parviennent.

Cela signifie que si l'agent processor_t envoie trois messages request_t consécutifs, il traitera le premier et les deux autres seront levés, car lors du traitement du premier message, l'agent ira à st_busy et ignorera ce qui lui arrivera pendant qu'il est dans st_busy.

Dans st_busy, l'agent processor_t passera 2 secondes, après quoi il reviendra à nouveau à st_normal et sera prêt à traiter les nouveaux messages.

Voici à quoi ressemble l'agent 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; } }; 

Nous pouvons maintenant définir l'agent requests_generator_t, qui a un tas de requêtes qui doivent être livrées à processor_t. L'agent request_generator_t envoie le paquet entier toutes les 3 secondes, puis attend la confirmation de livraison sous la forme de delivery_receipt_t.

Lorsque delivery_recept_t arrive, l'agent requests_generator_t jette la demande livrée hors du bundle. Si le pack est complètement vide, l'exemple est terminé. S'il reste quelque chose d'autre, le paquet restant sera envoyé à nouveau lors de la prochaine fois pour le renvoi.

Voici donc le code de l'agent request_generator_t. C'est assez volumineux, mais primitif. Vous ne pouvez faire attention qu'aux internes de la méthode send_requests (), dans laquelle les messages request_t sont envoyés, enfermés dans une enveloppe spéciale.

Code d'agent 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); } }; 

Nous avons maintenant des messages et des agents qui doivent utiliser ces messages pour communiquer. Il ne restait qu'une petite chose - faire en sorte que les messages delivery_receipt_t arrivent lors de la remise de request_t à processor_t.

Cela se fait en utilisant cette enveloppe:

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

En général, il n'y a rien de compliqué. Nous héritons de so_5 :: extra :: envelopped_msg :: just_envelope_t. Il s'agit d'un type d'enveloppe auxiliaire qui stocke le message qu'il
contient et fournit l'implémentation de base des hooks handler_found_hook () et transformation_hook (). Par conséquent, nous ne pouvons enregistrer que les attributs dont nous avons besoin dans custom_envelope_t et envoyer delivery_receipt_t dans le hook handler_found_hook ().

En fait, c'est tout. Si nous exécutons cet exemple, nous obtenons ce qui suit:

 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 

De plus, je dois dire que dans la pratique, un simple custom_envelope_t pour générer des rapports de livraison ne convient guère. Mais si quelqu'un s'intéresse à ce sujet, alors il peut être discuté dans les commentaires, et non augmenter le volume de l'article.

Que pourrait-on faire d'autre avec des enveloppes?


Grande question!À laquelle nous-mêmes n'avons pas de réponse globale. Probablement, les possibilités ne sont limitées que par l'imagination des utilisateurs. Eh bien, si pour la réalisation de fantasmes dans SObjectizer quelque chose manque, alors cela peut nous être dit. Nous écoutons toujours. Et, ce qui est important, parfois nous le faisons même :)

Intégration d'agents avec mchain


Parlant un peu plus au sérieux, c'est une autre fonctionnalité que j'aimerais avoir de temps en temps et qui était même prévue pour so_5_extra-1.2.0. Mais qui, très probablement, ne tombera pas dans la version 1.2.0.

Il s'agit de simplifier l'intégration des chaînes et des agents.

Le fait est qu'au départ, des chaînes ont été ajoutées à SObjectizer afin de simplifier la communication des agents avec d'autres parties de l'application écrites sans agents. Par exemple, il existe le thread principal de l'application, sur lequel l'utilisateur interagit à l'aide de l'interface graphique. Et il y a plusieurs agents-travailleurs qui font le travail «dur» de fond. L'envoi d'un message à un agent à partir du thread principal n'est pas un problème: il suffit d'appeler l'envoi régulier. Mais comment retransférer des informations?

Pour cela, des chaînes ont été ajoutées.

Mais au fil du temps, il s'est avéré que les chaînes pouvaient jouer un rôle beaucoup plus important. Il est possible, en principe, de faire des applications multithread sur SObjectizer sans aucun agent, uniquement sur mchain-ahs (plus de détails ici ). Et vous pouvez utiliser mchain-s comme moyen d'équilibrer la charge des agents. Comme mécanisme de résolution des problèmes producteurs-consommateurs.

Le problème avec le producteur-consommateur est que si le producteur génère des messages plus rapidement que le consommateur ne peut les traiter, alors nous sommes en difficulté. Les files d'attente de messages vont augmenter, les performances peuvent se dégrader avec le temps ou l'application se bloque complètement en raison de l'épuisement de la mémoire.

La solution habituelle que nous avons proposé d'utiliser dans ce cas est d'utiliserune paire d'agents collectionneurs-interprètes . Vous pouvez également utiliser des limites de messages (soit en tant que mécanisme de protection principal, soit en complément du collecteur-interprète). Mais l'écriture collector-performer nécessite un travail supplémentaire de la part du programmeur.

Mais les chaînes peuvent être utilisées à ces fins avec un effort minimal du développeur. Ainsi, le producteur mettrait le message suivant dans la chaîne, et le consommateur prendrait les messages de cette chaîne.

Mais le problème est que lorsque le consommateur est un agent, il n'est pas très pratique pour un agent de travailler avec mchain via les fonctions de réception () et de sélection () disponibles. Et cet inconvénient pourrait être tenté d'être éliminé à l'aide d'un outil d'intégration d'agents et de chaînes.

Lors du développement d'un tel outil, il sera nécessaire de résoudre plusieurs problèmes. Par exemple, lorsqu'un message arrive dans mchain, à quel moment doit-il être extrait de mchain? Si le consommateur est libre et ne traite rien, vous pouvez immédiatement récupérer le message de mchain et le transmettre à l'agent consommateur. Si un message a déjà été envoyé au consommateur depuis mchain, il n'a pas encore réussi à traiter ce message, mais un nouveau message est déjà arrivé dans mchain ... Que faire dans ce cas?

Il y a des spéculations que les enveloppes pourraient aider dans ce cas. Ainsi, lorsque nous prenons le premier message de mchain et l'envoyons au consommateur, nous enveloppons ce message dans une enveloppe spéciale. Lorsque l'enveloppe voit que le message a été remis et traité, elle demande le message suivant à mchain (s'il y en a un).

Bien sûr, tout n'est pas si simple ici. Mais jusqu'à présent, cela semble assez résoluble. Et, j'espère, un mécanisme similaire apparaîtra dans l'une des prochaines versions de so_5_extra.

Allons-nous ouvrir la boîte de Pandore?


Il convient de noter qu'avec nous, les capacités supplémentaires elles-mêmes provoquent un double sentiment.

D'une part, les enveloppes ont déjà permis / permis de faire des choses qui ont été mentionnées précédemment (mais qui rêvaient de quelque chose). Par exemple, il s'agit d'une annulation garantie des minuteries et d'une restriction du délai de livraison, des rapports de livraison, de la possibilité de rappeler un message précédemment envoyé.

D'un autre côté, il n'est pas clair à quoi cela conduira par la suite. Après tout, vous pouvez créer un problème à partir de n'importe quelle opportunité si vous commencez à utiliser cette opportunité là où vous en avez besoin et où vous n'en avez pas. Alors peut-être que nous ouvrons la boîte de Pandore et nous n'imaginons toujours pas ce qui nous attend?

Il ne reste plus qu'à être patient et à voir où tout cela nous mènera.

À propos des plans de développement immédiat de SObjectizer au lieu de conclure


Au lieu d'une conclusion, je veux parler de la façon dont nous voyons l'avenir très proche (et pas seulement) de SObjectizer. Si quelqu'un n'est pas satisfait de quelque chose dans nos plans, alors vous pouvez parler et influencer la façon dont SObjectizer-5 se développera.

Les premières versions bêta de SObjectizer-5.5.23 et so_5_extra-1.2.0 sont déjà corrigées et disponibles pour téléchargement et expérimentations. Il y aura encore beaucoup de travail à faire dans le domaine de la documentation et des cas d'utilisation. Par conséquent, la sortie officielle est prévue dans la première décennie de novembre. Si cela fonctionne plus tôt, nous le ferons plus tôt.

La sortie de SObjectizer-5.5.23 semble signifier que l'évolution de la branche 5.5 touche à sa fin. La toute première version de ce fil a eu lieu il y a quatre ans, en octobre 2014.. Depuis lors, SObjectizer-5 a évolué au sein de la branche 5.5 sans aucun changement majeur entre les versions. Ce n'était pas facile. Surtout compte tenu du fait que pendant tout ce temps, nous avons dû regarder en arrière des compilateurs qui avaient loin d'être un support idéal pour C ++ 11.

Maintenant, nous ne voyons aucune raison de revenir sur la compatibilité au sein de la branche 5.5, et en particulier sur les anciens compilateurs C ++. Ce qui pouvait être justifié en 2014, alors que le C ++ 14 se préparait à être officiellement adopté et que le C ++ 17 n'était pas encore à l'horizon, il semble maintenant complètement différent.

De plus, dans SObjectizer-5.5 lui-même, il a déjà accumulé une bonne quantité de rake et de sauvegardes qui sont apparues en raison de cette même compatibilité et qui compliquent le développement ultérieur de SObjectizer.

Par conséquent, dans les prochains mois, nous allons agir selon le scénario suivant:

1. Développement de la prochaine version de so_5_extra, dans laquelle je souhaite ajouter des outils pour simplifier l'écriture de tests pour les agents. Il ne sera pas encore clair si ce sera so_5_extra-1.3.0 (c'est-à-dire avec des changements de rupture par rapport à 1.2.0) ou s'il sera so_5_extra-1.2.1 (c'est-à-dire sans changements de rupture). Voyons comment ça se passe. Il est clair que la prochaine version de so_5_extra sera basée sur SObjectizer-5.5.

1a. Si pour la prochaine version de so_5_extra vous devez faire quelque chose de plus dans SObjectizer-5.5, alors la prochaine version 5.5.24 sera publiée. Si pour so_5_extra il ne sera pas nécessaire d'apporter des améliorations au cœur de SObjectizer, alors la version 5.5.23 se révélera être la dernière version significative dans le cadre de la branche 5.5. Des versions mineures de correction de bugs sortiront. Mais le développement de la branche 5.5 elle-même s'arrête sur la version 5.5.23 ou 5.5.24.

2. Ensuite, une version de SObjectizer-5.6.0 sera publiée, ce qui ouvrira une nouvelle branche. Dans la branche 5.6, nous allons nettoyer le code SObjectizer de toutes les béquilles et sauvegardes accumulées, ainsi que des anciennes corbeilles qui ont longtemps été marquées comme obsolètes. Il est probable que certaines choses subiront une refactorisation (par exemple, abstract_message_box_t peut être modifié), mais à peine cardinal. Les principes de base du travail et les caractéristiques de SObjectizer-5.5 dans SObjectizer-5.6 resteront sous la même forme.

SObjectizer-5.6 nécessitera déjà C ++ 14 (au moins au niveau GCC-5.5). Les compilateurs Visual C ++ sous VC ++ 15 (qui proviennent de Visual Studio 2017) ne seront pas pris en charge.

Nous considérons la branche 5.6 comme une branche stable de SObjectizer, qui sera pertinente jusqu'à l'apparition de la première version de SObjectizer-5.7.

Je voudrais publier la version 5.6.0 au début de 2019, provisoirement en février.

3. Après avoir stabilisé la branche 5.6, nous aimerions commencer à travailler sur la branche 5.7, dans laquelle nous pourrions réviser certains principes de base du travail de SObjectizer. Par exemple, abandonnez complètement les répartiteurs publics pour ne laisser que des répartiteurs privés. Refaire le mécanisme des coopératives et leurs relations parents-enfants, éliminant ainsi le goulot d'étranglement lors de l'enregistrement / de la désinscription des coopératives. Supprimer la division par message / signal. Autorisez uniquement send / send_delayed / send_periodic à envoyer des messages, et masquez les méthodes delivery_message et schedule_timer «sous le capot». Modifiez le mécanisme de distribution des messages de manière à supprimer complètement dynamic_casts de ce processus ou à les réduire au minimum.

En général, il y a où se retourner. Dans le même temps, SObjectizer-5.7 nécessitera déjà C ++ 17, sans égard à C ++ 14.

Si vous regardez des choses sans lunettes roses, c'est bien si la version 5.7.0 a lieu à la fin de l'automne 2019. la principale version de travail de SObjectizer pour 2019 sera la branche 5.6.

4. Parallèlement à tout cela, so_5_extra va se développer. Probablement, la version so_5_extra-2 sera publiée avec SObjectizer-5.6, qui incorporera de nouvelles fonctionnalités au cours de 2019, mais basé sur SObjectizer-5.6.

Ainsi, nous voyons nous-mêmes une évolution progressive de SObjectizer-5 avec une révision progressive de certains des principes de base de SObjectizer-5. Dans le même temps, nous essaierons de le faire le plus en douceur possible afin qu'il soit possible de passer d'une version à l'autre avec un minimum de douleur.

Cependant, si quelqu'un souhaite des changements plus spectaculaires et significatifs de SObjectizer, alors nous avons quelques réflexions à ce sujet . En bref: vous pouvez refaire SObjectizer comme vous le souhaitez, jusqu'à implémenter SObjectizer-6 pour un autre langage de programmation. Mais nous ne le ferons pas complètement à nos propres frais, comme cela se produit avec l'évolution de SObjectizer-5.

C’est probablement tout.Les commentaires sur l'article précédent se sont révélés être une discussion bonne et constructive. Il serait utile pour nous qu'une discussion similaire ait lieu cette fois. Comme toujours, nous sommes prêts à répondre à toutes les questions, mais aux plus sensées, et avec plaisir.

Et aux lecteurs les plus patients qui ont atteint ces lignes, merci beaucoup pour le temps passé à lire l'article.

Source: https://habr.com/ru/post/fr426983/


All Articles