La première version de SObjectizer dans le cadre de la branche 5.5 a été publiée il y a un peu plus de quatre ans - début octobre 2014. Et aujourd'hui,
la prochaine version est sortie sous le numéro 5.5.23 , ce qui
clôturera très probablement l'histoire de SObjectizer-5.5. À mon avis, c'est une excellente raison de regarder en arrière et de voir ce qui a été fait au cours des quatre dernières années.
Dans cet article, je vais essayer d'analyser de manière abstraite les changements et innovations les plus importants et significatifs: ce qui a été ajouté, pourquoi, comment cela a affecté le SObjectizer lui-même ou son utilisation.
Peut-être que quelqu'un sera intéressé par une telle histoire du point de vue de l'archéologie. Et quelqu'un, peut-être, sera tenu à l'écart d'une aventure aussi douteuse que le développement de son propre framework d'acteur pour C ++;)
Une petite digression lyrique sur le rôle des anciens compilateurs C ++
L'histoire de SObjectizer-5 a commencé à la mi-2010. Dans le même temps, nous nous sommes immédiatement concentrés sur C ++ 0x. Déjà en 2011, les premières versions de SObjectizer-5 ont commencé à être utilisées pour écrire du code de production. Il est clair que nous n'avions pas alors de compilateurs avec un support C ++ 11 normal.
Pendant longtemps, nous n'avons pas pu utiliser pleinement toutes les fonctionnalités du "C ++ moderne": modèles variadic, noexcept, constexpr, etc. Cela ne pouvait qu'affecter l'API SObjectizer. Et cela a affecté pendant très, très longtemps. Par conséquent, si lors de la lecture de la description d'une fonctionnalité, vous avez une question: «Pourquoi cela n'a-t-il pas été fait auparavant?», La réponse à cette question est très probablement: «Parce que cela n'était pas possible auparavant».
Qu'est-ce qui est apparu et / ou changé dans SObjectizer-5.5 dans le passé?
Dans cette section, nous allons passer en revue un certain nombre de fonctionnalités qui ont eu un impact significatif sur SObjectizer. L'ordre dans cette liste est aléatoire et n'est pas lié à la «signification» ou au «poids» des caractéristiques décrites.
Rejeter l'espace de noms so_5 :: rt
Qu'est-ce qui se passait?
Initialement, dans le cinquième SObjectizer, tout ce qui concerne le runtime SObjectizer a été défini dans l'espace de noms so_5 :: rt. Par exemple, nous avions so_5 :: rt :: environment_t, so_5 :: rt :: agent_t, so_5 :: rt :: message_t, etc. Ce que vous pouvez voir, par exemple, dans l'exemple traditionnel HelloWorld de SO-5.5.0:
#include <so_5/all.hpp> class a_hello_t : public so_5::rt::agent_t { public: a_hello_t( so_5::rt::environment_t & env ) : so_5::rt::agent_t( env ) {} void so_evt_start() override { std::cout << "Hello, world! This is SObjectizer v.5." << std::endl; so_environment().stop(); } void so_evt_finish() override { std::cout << "Bye! This was SObjectizer v.5." << std::endl; } }; int main() { try { so_5::launch( []( so_5::rt::environment_t & env ) { env.register_agent_as_coop( "coop", new a_hello_t( env ) ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; }
L'abréviation "rt" signifie run-time. Et il nous a semblé que l'enregistrement «so_5 :: rt» est bien meilleur et plus pratique que «so_5 :: runtime».
Mais il s'est avéré que pour beaucoup de gens, «rt» n'est que «en temps réel» et rien d'autre. Et l'utilisation de "rt" comme raccourci pour "runtime" viole tellement leurs sentiments que parfois les annonces des versions de SObjectizer dans RuNet se transformaient en holivar sur le sujet d'une interprétation [non] permise de "rt" autre que "en temps réel".
Au final, on en a assez. Et nous venons de décompresser l'espace de noms so_5 :: rt.
Qu'est devenu?
Tout ce qui a été défini dans "so_5 :: rt" est simplement passé à "so_5". En conséquence, le même HelloWorld ressemble maintenant à ceci:
#include <so_5/all.hpp> class a_hello_t : public so_5::agent_t { public: a_hello_t( context_t ctx ) : so_5::agent_t( ctx ) {} void so_evt_start() override { std::cout << "Hello, world! This is SObjectizer v.5 (" << SO_5_VERSION << ")" << std::endl; so_environment().stop(); } void so_evt_finish() override { std::cout << "Bye! This was SObjectizer v.5." << std::endl; } }; int main() { try { so_5::launch( []( so_5::environment_t & env ) { env.register_agent_as_coop( "coop", env.make_agent<a_hello_t>() ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; }
Mais les anciens noms de "so_5 :: rt" sont restés disponibles malgré tout en utilisant s (typedefs). Le code écrit pour les premières versions de SO-5.5 est donc également utilisable dans les versions récentes de SO-5.5.
Enfin, l'espace de noms so_5 :: rt sera supprimé dans la version 5.6.
Quel impact cela a-t-il eu?
Probablement, le code sur SObjectizer est maintenant plus lisible. Pourtant, so_5 :: send () est mieux perçu que so_5 :: rt :: send ().
Eh bien, ici, comme avec les développeurs SObjectizer, le mal de tête a diminué. Il y avait trop de bavardages vides et de raisonnements inutiles autour des annonces de SObjectizer à la fois (à partir des questions «Pourquoi les acteurs sont-ils nécessaires en C ++ en général» et se terminant par «Pourquoi n'utilisez-vous pas PascalCase pour nommer des entités»). Un sujet inflammable est devenu moins et c'était bien :)
Simplifiez l'envoi de messages et l'évolution des gestionnaires de messages
Qu'est-ce qui se passait?
Même dans les toutes premières versions de SObjectizer-5.5, le message habituel était envoyé à l'aide de la méthode delivery_message, qui devait être appelée sur la mbox du destinataire. Pour envoyer un message en attente ou périodique, il fallait appeler single_timer / schedule_timer sur un objet de type environment_t. Et l'envoi d'une demande synchrone à un autre agent nécessitait généralement toute une chaîne d'opérations. Voici, par exemple, à quoi tout cela aurait pu ressembler il y a quatre ans (std :: make_unique (), qui n'était pas encore disponible en C ++ 11):
De plus, le format des gestionnaires de messages dans SObjectizer a évolué vers la version 5.5. Si initialement dans SObjectizer-5, tous les gestionnaires doivent avoir le format:
void evt_handler(const so_5::event_data_t<Msg> & cmd);
puis au fil du temps, quelques autres ont été ajoutés aux formats autorisés:
Les nouveaux formats de gestionnaire sont devenus largement utilisés depuis peindre constamment «const so_5 :: event_data_t <Msg> &» est toujours un plaisir. Mais, d'un autre côté, les formats plus simples n'étaient pas adaptés aux agents modèles. Par exemple:
template<typename Msg_To_Process> class my_actor : public so_5::agent_t { void on_receive(const Msg_To_Process & msg) {
Un tel agent modèle ne fonctionnera que si Msg_To_Process est un type de message, pas un type de signal.
Qu'est devenu?
Dans la branche 5.5, une famille de fonctions d'envoi est apparue et a considérablement évolué. Pour ce faire, tout d'abord, j'ai dû mettre à ma disposition des compilateurs prenant en charge les modèles variadic. Et, d'autre part, pour accumuler une expérience suffisante de travail à la fois avec les modèles variadic en général et avec les premières versions des fonctions d'envoi. De plus, dans différents contextes: dans les agents ordinaires et dans les agents ad-hoc, et dans les agents qui sont implémentés par des classes modèles et les agents externes en général. Y compris lors de l'utilisation de fonctions d'envoi avec des chaînes (elles seront discutées ci-dessous).
En plus d'envoyer des fonctions, des fonctions request_future / request_value sont apparues, conçues pour une interaction synchrone entre les agents.
Par conséquent, l'envoi de messages est désormais le suivant:
Un autre format possible pour les gestionnaires de messages a été ajouté. De plus, c'est ce format qui sera laissé dans les prochaines versions majeures de SObjectizer comme principal (et, peut-être, le seul). Il s'agit du format suivant:
ret_type evt_handler(so_5::mhood_t<Msg> cmd);
Où Msg peut être un type de message ou un type de signal.
Ce format brouille non seulement la ligne entre les agents sous forme de classes ordinaires et les agents sous forme de classes modèles. Mais cela simplifie également la transmission du message / signal (grâce à la famille de fonctions d'envoi):
void my_agent::on_msg(mhood_t<Some_Msg> cmd) { ...
Quel impact cela a-t-il eu?
L'apparition de fonctions d'envoi et de gestionnaires de messages recevant mhood_t <Msg>, nous pouvons dire, a fondamentalement changé le code dans lequel les messages sont envoyés et traités. C'est juste le cas quand il ne reste qu'à regretter qu'au tout début des travaux sur SObjectizer-5, nous n'avions ni compilateurs prenant en charge les modèles variadiques, ni expérience de leur utilisation. La famille des fonctions d'envoi et mhood_t aurait dû être dès le début. Mais l'histoire s'est développée comme elle s'est développée ...
Prise en charge des types de messages personnalisés
Qu'est-ce qui se passait?
Initialement, tous les messages envoyés étaient censés être des classes descendantes de la classe so_5 :: message_t. Par exemple:
struct my_message : public so_5::message_t { ...
Bien que le cinquième SObjectizer n'ait été utilisé que par nous-mêmes, cela n'a soulevé aucune question. Eh bien, comme ça et comme ça.
Mais dès que des utilisateurs tiers ont commencé à s'intéresser à SObjectizer, nous sommes immédiatement tombés sur une question récurrente: "Dois-je hériter un message de so_5 :: message_t?" Ce problème était particulièrement pertinent dans les situations où il était nécessaire d'envoyer des objets de types en tant que messages que l'utilisateur ne pouvait pas influencer du tout. Supposons qu'un utilisateur utilise un SObjectizer et une autre bibliothèque externe. Et dans cette bibliothèque externe, il existe un certain type M, dont l'utilisateur souhaite envoyer des messages. Eh bien et comment dans de telles conditions se faire des amis de type M et so_5 :: message_t? Seuls les wrappers supplémentaires que l'utilisateur devait écrire manuellement.
Qu'est devenu?
Nous avons ajouté la possibilité d'envoyer des messages à SObjectizer-5.5 même si le type de message n'est pas hérité de so_5 :: message_t. C'est-à-dire L'utilisateur peut désormais facilement écrire:
so_5::send<std::string>(mbox, "Hello, World!");
So_5 :: message_t reste de toute façon sous le capot, juste à cause du modèle magic send () comprend que std :: string n'est pas hérité de so_5 :: message_t et pas une simple std :: string n'est construite à l'intérieur de send, mais un héritier spécial de so_5 :: message_t, dans lequel se trouve déjà la chaîne std :: de l'utilisateur.
La magie du modèle similaire s'applique aux abonnements. Lorsque SObjectizer voit un gestionnaire de messages du formulaire:
void evt_handler(mhood_t<std::string> cmd) {...}
alors SObjectizer comprend qu'en fait un message spécial viendra avec l'objet std :: string à l'intérieur. Et ce dont vous avez besoin pour appeler le gestionnaire en lui passant un lien vers std :: string à partir de ce message spécial.
Quel impact cela a-t-il eu?
L'utilisation de SObjectizer est devenue plus facile, en particulier lorsque vous devez envoyer non seulement des objets de vos propres types en tant que messages, mais également des objets de bibliothèques externes. Plusieurs personnes ont même pris le temps de vous remercier spécialement pour cette fonctionnalité.
Messages mutables
Qu'est-ce qui se passait?
Initialement, dans SObjectizer-5, seul le modèle d'interaction 1: N a été utilisé. C'est-à-dire un message envoyé peut avoir plusieurs destinataires (ou il peut y en avoir plusieurs). Même si les agents devaient interagir en mode 1: 1, ils communiquaient toujours via une boîte aux lettres multi-producteurs / multi-consommateurs. C'est-à-dire en mode 1: N, juste N dans ce cas était strictement une unité.
Dans les conditions où un message peut être reçu par plusieurs agents destinataires, les messages envoyés doivent être immuables. C'est pourquoi les gestionnaires de messages avaient les formats suivants:
En général, une approche simple et compréhensible. Cependant, ce n'est pas très pratique lorsque les agents doivent communiquer entre eux en mode 1: 1 et, par exemple, se transférer la propriété de certaines données. Disons qu'un message aussi simple ne peut pas être fait si tous les messages sont des objets strictement immuables:
struct process_image : public so_5::message_t { std::unique_ptr<gif_image> image_; process_image(std::unique_ptr<gif_image> image) : image_{std::move(image)) {} };
Plus précisément, un tel message pourrait être envoyé. Mais l'ayant reçu comme un objet constant, il n'aurait pas été possible de supprimer le contenu de process_image :: image_ pour lui-même. Je devrais marquer un tel attribut comme mutable. Mais alors nous perdrions le contrôle du compilateur si process_image est envoyé pour une raison quelconque en mode 1: N.
Qu'est devenu?
Dans SObjectizer-5.5, la possibilité d'envoyer et de recevoir des messages modifiables a été ajoutée. Dans le même temps, l'utilisateur doit spécialement marquer le message lors de son envoi et lors de son abonnement.
Par exemple:
Pour SObjectizer, my_message et mutable_msg <my_message> sont deux types de messages différents.
Lorsqu'une fonction d'envoi voit qu'on lui demande d'envoyer un message modifiable, la fonction d'envoi vérifie à quelle boîte aux lettres elle essaie d'envoyer le message. S'il s'agit d'une boîte multi-consommateurs, l'envoi n'est pas effectué, mais une exception est levée avec le code d'erreur correspondant. C'est-à-dire SObjectizer garantit que les messages modifiables ne peuvent être utilisés que lorsqu'ils interagissent en mode 1: 1 (via des boîtes aux lettres à consommateur unique ou des mchains, qui sont une forme de boîtes aux lettres à consommateur unique). Pour fournir cette garantie, soit dit en passant, SObjectizer interdit d'envoyer des messages modifiables sous forme de messages périodiques.
Quel impact cela a-t-il eu?
Avec des messages mutables, cela s'est avéré de façon inattendue. Nous les avons ajoutés à SObjectizer à la suite d'une discussion en marge d'un
rapport sur SObjectizer à C ++ Russia-2017 . Avec le sentiment, "Eh bien, s'ils demandent, alors quelqu'un en a besoin, donc ça vaut le coup d'essayer." Eh bien, ils l'ont fait sans grand espoir d'une demande généralisée. Bien que pour cela, j'ai dû "fumer du bambou" pendant très longtemps avant de penser à comment ajouter des messages modifiables à SO-5.5 sans rompre la compatibilité.
Mais lorsque des messages mutables sont apparus dans SObjectizer, il s'est avéré qu'il n'y avait pas si peu d'applications pour eux. Et que les messages mutables sont utilisés de manière surprenante souvent (mention de cela peut être trouvée
dans la deuxième partie de l'histoire du projet de démonstration de Shrimp ). Donc, dans la pratique, cette fonctionnalité était plus qu'utile, car il vous permet de résoudre des problèmes qui, sans le support des messages modifiables au niveau de SObjectizer, n'avaient pas de solution normale.
Agents de la machine d'état hiérarchique
Qu'est-ce qui se passait?
Les agents de SObjectizer étaient à l'origine des machines à états. Les agents devaient décrire explicitement les états et s'abonner aux messages dans des états spécifiques.
Par exemple:
class worker : public so_5::agent_t { state_t st_free{this, "free"}; state_t st_bufy{this, "busy"}; ... void so_define_agent() override {
Mais c'étaient de simples machines à états. Les États ne pouvaient pas être imbriqués les uns dans les autres. Il n'y avait aucun support pour les gestionnaires d'entrée et de sortie de l'État. Il n'y avait aucune restriction sur le temps passé dans l'État.
Même un support aussi limité pour les machines d'état était pratique et nous l'avons utilisé pendant plus d'un an. Mais à un moment donné, nous voulions plus.
Qu'est devenu?
SObjectizer introduit la prise en charge des machines à états hiérarchiques.
Désormais, les états peuvent être imbriqués les uns dans les autres. Les gestionnaires d'événements des états parents sont automatiquement "hérités" par les états enfants.
Les gestionnaires d'entrée et de sortie d'un état sont pris en charge.
Il est possible de fixer une limite au temps pendant lequel l'agent reste dans l'état.
Il est possible de garder une histoire pour l'État.
Pour ne pas être infondé, voici un exemple d'agent qui n'est pas une machine à états hiérarchique complexe (le code de l'exemple standard est clignotant_led):
class blinking_led final : public so_5::agent_t { state_t off{ this }, blinking{ this }, blink_on{ initial_substate_of{ blinking } }, blink_off{ substate_of{ blinking } }; public : struct turn_on_off final : public so_5::signal_t {}; blinking_led( context_t ctx ) : so_5::agent_t{ ctx } { this >>= off; off.just_switch_to< turn_on_off >( blinking ); blinking.just_switch_to< turn_on_off >( off ); blink_on .on_enter( []{ std::cout << "ON" << std::endl; } ) .on_exit( []{ std::cout << "off" << std::endl; } ) .time_limit( std::chrono::milliseconds{1250}, blink_off ); blink_off .time_limit( std::chrono::milliseconds{750}, blink_on ); } };
Nous avons déjà décrit tout cela
dans un article séparé , il n'est pas nécessaire de le répéter.
Il n'y a actuellement aucun support pour les états orthogonaux. Mais ce fait a deux explications. Premièrement, nous avons essayé d'apporter cet appui et avons rencontré un certain nombre de difficultés, dont la résolution nous a paru trop coûteuse. Et, deuxièmement, personne n'a encore demandé d'états orthogonaux. Lorsqu'on lui a demandé, revenons à ce sujet.
Quel impact cela a-t-il eu?
On a l'impression que c'est très grave (bien que nous soyons ici, bien sûr, subjectifs et partiaux). Après tout, c'est une chose lorsque, face à des machines à états finis complexes dans le domaine, vous commencez à chercher des solutions de contournement, simplifiez quelque chose, dépensez plus de force sur quelque chose. Et c'est une tout autre chose lorsque vous pouvez mapper des objets de votre application à votre code C ++ presque 1-en-1.
De plus, à en juger par les questions posées, par exemple, par le comportement des gestionnaires d'entrée / sortie dans / hors de l'état, cette fonctionnalité est utilisée.
mchain's
Qu'est-ce qui se passait?
C'était une situation intéressante. SObjectizer était souvent utilisé de sorte que seule une partie de l'application était écrite dans SObjectizer. Le reste du code de l'application pourrait n'avoir rien à voir avec les acteurs en général, ou avec SObjectizer en particulier. Par exemple, une application GUI dans laquelle un SObjectizer est utilisé pour certaines tâches d'arrière-plan, tandis que le travail principal est effectué sur le thread principal de l'application.
Et dans de tels cas, il s'est avéré que de la partie non SObjectizer à la partie SObjectizer, l'envoi d'informations est aussi simple que simple: il suffit d'appeler des fonctions d'envoi ordinaires. Mais la diffusion d'informations en sens inverse n'est pas si simple. Il nous a semblé que ce n'est pas bon et que vous devriez avoir des canaux de communication pratiques entre les parties SObjectizer de l'application et les parties non-SObjectizer directement hors de la boîte.
Qu'est devenu?
Ainsi, les chaînes de messages ou, dans la notation plus familière, les mchains sont apparues dans SObjectizer.
Mchain est une variante spécifique d'une boîte aux lettres à consommateur unique où les messages sont envoyés par des fonctions d'envoi régulières. Mais pour extraire des messages de mchain, vous n'avez pas besoin de créer d'agents et de les signer. Il existe deux fonctions spéciales qui peuvent être appelées même à l'intérieur des agents, même à l'extérieur des agents: recevoir () et sélectionner (). Le premier lit les messages d'un seul canal, tandis que le second peut lire les messages de plusieurs canaux à la fois:
using namespace so_5; mchain_t ch1 = env.create_mchain(...); mchain_t ch2 = env.create_mchain(...); select( from_all().handle_n(3).empty_timeout(200ms), case_(ch1, [](mhood_t<first_message_type> msg) { ... }, [](mhood_t<second_message_type> msg) { ... }), case_(ch2, [](mhood_t<third_message_type> msg ) { ... }, [](mhood_t<some_signal_type>){...}, ... ));
Nous avons déjà parlé de mchain à plusieurs reprises ici:
en août 2017 et
en mai 2018 . Par conséquent, en particulier sur le sujet de l'apparence du travail avec les chaînes, nous n'approfondirons pas ici.
Quel impact cela a-t-il eu?
Après l'apparition de mchains dans SObjectizer-5.5, il s'est avéré que SObjectizer, en fait, est devenu encore moins un framework «acteur» qu'auparavant. En plus de prendre en charge le modèle d'acteur et Pub / Sub, SObjectizer a également ajouté la prise en charge du modèle CSP (communicating sequential process). Mchains vous permet de développer des applications multi-threads assez complexes sur SObjectizer sans aucun acteur. Et pour certaines tâches, c'est plus que pratique. Ce que nous utilisons nous-mêmes de temps en temps.
Mécanisme de limitation des messages
Qu'est-ce qui se passait?
L'une des lacunes les plus graves du modèle des acteurs est sa prédisposition à la surcharge. Il est très facile de se retrouver dans une situation où l'acteur expéditeur envoie des messages à l'acteur destinataire à un rythme plus rapide que l'acteur destinataire peut traiter les messages.
En règle générale, l'envoi de messages dans les frameworks d'acteurs est une opération non bloquante. Par conséquent, lorsqu'une paire de «producteur agile et consommateur ringard» se produit, la file d'attente de l'acteur destinataire augmente alors qu'il existe au moins une sorte de mémoire libre.
La principale difficulté de ce problème est qu'un bon mécanisme de protection contre la surcharge doit être affiné pour la tâche appliquée et les caractéristiques du domaine. Par exemple, pour comprendre quels messages peuvent être dupliqués (et donc pouvoir éliminer les doublons en toute sécurité). Pour comprendre quels messages ne peuvent pas être jetés de toute façon. Qui peut être suspendu et de combien, et qui n'est pas autorisé du tout. Etc., etc.
Une autre difficulté est qu'il n'est pas toujours nécessaire d'avoir un bon mécanisme de défense. Parfois, il suffit d'avoir quelque chose de primitif, mais efficace, accessible et prêt à l'emploi.
Afin de ne pas forcer l'utilisateur à faire son contrôle de surcharge là où il suffit de jeter simplement des messages «supplémentaires» ou de transmettre ces messages à un autre agent.Qu'est devenu?
Juste pour que, dans des scénarios simples, vous puissiez utiliser des outils de protection contre les surcharges prêts à l'emploi, limites de messages. Ce mécanisme vous permet de supprimer les messages inutiles, ou de les envoyer à d'autres destinataires, ou même simplement d'interrompre l'application si les limites sont dépassées. Par exemple:
class worker : public so_5::agent_t { public: worker(context_t ctx) : so_5::agent_t{ ctx
Cette rubrique est décrite plus en détail dans un article séparé .Quel impact cela a-t-il eu?
Cela ne veut pas dire que l'émergence de limites de message est devenue quelque chose qui a fondamentalement changé SObjectizer, les principes de son travail ou travailler avec lui. Il peut plutôt être comparé à un parachute de secours, qui n'est utilisé qu'en dernier recours. Mais quand vous devez l'utiliser, vous êtes content qu'il existe même.Mécanisme de suivi de la remise des messages
Qu'est-ce qui se passait?
SObjectizer-5 était une boîte noire pour les développeurs. Dans lequel le message est envoyé et ... Et soit il arrive au destinataire, soit il ne vient pas.Si le message n'atteint pas le destinataire, alors l'utilisateur est confronté à la nécessité de passer par une quête passionnante à la recherche d'une raison. Dans la plupart des cas, les raisons sont triviales: soit le message a été envoyé à la mauvaise mbox, soit l'abonnement n'a pas été effectué (par exemple, l'utilisateur a effectué un abonnement dans un état de l'agent, mais a oublié de le faire dans un autre). Mais il peut y avoir des cas plus complexes lorsqu'un message est, par exemple, rejeté par un mécanisme de protection contre les surcharges.Le problème était que le mécanisme de livraison des messages était caché au fond des abats de SObjectizer Run-Time et, par conséquent, il était difficile même pour les développeurs de SObjectizer d'acheminer le message vers le destinataire, sans parler des utilisateurs. Surtout pour les utilisateurs novices qui ont commis le plus grand nombre d'erreurs aussi triviales.Qu'est devenu?
Dans SObjectizer-5.5, un mécanisme spécial de traçage du processus de remise des messages appelé traçage de remise des messages (ou simplement msg_tracing) a été ajouté, puis finalisé. Ce mécanisme et ses capacités ont été décrits plus en détail dans un article séparé .Alors maintenant, si des messages sont perdus à la livraison, vous pouvez simplement activer msg_tracing et voir pourquoi cela se produit.Quel impact cela a-t-il eu?
Le débogage d'applications écrites dans SObjectizer est devenu beaucoup plus simple et plus agréable. Même pour nous .Le concept d'env_infrastructure et d'env_infrastructures monothread
Qu'est-ce qui se passait?
Nous avons toujours considéré SObjectizer comme un outil pour simplifier le développement de code multi-thread. Par conséquent, les premières versions de SObjectizer-5 ont été écrites pour fonctionner uniquement dans un environnement multithread.Cela a été exprimé par l'utilisation de primitives de synchronisation à l'intérieur du SObjectizer pour protéger les composants internes du SObjectizer lorsque vous travaillez dans un environnement multithread. C'est donc dans la création de plusieurs threads de travail auxiliaires à l'intérieur du SObjectizer lui-même (pour effectuer des opérations importantes telles que l'entretien du temporisateur et la suppression de l'enregistrement des coopérations d'agent).C'est-à-dire
SObjectizer a été créé pour la programmation multi-thread et pour une utilisation dans des environnements multi-thread. Et cela nous convenait parfaitement.Cependant, comme le SObjectizer était utilisé «à l'état sauvage», des situations ont été découvertes où la tâche était suffisamment difficile pour utiliser des acteurs dans sa solution. Mais, en même temps, tout le travail pouvait et, en outre, devait être exécuté sur un seul flux de travail.Et nous avons été confrontés à un problème très intéressant: est-il possible d'apprendre à SObjectizer à travailler sur un seul thread de travail?Qu'est devenu?
Il s'est avéré que c'était possible.Cela nous a coûté beaucoup d'argent, il a fallu beaucoup de temps et d'efforts pour trouver une solution. Mais la solution a été inventée.Un concept tel que l'infrastructure d'environnement a été introduit (ou env_infrastructure sous une forme légèrement abrégée). Env_infrastructure a pris en charge la gestion de la cuisine interne de SObjectizer. En particulier, il a résolu des problèmes tels que l'entretien des minuteries, l'enregistrement et la radiation des coopératives.Pour SObjectizer, plusieurs options env_infrastructures à thread unique ont été faites. Cela nous a permis de développer des applications monothread sur SObjectizer, à l'intérieur desquelles se trouvent des agents normaux échangeant des messages réguliers entre eux.Nous avons parlé de cette fonctionnalité plus en détail dans un article séparé. .Quel impact cela a-t-il eu?
Peut-être la chose la plus importante qui s'est produite lors de la mise en œuvre de cette fonctionnalité a été la rupture de nos propres modèles. Un regard sur SObjectizer ne sera plus jamais le même. Pendant tant d'années, considérez SObjectizer exclusivement comme un outil pour développer du code multi-thread. Et puis encore! Et trouvez que le code à thread unique sur SObjectizer peut également être développé. La vie est pleine de surprises.Outils de surveillance d'exécution
Qu'est-ce qui se passait?
SObjectizer-5 était une boîte noire non seulement en termes de mécanisme de livraison de messages. Mais il n'y avait également aucun moyen de savoir combien d'agents travaillent actuellement dans l'application, combien et quels répartiteurs sont créés, combien de threads de travail sont impliqués, combien de messages attendent dans les files d'attente des répartiteurs, etc.Toutes ces informations sont très utiles pour surveiller les applications fonctionnant 24h / 24 et 7j / 7. Mais pour le débogage, je voudrais également comprendre de temps en temps si les files d'attente augmentent ou si le nombre d'agents augmente / diminue.Malheureusement, pour l'instant, nos mains n'ont tout simplement pas atteint le point d'ajouter des fonds à SObjectizer pour collecter et diffuser ces informations.Qu'est devenu?
À un moment donné dans SObjectizer-5.5, des outils sont apparus pour surveiller l'exécution des composants internes de SObjectizer . Par défaut, la surveillance de l'exécution est désactivée, mais si vous l'activez, des messages seront envoyés régulièrement à la mbox spéciale, à l'intérieur de laquelle il y aura des informations sur le nombre d'agents et de coopérations, sur le nombre de temporisateurs, sur les threads de travail appartenant aux répartiteurs (et il y aura déjà des informations sur le nombre de messages dans les files d'attente, le nombre d'agents liés à ces threads).De plus, au fil du temps, il est devenu possible d'activer en outre la collecte d'informations sur le temps que les agents passent à l'intérieur des gestionnaires d'événements. Cela vous permet de détecter les situations où certains agents sont trop lents (ou perdent du temps à bloquer les appels).Quel impact cela a-t-il eu?
Dans notre pratique, la surveillance d'exécution n'est pas souvent utilisée. Mais quand vous en avez besoin, vous vous rendez compte de son importance. En effet, sans un tel mécanisme, il est impossible (enfin, ou très difficile) de déterminer ce qui ne fonctionne pas et comment.Il s'agit donc d'une caractéristique de la catégorie «vous pouvez le faire», mais sa présence, à notre avis, transfère immédiatement l'instrument à une autre catégorie de poids. Parce que
faire un prototype de cadre d'acteur "à genoux" n'est pas si difficile. Beaucoup l'ont fait et beaucoup d'autres le feront. Mais ensuite, pour équiper votre développement d'une telle chose que la surveillance de l'exécution ... À l'heure actuelle, tous les projets dessinés au genou ne survivent pas.Et encore une chose en une seule ligne
Pendant quatre ans, SObjectizer-5.5 a eu beaucoup d'innovations et de changements, dont la description, même dans un synopsis, prendra trop de place. Par conséquent, nous en désignons une partie par littéralement une ligne. Dans un ordre aléatoire, sans aucune priorité.SObjectizer-5.5 ajoute la prise en charge du système de construction CMake.Désormais, SObjectizer-5 peut être construit à la fois en tant que bibliothèque dynamique et statique.SObjectizer-5.5 est maintenant construit et fonctionne sur Android (à la fois via CrystaX NDK et via le nouveau NDK Android).Des répartiteurs privés sont apparus. Vous pouvez maintenant créer et utiliser des répartiteurs que personne d'autre ne voit.Implémentation du mécanisme de filtres de livraison. Désormais, lorsque vous vous abonnez à des messages provenant de MPMC-mbox, vous pouvez interdire la remise de messages dont le contenu ne vous intéresse pas.Les outils de création et d'enregistrement des coopérations ont été considérablement simplifiés: les méthodes introduisent_coop / introduisent_enfant_coop, make_agent / make_agent_with_binder et c'est tout.Le concept d'une usine d'objets de verrouillage est apparu et vous pouvez maintenant choisir les objets de verrouillage dont vous avez besoin (basés sur des mutex-s, des verrous tournants, combinés ou d'autres).La classe wrapped_env_t est apparue et vous pouvez maintenant exécuter SObjectizer dans votre application non seulement avec so_5 :: launch ().Le concept de stop_guards est apparu et vous pouvez maintenant influencer le processus d'arrêt de SObjectizer. Par exemple, vous pouvez empêcher l'arrêt de SObjectizer jusqu'à ce que certains agents aient terminé leur travail d'application.Vous pouvez désormais intercepter les messages qui ont été remis à l'agent mais qui n'ont pas été traités par l'agent (les soi-disant dead_letter_handlers).Il était possible d'envelopper les messages dans des "enveloppes" spéciales. Les enveloppes peuvent contenir des informations supplémentaires sur le message et peuvent effectuer une action lorsque le message est remis au destinataire.5.5.0 à 5.5.23 en chiffres
Il est également intéressant de regarder le chemin parcouru en termes de code / tests / exemples. Voici ce que l'utilitaire cloc nous dit sur la quantité de code du noyau SObjectizer-5.5.0: -------------------------------------------------- -----------------------------
Code de commentaire vierge des fichiers de langue
-------------------------------------------------- -----------------------------
En-tête C / C ++ 58 2119 5156 5762
C ++ 39 1167 779 4759
Rubis 2 30 2 75
-------------------------------------------------- -----------------------------
SOMME: 99 3316 5937 10596
-------------------------------------------------- -----------------------------
Et voici la même chose, mais pour la v.5.5.23 (dont 1147 lignes sont le code de la bibliothèque optionnelle-lite): -------------------------------------------------- -----------------------------
Code de commentaire vierge des fichiers de langue
-------------------------------------------------- -----------------------------
En-tête C / C ++ 133 6279 22173 21068
C ++ 53 2498 2760 10398
CMake 2 29 0 177
Rubis 4 53 2 129
-------------------------------------------------- -----------------------------
SOMME: 192 8859 24935 31772
-------------------------------------------------- -----------------------------
Volume de tests pour v.5.5.0: -------------------------------------------------- -----------------------------
Code de commentaire vierge des fichiers de langue
-------------------------------------------------- -----------------------------
C ++ 84 2510 390 11540
Rubis 162 496 0 1054
En-tête C / C ++ 1 11 0 32
-------------------------------------------------- -----------------------------
SOMME: 247 3017390 12626
-------------------------------------------------- -----------------------------
Tests pour v.5.5.23: -------------------------------------------------- -----------------------------
Code de commentaire vierge des fichiers de langue
-------------------------------------------------- -----------------------------
C ++ 324 7345 1305 35231
Rubis 675 2353 0 4671
CMake 338 43 0 955
En-tête C / C ++ 11107 3448
-------------------------------------------------- -----------------------------
SOMME: 1348 9848 1308 41305
Eh bien, des exemples pour la v.5.5.0: -------------------------------------------------- -----------------------------
Code de commentaire vierge des fichiers de langue
-------------------------------------------------- -----------------------------
C ++ 27 765 463 3322
Rubis 28 95 0192
-------------------------------------------------- -----------------------------
SOMME: 55 860 463 3514
Ils le sont, mais déjà pour la v.5.5.23: -------------------------------------------------- -----------------------------
Code de commentaire vierge des fichiers de langue
-------------------------------------------------- -----------------------------
C ++ 67 2141 2061 9341
Rubis 133451 0868
CMake 67 93 0 595
En-tête C / C ++ 1 12 11 32
-------------------------------------------------- -----------------------------
SOMME: 268 2697 2072 10836
Presque partout, une augmentation de près de trois fois.Et la quantité de documentation pour SObjectizer a probablement encore augmenté.Plans pour un avenir proche (et pas seulement)
Les plans de développement préliminaires de SObjectizer après la sortie de la version 5.5.23 ont été décrits ici il y a environ un mois. Fondamentalement, ils n'ont pas changé. Mais il y avait un sentiment que la version 5.6.0, dont la sortie est prévue pour le début de 2019, devra être positionnée comme le début de la prochaine branche stable de SObjectizer. En gardant à l'esprit que pendant 2019, SObjectizer se développera sous la branche 5.6 sans aucun changement significatif.Cela permettra à ceux qui utilisent désormais SO-5.5 dans leurs projets de passer progressivement à SO-5.6 sans craindre de devoir également passer à SO-5.7.La version 5.7, dans laquelle nous voulons nous permettre de nous éloigner quelque part des principes de base de SO-5.5 et SO-5.6, sera considérée comme expérimentale en 2019. Avec stabilisation et libération, si tout se passe bien, déjà dans la 2020e année.Conclusion
En conclusion, je voudrais remercier tous ceux qui nous ont aidés avec le développement de SObjectizer pendant tout ce temps. Et je voudrais remercier séparément tous ceux qui ont osé essayer de travailler avec SObjectizer. Vos commentaires nous ont toujours été très utiles.Nous voulons dire à ceux qui n'ont pas encore utilisé SObjectizer: essayez-le. Ce n'est pas aussi effrayant que cela puisse paraître.Si vous n’aimiez pas quelque chose ou n’en aviez pas assez dans SObjectizer, dites-le nous. Nous écoutons toujours les critiques constructives. Et, s'il est en notre pouvoir, nous donnons vie aux souhaits des utilisateurs.