
Dieser Artikel ist eine Fortsetzung eines vor einem Monat veröffentlichten Reflexionsartikels: „
Ist es einfach, dem alten Framework neue Funktionen hinzuzufügen? Die Qual der Wahl am Beispiel der Entwicklung von SObjectizer “. In diesem Artikel wurde die Aufgabe beschrieben, die wir in der nächsten Version von SObjectizer lösen wollten, zwei Lösungsansätze untersucht und die Vor- und Nachteile der einzelnen Ansätze aufgelistet.
Im
Laufe der Zeit wurde einer der Ansätze implementiert und neue Versionen von
SObjectizer sowie das dazugehörige
Projekt so_5_extra , das bereits als „
tief atmen “ bezeichnet wurde. Sie können buchstäblich nehmen und versuchen.
Heute werden wir darüber sprechen, was getan wurde, warum es getan wurde, wozu es geführt hat. Wenn jemand daran interessiert ist, zu verfolgen, wie sich eines der wenigen Live-, plattformübergreifenden und Open-Actor-Frameworks für C ++ entwickelt, sind Sie bei cat willkommen.
Wie hat alles angefangen?
Alles begann mit dem Versuch, das Problem der garantierten Stornierung von Timern zu lösen. Das Problem besteht im Wesentlichen darin, dass der Programmierer beim Senden einer verzögerten oder periodischen Nachricht die Zustellung der Nachricht abbrechen kann. Zum Beispiel:
auto timer_id = so_5::send_periodic<my_message>(my_agent, 10s, 10s, ...); ...
Nach dem Aufruf von
timer_id.release () sendet der Timer keine neuen Instanzen der my_message-Nachricht mehr. Aber die Kopien, die bereits gesendet wurden und sich in der Warteschlange der Empfänger befinden, werden nirgendwo hingehen. Im Laufe der Zeit werden sie aus denselben Warteschlangen extrahiert und zur Verarbeitung an die Empfängeragenten übertragen.
Dieses Problem ist eine Folge der Grundprinzipien des Betriebs von SObjectizer-5 und hat keine einfache Lösung, da SObjectizer keine Nachrichten aus Warteschlangen extrahieren kann. Dies ist nicht möglich, da die Warteschlangen in SObjectizer zu Dispatchern gehören, die Disponenten unterschiedlich sind und ihre Warteschlangen auch unterschiedlich organisiert sind. Einschließlich
gibt es Disponenten, die nicht Teil des SObjectizers sind, und SObjectizer kann im Prinzip nicht wissen, wie diese Dispatcher funktionieren.
Im Allgemeinen gibt es eine solche Funktion in den nativen SObjectizer-Timern. Nicht, dass es Entwickler zu sehr verwöhnt. Es ist jedoch besondere Vorsicht geboten. Besonders für Anfänger, die sich gerade erst mit dem Framework vertraut machen.
Und schließlich gingen die Hände so weit, eine Lösung für dieses Problem vorzuschlagen.
Welcher Lösungsweg wurde gewählt?
In einem
früheren Artikel wurden zwei mögliche Optionen in Betracht gezogen. Die erste Option erforderte keine Änderungen am Nachrichtenübermittlungsmechanismus in SObjectizer, aber der Programmierer musste den Typ der gesendeten / empfangenen Nachricht explizit ändern.
Die zweite Option erforderte eine Änderung des SObjectizer-Nachrichtenübermittlungsmechanismus. Es wurde dieser Pfad gewählt, da er es dem Empfänger der Nachricht ermöglichte, die Tatsache zu verbergen, dass die Nachricht auf eine bestimmte Weise gesendet wurde.
Was hat sich in SObjectizer geändert?
Neues Konzept: Umschlag mit Nachricht im Inneren
Die erste Komponente der implementierten Lösung ist das Hinzufügen eines solchen Konzepts als Umschlag zu SObjectizer. Ein Umschlag ist eine spezielle Nachricht, in der sich die aktuelle Nachricht (Nutzlast) befindet. SObjectizer liefert den Umschlag mit der Nachricht fast wie gewohnt an den Empfänger. Der grundlegende Unterschied in der Briefumschlagverarbeitung wird erst in der allerletzten Lieferphase festgestellt:
- Bei der Zustellung einer regulären Nachricht sucht der Empfängeragent einfach nach einem Handler für diesen Nachrichtentyp. Wenn ein solcher Handler gefunden wird, wird der gefundene Handler aufgerufen und die zugestellte Nachricht als Parameter zurückgegeben.
- und bei Zustellung des Umschlags mit der Nachricht, nachdem der Handler gefunden wurde, wird zuerst versucht, die Nachricht aus dem Umschlag herauszuholen. Und nur wenn der Umschlag die darin gespeicherte Nachricht enthält, wird der Handler aufgerufen.
Hier gibt es zwei wichtige Punkte, die einen großen Einfluss darauf haben, warum und wie Nachrichtenumschläge verwendet werden können.
Der erste wichtige Punkt ist, dass eine Nachricht nur dann von einem Umschlag angefordert wird, wenn beim Empfänger ein Nachrichtenhandler gefunden wird. Das heißt, Nur wenn die Nachricht wirklich an den Empfänger übermittelt wurde und der Empfänger hier und jetzt ist, wird diese Nachricht verarbeitet.
Der zweite wichtige Punkt hierbei ist, dass der Umschlag die darin enthaltene Nachricht möglicherweise nicht weitergibt. Das heißt, ein Umschlag kann beispielsweise die aktuelle Uhrzeit überprüfen und feststellen, dass alle Liefertermine versäumt wurden und die Nachricht daher nicht mehr relevant ist und nicht mehr verarbeitet werden kann. Daher gibt der Umschlag die Nachricht nicht aus. Dementsprechend ignoriert SObjectizer diesen Umschlag einfach und ergreift keine zusätzlichen Maßnahmen.
Wie ist ein Umschlag?
Ein Envelope ist eine Implementierung der Envelope_t-Schnittstelle, die wie folgt definiert ist:
class SO_5_TYPE envelope_t : public message_t { public: ...
Das heißt, Ein Umschlag ist im Wesentlichen dieselbe Nachricht wie alle anderen. Aber mit einem speziellen Attribut, das von der Methode so5_message_kind () zurückgegeben wird.
Der Programmierer kann seine Hüllkurven entwickeln, die von Envelope_t (oder bequemer von
so_5 :: extra :: Enveloped_msg :: just_envelope_t )
erben, und die Hook-Methoden handler_found_hook () und transformation_hook () überschreiben.
Innerhalb der Hook-Methoden entscheidet der Umschlagentwickler, ob er die Nachricht innerhalb des Umschlags zur Verarbeitung / Transformation geben möchte oder nicht. Wenn er möchte, muss der Entwickler die invoke () -Methode und das invoker-Objekt aufrufen. Wenn er nicht möchte, ruft er nicht an. In diesem Fall werden der Umschlag und sein Inhalt ignoriert.
Wie lösen Umschläge das Problem des Abbrechens von Timern?
Die Lösung, die jetzt in so_5_extra in Form des Namespace so_5 :: extra :: revocable_timer implementiert ist, ist sehr einfach: Durch spezielles Senden einer ausstehenden oder periodischen Nachricht wird ein spezieller Umschlag erstellt, in dem sich nicht nur die Nachricht selbst, sondern auch das widerrufene Atomflag befindet. Wenn dieses Flag gelöscht ist, wird die Nachricht als relevant angesehen. Wenn gesetzt, gilt die Nachricht als zurückgezogen.
Wenn die Hook-Methode für den Umschlag aufgerufen wird, überprüft der Umschlag den Wert des widerrufenen Flags. Wenn das Flag gesetzt ist, gibt der Umschlag keine Nachricht aus. Somit wird die Nachricht nicht verarbeitet, selbst wenn der Timer es bereits geschafft hat, die Nachricht in die Warteschlange des Empfängers zu stellen.
Schnittstellenerweiterung abstract_message_box_t
Das Hinzufügen der Envelope_t-Schnittstelle ist nur ein Teil der Implementierung von Envelopes in SObjectizer. Der zweite Teil berücksichtigt die Tatsache, dass Umschläge im Nachrichtenübermittlungsmechanismus im SObjectizer vorhanden sind.
Hier konnte man leider nicht darauf verzichten, Änderungen für den Benutzer sichtbar zu machen. Insbesondere in der Klasse abstract_message_box_t, die die Schnittstelle aller Postfächer in SObjectizer definiert, musste eine weitere virtuelle Methode hinzugefügt werden:
virtual void do_deliver_enveloped_msg( const std::type_index & msg_type, const message_ref_t & message, unsigned int overlimit_reaction_deep );
Diese Methode ist für die Zustellung eines Nachrichtenumschlags mit einer Nachricht vom Typ msg_type an den Empfänger verantwortlich. Eine solche Lieferung kann sich in den Implementierungsdetails unterscheiden, je nachdem, um welche Art von Mbox es sich handelt.
Beim Hinzufügen von do_deliver_enveloped_msg () zu abstract_message_box_t hatten wir die Wahl: es zu einer rein virtuellen Methode zu machen oder eine Standardimplementierung anzubieten.
Wenn wir do_deliver_enveloped_msg () zu einer rein virtuellen Methode machen würden, würden wir die Kompatibilität zwischen Versionen von SObjectizer in Zweig 5.5 unterbrechen. Schließlich müssten Benutzer, die ihre eigenen Mbox-Implementierungen geschrieben haben, beim Wechsel zu SObjectizer-5.5.23 ihre eigenen Mboxes ändern, da sie sonst nicht mit der neuen Version von SObjectizer kompilieren könnten.
Wir wollten dies nicht, also haben wir do_deliver_enveloped_msg () in Version 5.5.23 nicht zu einer rein virtuellen Methode gemacht. Es hat eine Standardimplementierung, die nur eine Ausnahme auslöst. Auf diese Weise können benutzerdefinierte Benutzer-mbox-s normal mit normalen Nachrichten weiterarbeiten, lehnen jedoch automatisch die Annahme von Umschlägen ab. Wir fanden dieses Verhalten akzeptabler. Darüber hinaus ist es unwahrscheinlich, dass Umschläge mit Nachrichten in der Anfangsphase weit verbreitet sind, und es ist unwahrscheinlich, dass in den "wilden" benutzerdefinierten Implementierungen von SObjectizer-Mboxen häufig gefunden werden;)
Darüber hinaus besteht bei weitem keine Wahrscheinlichkeit, dass in nachfolgenden Hauptversionen von SObjectizer, in denen die Kompatibilität mit Zweig 5.5 nicht untersucht wird, die Schnittstelle abstract_message_box_t wesentliche Änderungen erfährt. Aber wir sind uns schon voraus ...
So senden Sie Umschläge mit Nachrichten
SObjectizer-5.5.23 selbst bietet keine einfache Möglichkeit zum Senden von Umschlägen. Es wird davon ausgegangen, dass ein bestimmter Umschlagtyp und geeignete Werkzeuge für eine bestimmte Aufgabe entwickelt werden, um Umschläge eines bestimmten Typs bequem zu versenden. Ein Beispiel hierfür ist in
so_5 :: extra :: revocable_timer zu sehen, wo Sie nicht nur den Umschlag senden, sondern dem Benutzer auch eine spezielle timer_id geben müssen.
Für einfachere Situationen können Sie die Tools von
so_5 :: extra :: Enveloped_msg verwenden . So wird beispielsweise eine Nachricht mit einem festgelegten Limit für die Zustellzeit gesendet:
Damit alles Spaß macht: Umschläge in Umschlägen
Umschläge sind so konzipiert, dass sie einige Nachrichten in sich tragen. Aber welche?
Beliebig.
Und das bringt uns zu einer interessanten Frage: Ist es möglich, einen Umschlag in einen anderen Umschlag zu stecken?
Ja, das kannst du. So viel du willst. Die Verschachtelungstiefe wird nur durch den gesunden Menschenverstand des Entwicklers und die Tiefe des Stapels für den rekursiven Aufruf handler_found_hook / transformation_hook begrenzt.
Gleichzeitig richtet sich SObjectizer an die Entwickler eigener Umschläge: Der Umschlag sollte nicht darüber nachdenken, was sich darin befindet - eine bestimmte Nachricht oder ein anderer Umschlag. Wenn die Hook-Methode für den Umschlag aufgerufen wird und der Umschlag entscheidet, dass er seinen Inhalt angeben kann, ruft der Umschlag einfach invoke () auf handler_invoker_t auf und übergibt in invoke () einen Link zu seinem Inhalt. Und schon invoke () im Inneren wird herausfinden, womit es zu tun hat. Wenn dies ein anderer Umschlag ist, ruft invoke () selbst die erforderliche Hook-Methode für diesen Umschlag auf.
Mit dem oben gezeigten Toolkit von so_5 :: extra :: Enveloped_msg kann der Benutzer mehrere verschachtelte Umschläge wie folgt erstellen:
so_5::extra::enveloped_msg::make<my_message>(...)
Einige Beispiele für die Verwendung von Umschlägen
Nachdem wir die Interna von SObjectizer-5.5.23 durchgearbeitet haben, ist es an der Zeit, mit dem für Benutzer nützlicheren Anwendungsteil fortzufahren. Im Folgenden finden Sie einige Beispiele, die entweder auf dem basieren, was bereits in so_5_extra implementiert ist, oder die Tools von so_5_extra verwenden.
Widerrufliche Timer
Da diese ganze Küche mit Umschlägen konzipiert wurde, um das Problem des garantierten Abrufs von Timer-Nachrichten zu lösen, wollen wir sehen, was am Ende passiert ist. Wir werden das Beispiel aus so_5_extra-1.2.0 verwenden, das die Tools aus dem neuen Namespace so_5 :: extra :: revocable_timer verwendet:
Beispielcode mit widerruflichen Timern #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; }
Was haben wir hier?
Wir haben einen Agenten, der zuerst mehrere Timer-Nachrichten initiiert und dann seinen Arbeitsthread für eine Weile blockiert. Während dieser Zeit schafft es der Zeitgeber, dem Agenten aufgrund der ausgelösten Zeitgeber mehrere Anforderungen in die Warteschlange zu stellen: mehrere periodische Instanzen, jeweils eine erste und eine zweite Verzögerung.
Wenn ein Agent seinen Thread entsperrt, sollte er dementsprechend die erste periodische und die erste Verzögerung erhalten. Bei der Verarbeitung von first_delayed bricht der Agent die Zustellung von periodic und second_delayed ab. Daher sollten diese Signale den Agenten nicht erreichen, unabhängig davon, ob sie sich bereits in der Warteschlange des Agenten befinden oder nicht (und sie sind es).
Wir betrachten das Ergebnis des Beispiels:
hang the agent...done periodic received first_delayed received last_delayed received
Ja, das ist es. Habe die erste periodische und erste_verzögerte. Dann gibt es weder periodische noch zweite Verzögerungen.
Wenn wir im Beispiel jedoch die "Timer" von so_5 :: extra :: revocable_timer durch die Standard-Timer von SObjectizer ersetzen, ist das Ergebnis anders: Alle periodischen und zweiten verzögerten Signalinstanzen, die bereits in die Warteschlange des Agenten eingetreten sind, erreichen den Agenten.
Nachrichten mit eingeschränkter Lieferzeit
Eine andere nützliche Sache, die manchmal in so_5_extra-1.2.0 verfügbar sein wird, ist die zeitliche Zustellung von Nachrichten. Beispielsweise sendet der request_handler-Agent eine verify_signature-Nachricht an den crypto_master-Agenten. Gleichzeitig möchte request_handler, dass verify_signature innerhalb von 5 Sekunden geliefert wird. Wenn dies nicht der Fall ist, macht die Verarbeitung von verity_signature keinen Sinn. Der request_handler-Agent beendet seine Arbeit bereits.
Und der crypto_master-Agent ist so ein Kamerad, der sich gerne als „Engpass“ herausstellt: Manchmal beginnt er langsamer zu werden. In einem solchen Moment werden Nachrichten in der Warteschlange angesammelt, z. B. die obige verify_signature, die warten kann, bis crypto_master entlastet wird.
Angenommen, request_handler hat eine verify_signature-Nachricht an den crypto_master-Agenten gesendet, aber dann war crypto_master festgefahren und blieb 10 Sekunden lang hängen. Der request_handler-Agent ist bereits "abgefallen", d. H. schickte bereits allen einen Denial-of-Service und beendete seine Arbeit. Die Nachricht verify_signature bleibt jedoch in der Warteschlange crypto_master! Wenn crypto_master also "entklebt", nimmt es diese Nachricht auf und verarbeitet sie. Dies ist zwar nicht mehr notwendig.
Mit dem neuen Umschlag so_5 :: extra :: Enveloped_msg :: time_limited_delivery_t können wir dieses Problem lösen: Der Request_handler-Agent sendet die im Umschlag enthaltene verify_signature time_limited_delivery_t mit einem Lieferzeitlimit:
so_5::extra::enveloped_msg::make<verify_signature>(...) .envelope<so_5::extra::enveloped_msg::time_limited_delivery_t>(5s) .send_to(crypto_master_mbox);
Wenn crypto_master nun "klebt" und es nicht schafft, innerhalb von 5 Sekunden die Verifizierungssignatur zu erreichen, sendet der Umschlag diese Nachricht einfach nicht zur Verarbeitung. Und crypto_master wird keine Arbeit machen, die sonst niemand braucht.
Empfängerlieferberichte
Und schließlich ein Beispiel für eine merkwürdige Sache, die weder in SObjectizer noch in so_5_extra regelmäßig implementiert wird, sondern unabhängig voneinander ausgeführt werden kann.
Manchmal möchten Sie vom SObjectizer so etwas wie eine "Zustellungsbericht" -Nachricht an den Empfänger erhalten. Immerhin ist es eine Sache, als die Nachricht den Empfänger erreichte, aber der Empfänger aus irgendeinem Grund nicht darauf antwortete. Eine andere Sache ist, wenn die Nachricht den Empfänger überhaupt nicht erreicht hat. Beispielsweise wurde es durch
einen Agentenüberlastungsschutzmechanismus blockiert. Im ersten Fall kann eine Nachricht, auf die wir nicht auf eine Antwort gewartet haben, weggelassen werden. Im zweiten Fall kann es jedoch sinnvoll sein, die Nachricht nach einiger Zeit erneut zu senden.
Nun werden wir uns überlegen, wie der einfachste Mechanismus von „Lieferberichten“ mithilfe von Umschlägen implementiert werden kann.
Also machen wir zuerst die notwendigen vorbereitenden Schritte:
#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;
Jetzt können wir die Nachrichten definieren, die im Beispiel verwendet werden. Die erste Nachricht ist eine Aufforderung, einige von uns benötigte Aktionen auszuführen. Und die zweite Nachricht ist eine Bestätigung, dass die erste Nachricht den Empfänger erreicht hat:
struct request_t final { request_id_t m_id; std::string m_data; }; struct delivery_receipt_t final {
Als nächstes können wir einen Agentenprozessor_t definieren, der Nachrichten vom Typ request_t verarbeitet. Die Verarbeitung erfolgt jedoch mit der Nachahmung des „Klebens“. Das heißt, Es verarbeitet request_t und ändert anschließend seinen Status von st_normal in st_busy. Im Zustand st_busy wird nichts unternommen und alle eingehenden Nachrichten werden ignoriert.
Dies bedeutet, dass wenn der Prozessor_t-Agent drei aufeinanderfolgende request_t-Nachrichten sendet, er die erste verarbeitet und die anderen beiden ausgelöst werden, weil Bei der Verarbeitung der ersten Nachricht wechselt der Agent zu st_busy und ignoriert, was zu ihm kommt, während er sich in st_busy befindet.
In st_busy verbringt Agent process_t 2 Sekunden. Danach kehrt er wieder zu st_normal zurück und ist bereit, neue Nachrichten zu verarbeiten.
So sieht der Agent process_t aus:
class processor_t final : public so_5::agent_t {
Jetzt können wir den Agenten request_generator_t definieren, der eine Reihe von Anforderungen enthält, die an processor_t gesendet werden müssen. Der Agent request_generator_t sendet alle 3 Sekunden das gesamte Paket und wartet dann auf die Zustellbestätigung in Form von Delivery_receipt_t.
Wenn Delivery_Recept_t eintrifft, wirft der Agent request_generator_t die gelieferte Anfrage aus dem Bundle. Wenn die Packung vollständig leer ist, ist das Beispiel abgeschlossen. Wenn noch etwas übrig ist, wird das verbleibende Paket erneut gesendet, wenn das nächste Mal erneut gesendet wird.
Hier ist also der Agent-Code request_generator_t. Es ist ziemlich voluminös, aber primitiv.
Sie können nur auf die Interna der Methode send_requests () achten, in der request_t-Nachrichten gesendet werden, die in einem speziellen Umschlag eingeschlossen sind.Requests_generator_t Agentencode class requests_generator_t final : public so_5::agent_t {
Jetzt haben wir Nachrichten und Agenten, die diese Nachrichten zur Kommunikation verwenden müssen. Es war nur noch eine Kleinigkeit übrig - irgendwie kamen Zustellungsnachrichten an, wenn request_t an processor_t übermittelt wurde.Dies geschieht mit diesem Umschlag: 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); } };
Im Allgemeinen gibt es nichts Kompliziertes. Wir erben von so_5 :: extra :: Enveloped_msg :: just_envelope_t. Dies ist ein zusätzlicher Umschlagtyp, der die darin enthaltene Nachricht speichert und die grundlegende Implementierung der Hookshandler_found_hook () und transformation_hook () bereitstellt. Daher können wir nur die Attribute speichern, die wir in custom_envelope_t benötigen, und Delivery_receipt_t innerhalb des Hooks handler_found_hook () senden.Das ist in der Tat alles.
Wenn wir dieses Beispiel ausführen, erhalten wir Folgendes: 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
Außerdem muss ich sagen, dass in der Praxis ein so einfacher custom_envelope_t zum Generieren von Lieferberichten kaum geeignet ist. Wenn sich jedoch jemand für dieses Thema interessiert, kann es in den Kommentaren besprochen werden, ohne das Volumen des Artikels zu erhöhen.Was könnte man noch mit Umschlägen machen?
Gute Frage!
Auf die wir selbst keine umfassende Antwort haben. Wahrscheinlich sind die Möglichkeiten nur durch die Vorstellungskraft der Benutzer begrenzt. Nun, wenn für die Verwirklichung von Fantasien in SObjectizer etwas fehlt, dann kann uns dies gesagt werden. Wir hören immer zu. Und vor allem manchmal sogar :)Integration von Agenten in mchain
Etwas ernster gesagt, das ist eine weitere Funktion, die ich von Zeit zu Zeit haben möchte und die sogar für so_5_extra-1.2.0 geplant war. Was aber höchstwahrscheinlich nicht in Release 1.2.0 fallen wird.Es geht darum, die Integration von Ketten und Agenten zu vereinfachen .Tatsache ist, dass ursprünglich Ketten zu SObjectizer hinzugefügt wurden, um die Kommunikation von Agenten mit anderen Teilen der Anwendung zu vereinfachen, die ohne Agenten geschrieben wurden. Beispielsweise gibt es den Hauptthread der Anwendung, auf dem der Benutzer über die GUI interagiert. Und es gibt mehrere Agenten, die im Hintergrund "harte" Arbeit leisten. Das Senden einer Nachricht an einen Agenten aus dem Hauptthread ist kein Problem: Rufen Sie einfach reguläres Senden auf. Aber wie übertrage ich Informationen zurück?Hierzu wurden mchain-s hinzugefügt.Im Laufe der Zeit stellte sich jedoch heraus, dass Ketten eine viel größere Rolle spielen können. Grundsätzlich ist es möglich, Multithread-Anwendungen auf SObjectizer ohne Agenten zu erstellen, nur auf mchain-ahs (weitere Details hier ). Und Sie können mchain-s verwenden, um die Belastung der Agenten auszugleichen. Als Mechanismus zur Lösung von Produzenten-Konsumenten-Problemen.Das Problem mit dem Produzenten-Konsumenten ist, dass wir in Schwierigkeiten sind, wenn der Produzent Nachrichten schneller generiert, als der Konsument sie verarbeiten kann. Die Nachrichtenwarteschlangen werden größer, die Leistung kann sich mit der Zeit verschlechtern oder die Anwendung stürzt aufgrund von Speicherauslastung vollständig ab.Die übliche Lösung, die wir in diesem Fall vorgeschlagen haben, ist die Verwendungein Paar Sammler-Performer-Agenten . Sie können auch Nachrichtenlimits verwenden (entweder als Hauptschutzmechanismus oder als Ergänzung zu Collector-Performer). Das Schreiben von Collector-Performer erfordert jedoch zusätzliche Arbeit vom Programmierer.Aber Ketten könnten für diese Zwecke mit minimalem Aufwand des Entwicklers verwendet werden. Der Produzent würde also die nächste Nachricht in die Kette einfügen, und der Verbraucher würde Nachrichten aus dieser Kette entgegennehmen.Das Problem ist jedoch, dass es für einen Agenten als Agent nicht sehr bequem ist, mit mchain über die verfügbaren Funktionen receive () und select () zu arbeiten. Und diese Unannehmlichkeiten könnten mit Hilfe eines Tools zur Integration von Agenten und Mchain-s behoben werden.Bei der Entwicklung eines solchen Tools müssen mehrere Probleme gelöst werden. Wenn eine Nachricht beispielsweise in mchain eintrifft, an welchem Punkt sollte sie aus mchain extrahiert werden? Wenn der Verbraucher frei ist und nichts verarbeitet, können Sie die Nachricht sofort von mchain abholen und an den Verbraucheragenten weitergeben. Wenn eine Nachricht bereits von mchain an den Verbraucher gesendet wurde, konnte er diese Nachricht noch nicht verarbeiten, aber eine neue Nachricht ist bereits in mchain eingetroffen ... Was ist in diesem Fall zu tun?Es gibt Spekulationen, dass Umschläge in diesem Fall helfen könnten. Wenn wir also die erste Nachricht von mchain nehmen und an den Verbraucher senden, verpacken wir diese Nachricht in einen speziellen Umschlag. Wenn der Umschlag feststellt, dass die Nachricht zugestellt und verarbeitet wurde, fordert er die nächste Nachricht von mchain an (falls vorhanden).Natürlich ist hier nicht alles so einfach. Aber bisher sieht es ziemlich lösbar aus. Und ich hoffe, ein ähnlicher Mechanismus wird in einer der nächsten Versionen von so_5_extra erscheinen.Öffnen wir die Büchse der Pandora?
Es sollte beachtet werden, dass bei uns die zusätzlichen Fähigkeiten selbst doppelte Gefühle hervorrufen.Einerseits haben Umschläge bereits erlaubt / erlaubt, Dinge zu tun, die zuvor erwähnt wurden (aber nur von etwas geträumt haben). Dies ist beispielsweise eine garantierte Stornierung von Zeitgebern und eine Einschränkung der Zustellzeit, Zustellberichte und die Möglichkeit, eine zuvor gesendete Nachricht abzurufen.Andererseits ist nicht klar, wozu dies später führen wird. Schließlich können Sie aus jeder Gelegenheit ein Problem machen, wenn Sie diese Gelegenheit nutzen, wo Sie sie brauchen und wo nicht. Vielleicht öffnen wir die Büchse von Pandora und stellen uns immer noch nicht vor, was uns erwartet?Es bleibt nur geduldig zu sein und zu sehen, wohin uns das alles führen wird.Über die unmittelbaren Entwicklungspläne von SObjectizer, anstatt abzuschließen
Anstelle einer Schlussfolgerung möchte ich darüber sprechen, wie wir die nahe (und nicht nur) Zukunft von SObjectizer sehen. Wenn jemand mit etwas in unseren Plänen nicht zufrieden ist, können Sie sich äußern und beeinflussen, wie sich SObjectizer-5 entwickeln wird.Die ersten Beta-Versionen von SObjectizer-5.5.23 und so_5_extra-1.2.0 sind bereits behoben und stehen zum Download und für Experimente zur Verfügung. Im Bereich Dokumentation und Anwendungsfälle bleibt noch viel zu tun. Daher ist die offizielle Veröffentlichung im ersten Jahrzehnt des Novembers geplant. Wenn es früher klappt, machen wir es früher.Die Veröffentlichung von SObjectizer-5.5.23 scheint zu bedeuten, dass die Entwicklung von Zweig 5.5 zu Ende geht. Die allererste Veröffentlichung in diesem Thread fand vor vier Jahren im Oktober 2014 statt.. Seitdem hat sich SObjectizer-5 innerhalb des 5.5-Zweigs weiterentwickelt, ohne dass wesentliche Änderungen zwischen den Versionen vorgenommen wurden. Es war nicht einfach. Vor allem angesichts der Tatsache, dass wir die ganze Zeit auf Compiler zurückblicken mussten, die C ++ 11 bei weitem nicht optimal unterstützen.Jetzt sehen wir keinen Grund, auf die Kompatibilität innerhalb des 5.5-Zweigs und insbesondere auf ältere C ++ - Compiler zurückzublicken. Was 2014 gerechtfertigt sein könnte, als sich C ++ 14 gerade auf die offizielle Einführung vorbereitete und C ++ 17 noch nicht in Sicht war, sieht es jetzt ganz anders aus.Außerdem hat sich in SObjectizer-5.5 selbst bereits eine ganze Reihe von Rakes und Backups angesammelt, die aufgrund derselben Kompatibilität aufgetreten sind und die weitere Entwicklung von SObjectizer erschweren.Daher werden wir in den kommenden Monaten nach folgendem Szenario handeln:1. Entwicklung der nächsten Version von so_5_extra, in der ich Tools hinzufügen möchte, um das Schreiben von Tests für Agenten zu vereinfachen. Ob es so_5_extra-1.3.0 sein wird (d. H. Mit brechenden Änderungen relativ zu 1.2.0) oder ob es so_5_extra-1.2.1 sein wird (d. H. Ohne brechende Änderungen), ist noch nicht klar. Mal sehen, wie es geht. Es ist nur klar, dass die nächste Version von so_5_extra auf SObjectizer-5.5 basiert.1a. Wenn Sie für die nächste Version von so_5_extra in SObjectizer-5.5 zusätzliche Aktionen ausführen müssen, wird die nächste Version 5.5.24 veröffentlicht. Wenn für so_5_extra keine Verbesserungen am Kernel von SObjectizer erforderlich sind, stellt sich heraus, dass Version 5.5.23 die letzte wichtige Version im Rahmen von Zweig 5.5 ist. Kleinere Bugfixes werden veröffentlicht. Die Entwicklung von Zweig 5.5 selbst stoppt jedoch in Version 5.5.23 oder 5.5.24.2. Dann wird eine Version von SObjectizer-5.6.0 veröffentlicht, die einen neuen Zweig eröffnet. In Zweig 5.6 werden wir den SObjectizer-Code von allen angesammelten Krücken und Backups sowie von altem Papierkorb entfernen, der seit langem als veraltet markiert ist. Es ist wahrscheinlich, dass einige Dinge einem Refactoring unterzogen werden (z. B. kann abstract_message_box_t geändert werden), aber kaum Kardinal. Die Grundprinzipien der Arbeit und die charakteristischen Merkmale von SObjectizer-5.5 in SObjectizer-5.6 bleiben in derselben Form.SObjectizer-5.6 benötigt bereits C ++ 14 (zumindest auf GCC-5.5-Ebene). Visual C ++ - Compiler unter VC ++ 15 (aus Visual Studio 2017) werden nicht unterstützt.Wir betrachten Zweig 5.6 als einen stabilen Zweig von SObjectizer, der relevant sein wird, bis die erste Version von SObjectizer-5.7 erscheint.Ich möchte die Version 5.6.0 Anfang 2019, vorläufig im Februar, veröffentlichen.3. Nach der Stabilisierung von Zweig 5.6 möchten wir mit der Arbeit an Zweig 5.7 beginnen, in dem wir einige Grundprinzipien der Arbeit von SObjectizer überarbeiten können. Verlassen Sie beispielsweise öffentliche Disponenten vollständig und lassen Sie nur private übrig. Wiederholen Sie den Mechanismus der Genossenschaften und ihre Eltern-Kind-Beziehungen, um den Engpass bei der Registrierung / Abmeldung von Genossenschaften zu beseitigen. Entfernen Sie die Teilung durch Nachricht / Signal. Erlauben Sie nur send / send_delayed / send_periodic, Nachrichten zu senden, und verbergen Sie die Methoden Deliver_message und Schedule_Timer "unter der Haube". Ändern Sie den Mechanismus zum Versenden von Nachrichten so, dass Sie dynamic_casts entweder vollständig aus diesem Prozess entfernen oder auf ein Minimum reduzieren.Im Allgemeinen gibt es einen Ort, an dem man sich umdrehen kann. Gleichzeitig benötigt SObjectizer-5.7 bereits C ++ 17, ohne Rücksicht auf C ++ 14.Wenn Sie sich Dinge ohne rosa Brille ansehen, ist es gut, wenn Release 5.7.0 im Spätherbst 2019 stattfindet. Die Hauptarbeitsversion von SObjectizer für 2019 wird Zweig 5.6 sein.4. Parallel dazu wird sich so_5_extra entwickeln. Wahrscheinlich wird die Version so_5_extra-2 zusammen mit SObjectizer-5.6 veröffentlicht, das im Laufe des Jahres 2019 neue Funktionen enthalten wird, jedoch auf SObjectizer-5.6 basiert.Daher sehen wir selbst eine fortschreitende Entwicklung für SObjectizer-5 mit einer schrittweisen Überarbeitung einiger der Grundprinzipien von SObjectizer-5. Gleichzeitig werden wir versuchen, dies so reibungslos wie möglich zu gestalten, damit mit minimalen Schmerzen von einer Version zur anderen gewechselt werden kann.Wenn jedoch jemand dramatischere und bedeutendere Änderungen von SObjectizer wünscht, haben wir einige Gedanken dazu . Kurz gesagt: Sie können SObjectizer nach Belieben neu erstellen, um SObjectizer-6 für eine andere Programmiersprache zu implementieren. Wir werden dies jedoch nicht vollständig auf eigene Kosten tun, da dies mit der Entwicklung von SObjectizer-5 geschieht.Das ist wahrscheinlich alles.
Die Kommentare zum vorherigen Artikel erwiesen sich als gute und konstruktive Diskussion. Es wäre nützlich für uns, wenn diesmal eine ähnliche Diskussion stattfinden würde. Wie immer sind wir bereit, alle Fragen zu beantworten, aber die vernünftigen und mit Vergnügen.Und den geduldigsten Lesern, die diese Zeilen erreicht haben, vielen Dank für die Zeit, die sie mit dem Lesen des Artikels verbracht haben.