
Die Entwicklung eines kostenlosen Frameworks für Entwickleranforderungen ist ein spezifisches Thema. Wenn das Framework gleichzeitig ziemlich lange lebt und sich entwickelt, werden die Besonderheiten hinzugefügt. Heute werde ich versuchen, dies anhand eines Beispiels für einen Versuch zu zeigen, die Funktionalität eines "Actor" -Frameworks für C ++ namens
SObjectizer zu erweitern .
Tatsache ist, dass dieses Framework bereits ziemlich alt ist und sich mehrmals dramatisch geändert hat. Sogar seine aktuelle Inkarnation, SObjectizer-5, hat viele Veränderungen erfahren, sowohl ernsthafte als auch nicht ernsthafte. Außerdem reagieren wir sehr empfindlich auf Kompatibilität, und Änderungen, die die Kompatibilität beeinträchtigen, sind ein zu schwerwiegender Schritt, als dass wir uns nur dafür entscheiden könnten.
Im Moment müssen wir entscheiden, wie der nächsten Version eine neue Funktion hinzugefügt werden soll. Bei der Suche nach einer geeigneten Lösung ergaben sich zwei Optionen. Beide sehen ziemlich realisierbar aus. Aber sie unterscheiden sich sehr voneinander. Sowohl in Bezug auf die Komplexität und Komplexität der Implementierung als auch in Bezug auf das „Erscheinungsbild“. Das heißt, Was der Entwickler tun wird, sieht in jeder der Optionen anders aus. Wahrscheinlich sogar grundlegend anders.
Und jetzt müssen wir als Entwickler des Frameworks eine Entscheidung zugunsten der einen oder anderen Lösung treffen. Oder man muss zugeben, dass keiner von ihnen zufriedenstellend ist und daher etwas anderes erfunden werden muss. Solche Entscheidungen in der Geschichte von SObjectizer mussten mehr als einmal getroffen werden. Wenn jemand daran interessiert ist, sich in den Schuhen des Entwicklers eines solchen Frameworks zu fühlen, dann sind Sie bei cat willkommen.
Ursprüngliches Problem
Also kurz die Essenz des ursprünglichen Problems. SObjectizer hatte von Anfang an folgende Funktion: Eine Timer-Nachricht ist nicht so einfach abzubrechen. Unter dem Timer wird zunächst eine verzögerte Nachricht verstanden. Das heißt, Eine Nachricht, die nicht sofort, sondern nach einiger Zeit an den Empfänger gesendet werden soll. Zum Beispiel senden wir_verzögert mit einer Pause von 1s. Dies bedeutet, dass in der Realität die Nachricht vom Timer 1s nach dem Aufruf send_delayed gesendet wird.
Eine ausstehende Nachricht kann grundsätzlich abgebrochen werden. Wenn sich die Nachricht noch im Besitz des Timers befindet, wird die Nachricht nach dem Abbrechen nirgendwo hingehen. Es wird vom Timer ausgelöst und das wars. Wenn der Timer jedoch bereits eine Nachricht gesendet hat und sich jetzt in der Anforderungswarteschlange für den empfangenden Agenten befindet, funktioniert das Abbrechen des Timers nicht. In SObjectizer gibt es keinen Mechanismus zum Entfernen einer Nachricht aus der Anwendungswarteschlange.
Das Problem wird durch mindestens zwei Faktoren verschärft.
Erstens unterstützt SObjectizer die Lieferung im 1: N-Modus, d.h. Wenn die Nachricht an die Multi-Consumer-Mbox gesendet wurde, befindet sich die Nachricht nicht in einer Warteschlange, sondern in mehreren Warteschlangen für N Empfänger gleichzeitig.
Zweitens wird in SObjectizer der Dispatcher-Mechanismus verwendet, und die Dispatcher können sehr unterschiedlich sein, einschließlich der vom Benutzer für ihre spezifischen Anforderungen geschriebenen. Anforderungswarteschlangen werden von Dispatchern verwaltet. Und in der Schnittstelle des Dispatchers gibt es keine Funktionalität zum Zurückziehen einer Anwendung, die bereits an den Dispatcher übertragen wurde. Aber selbst wenn solche Funktionen in die Schnittstelle eingebettet wären, ist es weit davon entfernt, dass sie in allen Fällen effektiv implementiert werden könnten. Ganz zu schweigen von der Tatsache, dass eine solche Funktionalität die Komplexität der Entwicklung neuer Disponenten erhöhen würde.
Wenn der Timer bereits eine ausstehende Nachricht an den / die Empfänger gesendet hat, ist es im Allgemeinen objektiv derzeit unmöglich, SObjectizer zu zwingen, diese Instanz der Nachricht nicht zuzustellen.
Tatsächlich ist dieses Problem auch für periodische Nachrichten relevant (d. H. Nachrichten, die der Zeitgeber periodisch in vorbestimmten Zeitintervallen senden sollte). In der Praxis ist das Abbrechen periodischer Nachrichten jedoch viel weniger erforderlich als das Abbrechen einer ausstehenden Nachricht. Zumindest in unserer Praxis ist dies so.
Was kann jetzt getan werden?
Dieses Problem ist also nicht neu und es gibt seit langem Empfehlungen, wie man damit umgeht.
Eindeutige ID in ausstehender Nachricht
Der einfachste Weg ist, einen Zähler zu behalten. Der Agent verfügt über einen Zähler. Beim Senden einer ausstehenden Nachricht wird der aktuelle Zählerwert in der Nachricht gesendet. Wenn eine Nachricht abgebrochen wird, wird der Zähler am Agenten erhöht. Beim Empfang der Nachricht wird der aktuelle Zählerwert im Agenten mit dem Wert aus der Nachricht verglichen. Wenn die Werte nicht übereinstimmen, wird die Nachricht abgelehnt:
class demo_agent : public so_5::agent_t { struct delayed_msg final { int id_; ... }; int expected_msg_id_{}; so_5::timer_id_t timer_; void on_some_event() {
Das Problem bei dieser Methode ist, dass der Agentenentwickler durch die Pflege dieser Zähler verwirrt werden muss. Und wenn wir als verzögerte Nachricht die Nachricht eines anderen senden müssen, die jemand anderes getan hat und in der es kein id_-Feld gibt, befinden wir uns in einer schwierigen Situation.
Auf der anderen Seite ist dies der derzeit effektivste Weg.
Verwenden Sie eine eindeutige mbox für verzögerte Nachrichten
Eine andere Möglichkeit, die gut funktioniert, besteht darin, eine eindeutige Mailbox (mbox) für eine verzögerte Nachricht zu verwenden. In diesem Fall erstellen wir für jede ausstehende Nachricht eine neue Mbox, abonnieren sie und senden die ausstehende Nachricht an diese Mbox. Wenn eine Nachricht storniert werden muss, löschen wir einfach die mbox-Abonnements.
class demo_agent : public so_5::agent_t { struct delayed_msg final { ...
Diese Methode kann bereits mit Nachrichten anderer Personen arbeiten, in denen sich keine eindeutige Kennung befindet. Es erfordert aber auch Arbeit und Aufmerksamkeit des Entwicklers.
Beispielsweise gibt es in der obigen Ausführungsform keinen Schutz gegen die Tatsache, dass eine ausstehende Nachricht bereits früher gesendet wurde. Vor dem Senden einer neuen ausstehenden Nachricht sollten Sie immer Aktionen von on_cancel_event () ausführen, da der Agent sonst unnötige Abonnements dafür hat.
Warum wurde dieses Problem noch nicht gelöst?
Hier ist alles ganz einfach: Tatsächlich ist dies kein so ernstes Problem, wie es scheinen mag. Zumindest im wirklichen Leben muss man sich nicht oft damit auseinandersetzen. Normalerweise werden ausstehende und periodische Nachrichten überhaupt nicht abgebrochen (weshalb die Funktion send_delayed übrigens keine timer_id zurückgibt). Und wenn die Notwendigkeit einer Stornierung besteht, können Sie eine der oben beschriebenen Methoden anwenden. Oder verwenden Sie sogar eine andere. Erstellen Sie beispielsweise separate Agenten, die eine ausstehende Nachricht verarbeiten. Diese Agenten können abgemeldet werden, wenn eine ausstehende Nachricht abgebrochen werden muss.
Vor dem Hintergrund anderer Aufgaben, mit denen wir konfrontiert waren, war die Vereinfachung der garantierten Löschung einer ausstehenden Nachricht nicht so wichtig, dass unsere Ressourcen für die Lösung dieses Problems aufgewendet wurden.
Warum ist das Problem jetzt relevant?
Hier ist alles genauso einfach. Einerseits erreichten die Hände endlich.
Wenn jedoch neue Leute, die keine Erfahrung mit SObjectizer haben, anfangen, SObjectizer zu verwenden, überrascht sie diese Funktion mit der Löschung von Timern sehr. Nicht so angenehm überraschend. Und wenn ja, dann möchte ich die negativen Eindrücke beim Kennenlernen unseres Tools minimieren.
Außerdem hatten wir unsere eigenen Aufgaben, wir mussten ausstehende Nachrichten nicht ständig abbrechen. Und neue Benutzer haben ihre eigenen Aufgaben, vielleicht ist alles umgekehrt.
Neue Erklärung des Problems
Fast sofort, als die Prüfung der Möglichkeit einer „garantierten Timer-Stornierung“ begann, kam mir der Gedanke, dass die Aufgabe erweitert werden könnte. Sie können versuchen, das Problem des Abrufs einer der zuvor gesendeten Nachrichten zu lösen, die nicht unbedingt verzögert und regelmäßig sind.
Von Zeit zu Zeit ist diese Gelegenheit gefragt. Stellen Sie sich zum Beispiel vor, wir haben mehrere interagierende Agenten zweier Typen: entry_point (akzeptiert Anforderungen von Clients) und processor (verarbeitet Anforderungen):

Entry_point-Agenten senden Anforderungen an den Prozessoragenten, der sie so weit wie möglich verarbeitet und auf entry_point-Agenten antwortet. Manchmal stellt entry_point jedoch fest, dass die Verarbeitung einer zuvor gesendeten Anforderung nicht mehr erforderlich ist. Beispielsweise hat der Client einen Abbruchbefehl gesendet oder der Client ist "abgefallen", und Sie müssen seine Anforderungen nicht mehr verarbeiten. Wenn Anforderungsnachrichten vom Prozessoragenten in die Warteschlange gestellt werden, können Sie sie nicht mehr abrufen. Und es wäre nützlich.
Daher wird der derzeitige Ansatz zur Lösung des Problems der "garantierten Timer-Löschung" genau als Unterstützung für "Rückrufnachrichten" ausgeführt. Wir senden jede Nachricht auf besondere Weise, wir haben ein Handle zur Hand, mit dem Sie die Nachricht abrufen können. Und es ist nicht so wichtig, ob eine reguläre oder eine verspätete Nachricht antwortet.
Ein Versuch, die Implementierung von "Rückrufnachrichten" zu finden.
Sie müssen also das Konzept der "Rückrufnachricht" einführen und dieses Konzept in SObjectizer unterstützen. Und so, um in der 5.5-Filiale zu bleiben. Die erste Version dieses Threads, 5.5.0, wurde vor fast vier Jahren im Oktober 2014 veröffentlicht. Seitdem gab es in 5.5 keine wesentlichen Änderungen. Projekte, die bereits auf SObjectize-5.5 umgestellt oder sofort gestartet wurden, können problemlos auf neue Releases im 5.5-Zweig umstellen. Diese Kompatibilität muss diesmal beibehalten werden.
Im Allgemeinen ist alles einfach: Sie müssen nehmen und tun.
Was ist klar, wie zu tun
Nach der ersten Herangehensweise an das Problem wurden zwei Dinge bei der Implementierung von „Rückrufnachrichten“ klar.
Atomic Flag und seine Überprüfung vor der Nachrichtenverarbeitung
Erstens ist es offensichtlich, dass es im Rahmen der aktuellen SObjectizer-5.5-Architektur (und möglicherweise noch globaler: im Rahmen der Prinzipien von SObjectizer-5 selbst) unmöglich ist, Nachrichten aus Dispatcher-Anforderungswarteschlangen zu entfernen, in denen Nachrichten warten, bis die empfangenden Agenten sie verarbeiten. Der Versuch, dies zu tun, wird die ganze Idee heterogener Disponenten zunichte machen, die selbst der Benutzer je nach den Besonderheiten seiner Aufgabe (zum Beispiel
dieser ) selbst machen kann. Darüber hinaus ist es beim Senden einer Nachricht im 1: N-Modus, in dem N groß ist, teuer, eine Liste von Zeigern auf eine Instanz der gesendeten Nachricht in allen Warteschlangen zu führen.
Dies bedeutet, dass zusammen mit der Nachricht eine Art Atomflag übertragen werden muss, das unmittelbar nach dem Entfernen der Nachricht aus der Anforderungswarteschlange, jedoch bevor die Nachricht zur Verarbeitung an den empfangenden Agenten gesendet wird, analysiert werden muss. Das heißt, Die Nachricht wird in die Warteschlange gestellt und von dort nirgendwo entfernt. Aber wenn die Nachricht an der Reihe ist, wird ihre Flagge überprüft. Und wenn das Flag anzeigt, dass die Nachricht zurückgezogen wurde, wird die Nachricht nicht verarbeitet.
Dementsprechend besteht der Nachrichtenrückruf selbst darin, einen speziellen Wert für das Atomflag innerhalb der Nachricht festzulegen.
Revocable_handle_t <M> -Objekt
Zweitens ist es bisher (?) Offensichtlich, dass zum Senden einer widerruflichen Nachricht nicht die üblichen Methoden zum Senden von Nachrichten verwendet werden sollten, sondern ein spezielles Objekt unter dem bedingten Namen revocable_handle_t.
Um eine widerrufliche Nachricht zu senden, muss der Benutzer eine Instanz von revocable_handle_t erstellen und dann die send-Methode für diese Instanz aufrufen. Und wenn die Nachricht zurückgerufen werden muss, erfolgt dies mithilfe der Widerrufsmethode. So etwas wie:
struct my_message {...}; ... so_5::revocable_handle_t<my_message> msg;
Es gibt noch keine klaren Details zur Implementierung von revocable_handle_t, was seitdem nicht überraschend ist Der Mechanismus der Arbeit von Rückrufnachrichten wurde noch nicht ausgewählt. Das Prinzip der Arbeit ist jedoch, dass in revocable_handle_t ein intelligenter Link zur gesendeten Nachricht und zum Atomflag dafür gespeichert wird. Die Methode revoke () versucht, den Flag-Wert zu ersetzen. Wenn dies erfolgreich ist, wird die Nachricht nach dem Extrahieren aus der Bestellwarteschlange nicht mehr verarbeitet.
Womit es nicht befreundet sein wird
Leider gibt es einige Dinge, mit denen das Abrufen von Nachrichten nicht richtig verknüpft werden kann. Nur weil die zurückgezogene Nachricht weiterhin in den Warteschlangen verbleibt, in denen sie bereits angekommen ist.
message_limits
Ein so wichtiges Merkmal von SObjectizer wie
message_limits soll Agenten vor Überlastung schützen. Message_limits basieren auf der Anzahl der Nachrichten in der Warteschlange. Eine Nachricht in die Warteschlange gestellt - der Zähler wurde erhöht. Aus der Reihe geraten - reduziert.
Weil Wenn eine Nachricht widerrufen wird, bleibt sie in der Warteschlange. Message_limits hat keinen Einfluss auf die Antwort der Nachricht. Daher kann sich herausstellen, dass die Anzahl der Nachrichten vom Typ M in der Warteschlange begrenzt ist, aber alle wurden zurückgerufen. In der Tat wird keiner von ihnen verarbeitet. Das Einreihen einer neuen Nachricht vom Typ M funktioniert jedoch nicht, da das Limit ist überschritten.
Die Situation ist nicht gut. Aber wie kommt man da raus? Unverständlich.
Feste Warteschlangenketten
In SObjectizer kann eine Nachricht nicht nur an mbox, sondern auch an mchain gesendet werden (dies ist unser
Analogon zum CSP-Kanal ). Und Ketten können eine feste Größe für ihre Warteschlangen haben. Der Versuch, eine neue Nachricht für mchain mit einer festen Größe in die vollständige mchain einzufügen, sollte zu einer Reaktion führen. Warten Sie beispielsweise auf die Freigabe von Speicherplatz in der Warteschlange. Oder um die älteste Nachricht zu pushen.
Im Falle eines Nachrichtenrückrufs bleibt er in der mchain-Warteschlange. Es stellt sich heraus, dass die Nachricht nicht mehr benötigt wird, aber Platz in der mchain-Warteschlange beansprucht. Und verhindert, dass neue Nachrichten an mchain gesendet werden.
Die gleiche schlechte Situation wie bei message_limits. Und wieder ist nicht klar, wie es behoben werden kann.
Was ist nicht klar, wie zu tun ist
Wir haben also die Wahl zwischen zwei (bisher?) Optionen für die Implementierung von Rückrufnachrichten. Die erste Option ist einfach zu implementieren und erfordert keine Änderung der Innereien von SObjectizer. Die zweite Option ist viel komplizierter, aber darin weiß der Nachrichtenempfänger nicht einmal, dass es sich um widerrufbare Nachrichten handelt. Wir werden jeden von ihnen kurz betrachten.
Empfangen Sie widerrufliche Nachrichten als widerrufliche_t <M>
Die erste Lösung, die erstens machbar und zweitens recht praktisch aussieht, ist die Einführung eines speziellen Wrappers revocable_t <M>. Wenn der Benutzer eine widerrufliche Nachricht vom Typ M über revocable_handle_t <M> sendet, wird nicht die Nachricht M gesendet, sondern die Nachricht M innerhalb des speziellen Wrappers revocable_t <M>. Dementsprechend empfängt und verarbeitet der Benutzer nicht die Nachricht vom Typ M, sondern die Nachricht revocable_t <M>. Zum Beispiel auf diese Weise:
class processor : public so_5::agent_t { public: struct request { ... };
Die Methode revocable_t <M> :: try_handle () überprüft den Wert des Atom-Flags und ruft, wenn die Nachricht nicht abgerufen wird, die an sie übergebene Lambda-Funktion auf. Wenn die Nachricht zurückgezogen wird, führt try_handle () nichts aus.
Vor- und Nachteile dieses Ansatzes
Das Hauptplus ist, dass diese Reise leicht durchgeführt werden kann (zumindest soweit es scheint). Tatsächlich sind revocable_handle_t <M> und revocable_t <M> nur ein subtiles Add-On zum SObjectizer.
Eingriffe in SObjectizer-Interna können erforderlich sein, um Freunde widerruflich_t und mutable_msg zu machen. Tatsache ist, dass es in SObjectizer das Konzept unveränderlicher Nachrichten gibt (sie können sowohl im 1: 1-Modus als auch im 1: N-Modus gesendet werden). Und es gibt das Konzept
veränderlicher Nachrichten , die nur im 1: 1-Modus gesendet werden können. In diesem Fall behandelt SObjectizer auf besondere Weise den Marker mutable_msg <M> und führt zur Laufzeit die entsprechenden Überprüfungen durch. Im Fall von revocable_t <mutable_msg <M>> müssen Sie SObjectizer beibringen, dieses Konstrukt als mutable_msg <M> zu behandeln.
Ein weiteres Plus ist, dass der zusätzliche Overhead (sowohl für die Metadaten der widerrufbaren Nachricht als auch für die Überprüfung des Atom-Flags) nur an Stellen anfällt, an denen Sie nicht darauf verzichten können. Wenn Rückrufnachrichten nicht verwendet werden, entsteht überhaupt kein zusätzlicher Aufwand.
Aber das Hauptminus ist ideologisch. Bei diesem Ansatz wirkt sich die Verwendung widerrufbarer Nachrichten sowohl auf den Absender (unter Verwendung von widerrufbar_handle_t <M>) als auch auf den Empfänger (unter Verwendung von widerrufbar_t <M>) aus. Der Empfänger muss jedoch nicht wissen, dass er Rückrufnachrichten erhält. Darüber hinaus können Sie als Empfänger einen vorgefertigten Drittanbieter-Agenten haben, der ohne revocable_t <M> geschrieben wurde.
Darüber hinaus bleiben ideologische Fragen offen, beispielsweise über die Möglichkeit, solche Nachrichten weiterzuleiten. Nach ersten Schätzungen sind diese Probleme jedoch gelöst.
Empfangen Sie Rückrufnachrichten als reguläre Nachrichten
Der zweite Ansatz besteht darin, nur die Nachricht vom Typ M auf der Empfängerseite zu sehen und keine Vorstellung von der Existenz von revocable_handle_t <M> und revocable_t <M> zu haben. Das heißt, Wenn der Prozessor eine Anfrage erhalten soll, sollte er nur eine Anfrage ohne zusätzliche Wrapper sehen.
Eigentlich kann man bei diesem Ansatz nicht auf einige Wrapper verzichten, aber sie werden im SObjectizer versteckt und der Benutzer sollte sie nicht sehen. Nachdem die Anwendung aus der Warteschlange abgerufen wurde, stellt SObjectizer fest, dass es sich um eine speziell umschlossene widerrufbare Nachricht handelt, überprüft das Flag für die Relevanz der Nachricht und erweitert die Nachricht, falls sie noch relevant ist. Anschließend wird eine Nachricht zur Verarbeitung an den Agenten gesendet, als wäre es eine reguläre Nachricht.
Vor- und Nachteile dieses Ansatzes
Der Hauptvorteil dieses Ansatzes liegt auf der Hand: Der Nachrichtenempfänger weiß nicht, mit welchen Nachrichten er arbeitet. Auf diese Weise kann der Absender von Nachrichten Nachrichten für alle Agenten ruhig zurückziehen, auch für diejenigen, die von anderen Entwicklern geschrieben wurden.
Ein weiteres wichtiges Plus ist die Möglichkeit, sich in den Mechanismus zur Verfolgung der Nachrichtenübermittlung zu integrieren (
hier wird die Rolle dieses Mechanismus ausführlicher beschrieben ). Das heißt, Wenn msg_tracing aktiviert ist und der Absender die Nachricht zurückzieht, finden Sie Spuren davon im msg_tracing-Protokoll. Was beim Debuggen sehr praktisch ist.
Der Hauptnachteil ist jedoch die Komplexität der Implementierung dieses Ansatzes. Dabei müssen mehrere Faktoren berücksichtigt werden.
Erstens Overhead. Alle möglichen Dinge.
Angenommen, Sie können in einer Nachricht ein spezielles Flag erstellen, das angibt, ob diese Nachricht widerruflich ist oder nicht. Überprüfen Sie dieses Flag, bevor Sie mit der Verarbeitung der einzelnen Nachrichten beginnen. Grob gesagt wird dem Nachrichtenübermittlungsmechanismus ein weiteres if hinzugefügt, das während der Verarbeitung jeder (!) Nachricht funktioniert.
Ich bin mir sicher, dass in realen Anwendungen der Verlust dabei kaum spürbar sein wird. Aber der Rückgang bei synthetischen Benchmarks wird sicherlich auftreten. Je abstrakter der Benchmark, desto weniger echte Arbeit leistet er, desto mehr wird er sinken. Und das ist aus Marketing-Sicht schlecht, weil Es gibt eine Reihe von Personen, die Schlussfolgerungen zum Rahmen in Bezug auf synthetische Benchmarks ziehen. Und sie tun es speziell: Sie verstehen nicht, um welche Art von Benchmark es sich handelt, sondern zeigen im Grunde genommen an, auf welcher Hardware sie arbeiten, sondern vergleichen die Gesamtsummen mit der Leistung eines speziellen Tools, in einem anderen Szenario, auf einer anderen Hardware usw. ., usw.
Da wir ein universelles Framework erstellen, das, wie sich herausstellt, anhand abstrakter Zahlen in abstrakten Benchmarks beurteilt wird, möchten wir im Allgemeinen nicht beispielsweise 5% der Leistung im Zustellungsmechanismus
aller Nachrichten verlieren, da eine Funktion hinzugefügt wird, die nur Zeit benötigt von Zeit zu Zeit und nicht an alle Benutzer.
Daher müssen Sie sicherstellen, dass SObjectizer beim Senden der Nachricht an den Empfänger versteht, dass Sie beim Extrahieren der Nachricht auf besondere Weise damit umgehen müssen. Wenn eine Nachricht an einen Agenten übermittelt wird, speichert SObjectizer im Prinzip mit der Nachricht einen Zeiger auf eine Funktion, die bei der Verarbeitung der Nachricht verwendet wird. Dies wird jetzt benötigt, um asynchrone Nachrichten und synchrone Anforderungen auf unterschiedliche Weise zu verarbeiten. So sieht die Anforderung der an den Agenten adressierten Nachricht aus:
struct execution_demand_t {
Wobei demand_handler_pfn_t ein regulärer Funktionszeiger ist:
typedef void (*demand_handler_pfn_t)( current_thread_id_t, execution_demand_t & );
Der gleiche Mechanismus kann auch verwendet werden, um die zurückgezogene Nachricht speziell zu verarbeiten. Das heißt, Wenn mbox eine Nachricht an den Agenten sendet, weiß der Agent, ob eine asynchrone Nachricht oder eine synchrone Anforderung an ihn gesendet wird. Ebenso kann einem Agenten auf besondere Weise eine asynchrone Rückrufnachricht gegeben werden. Der Agent speichert zusammen mit der Nachricht einen Zeiger auf eine Funktion, die weiß, wie er mit gesperrten Nachrichten umgehen soll.
Alles scheint in Ordnung zu sein, aber es gibt zwei große "Aber" ... :(
Erstens verfügt die vorhandene mbox-Schnittstelle (nämlich die Klasse
abstract_message_mbox_t ) über keine Methoden zum Senden von Rückrufnachrichten. Diese Schnittstelle muss also erweitert werden. Und damit die mbox-Implementierungen anderer Leute, die mit abstract_message_box_t aus SObjectizer-5.5 verknüpft sind, nicht kaputt gehen (insbesondere ist die mbox-Serie in
so_5_extra implementiert und ich möchte sie einfach nicht kaputt machen).
Zweitens können Nachrichten nicht nur an mbox-s gesendet werden, hinter denen Agenten versteckt sind, sondern auch an mchain-s. Welches sind
unsere Gegenstücke zu CSP-Kanälen? Und bis jetzt lagen die Anwendungen ohne zusätzliche Zeiger auf Funktionen. So fügen Sie einen zusätzlichen Zeiger in jedes Element der Anwendungswarteschlangen-Kette ein ... Sie können das natürlich, aber es sieht nach einer ziemlich teuren Lösung aus. Darüber hinaus haben die mchain-Implementierungen selbst bisher keine Situation vorgesehen, in der die extrahierte Nachricht überprüft und möglicherweise weggeworfen werden muss.
Wenn Sie versuchen, alle oben beschriebenen Probleme zusammenzufassen, besteht das Hauptproblem dieses Ansatzes darin, dass die Implementierung nicht so einfach ist, sodass sie in Fällen, in denen keine Rückrufnachrichten verwendet werden, kostengünstig ist.
Aber was ist mit der garantierten Stornierung ausstehender Nachrichten?
Ich befürchte, das ursprüngliche Problem ist in der Wildnis der technischen Details verloren gegangen. Angenommen, es gibt widerrufbare Nachrichten. Wie erfolgt die Stornierung ausstehender / periodischer Nachrichten?Hier sind, wie sie sagen, Optionen möglich. Das Arbeiten mit ausstehenden / periodischen Nachrichten kann beispielsweise Teil der Funktionalität von revocable_handle_t <M> sein: revocable_handle_t<my_mesage> msg; msg.send_delayed(target, 15s, ...); ... msg.revoke();
Oder Sie können zusätzlich zu revocable_handle_t <M> eine zusätzliche Hilfsklasse cancelable_timer_t <M> erstellen, die die Methoden send_delayed / send_periodic bereitstellt.Weißer Fleck: synchrone Anfragen
SObjectizer-5 unterstützt nicht nur die asynchrone Interaktion zwischen Entitäten im Programm (durch Senden von Nachrichten an mbox und mchain), sondern auch die synchrone Interaktion über request_value / request_future. Diese synchrone Interaktion funktioniert nicht nur für Agenten. Das heißt,
Sie können nicht nur über seine mbox eine synchrone Anfrage an einen Agenten senden. Bei mchains können Sie auch synchrone Anforderungen stellen, z. B. an einen anderen Arbeitsthread, für den receive () oder select () für mchain aufgerufen wurde.Es ist daher immer noch unklar, ob synchrone Anforderungen in Verbindung mit widerrufbaren Nachrichten verwendet werden dürfen. Einerseits macht das vielleicht Sinn. Und es könnte zum Beispiel so aussehen: revocable_handle_t<my_request> msg; auto f = msg.request_future<my_reply>(target, ...); ... if(some_condition) msg.revoke(); ... f.get();
Auf der anderen Seite gibt es immer noch viele unverständliche Nachrichten mit Rückrufnachrichten, so dass das Problem der synchronen Interaktion auf bessere Zeiten verschoben wurde.Wählen Sie, aber seien Sie vorsichtig. Aber wähle
Es gibt also ein Verständnis für das Problem. Es gibt zwei Möglichkeiten, dies zu lösen. Was im Moment machbar erscheint. Sie unterscheiden sich jedoch stark in der Benutzerfreundlichkeit des Benutzers und noch mehr in den Implementierungskosten.Sie müssen zwischen diesen beiden Optionen wählen. Oder sich etwas anderes einfallen lassen.Was ist die Schwierigkeit bei der Auswahl?Die Schwierigkeit besteht darin, dass SObjectizer ein freies Framework ist. Er bringt uns kein Geld direkt. Wir machen es, wie sie sagen, für uns. Aus rein wirtschaftlichen Gründen ist eine einfachere und schnellere Implementierung rentabler.Andererseits wird nicht alles in Geld gemessen, und auf lange Sicht ist ein gut gemachtes Tool, dessen Funktionen normalerweise miteinander verknüpft sind, besser als ein Patchwork-Patch aus Patches, die irgendwie zusammenkleben. Die Qualität wird sowohl von den Benutzern als auch von uns selbst bewertet, wenn wir anschließend unsere Entwicklung begleiten und neue Funktionen hinzufügen.Die Wahl liegt also tatsächlich zwischen kurzfristigen Vorteilen und langfristigen Aussichten. In der modernen Welt sind C ++ - Tools mit langfristigen Aussichten zwar irgendwie neblig. Das macht die Wahl noch schwieriger.Unter solchen Bedingungen müssen Sie wählen. Achtung Aber wähle.Fazit
In diesem Artikel haben wir versucht, den Prozess des Entwerfens und Implementierens neuer Funktionen in unserem Framework ein wenig zu zeigen. Ein solcher Prozess findet regelmäßig bei uns statt. Früher oft, weil In den Jahren 2014-2016 hat sich SObjectizer viel aktiver entwickelt. Jetzt hat sich das Tempo der Veröffentlichung neuer Versionen verringert. Was objektiv ist, auch weil das Hinzufügen neuer Funktionen, ohne etwas zu beschädigen, mit jeder neuen Version schwieriger wird.Ich hoffe es war interessant, hinter die Kulissen zu schauen. Vielen Dank für Ihre Aufmerksamkeit!