Cuatro años de desarrollo de SObjectizer-5.5. ¿Cómo ha cambiado SObjectizer durante este tiempo?

La primera versión de SObjectizer dentro de la rama 5.5 se lanzó hace poco más de cuatro años, a principios de octubre de 2014. Y hoy se lanzó la próxima versión con el número 5.5.23 , que posiblemente cerrará la historia de SObjectizer-5.5. En mi opinión, esta es una gran razón para mirar hacia atrás y ver lo que se ha hecho en los últimos cuatro años.

En este artículo trataré de analizar de manera abstracta los cambios e innovaciones más importantes y significativos: qué se agregó, por qué, cómo afectó al SObjectizer o su uso.

Quizás alguien esté interesado en tal historia desde el punto de vista de la arqueología. Y alguien, tal vez, será disuadido de una aventura tan dudosa como desarrollar su propio marco de actor para C ++;)

Una pequeña digresión lírica sobre el papel de los viejos compiladores de C ++


La historia de SObjectizer-5 comenzó a mediados de 2010. Al mismo tiempo, nos centramos inmediatamente en C ++ 0x. Ya en 2011, las primeras versiones de SObjectizer-5 comenzaron a usarse para escribir código de producción. Está claro que entonces no teníamos compiladores con soporte normal de C ++ 11.

Durante mucho tiempo no pudimos utilizar por completo todas las características de "C ++ moderno": plantillas variadas, noexcept, constexpr, etc. Esto no podía sino afectar la API de SObjectizer. Y afectó durante mucho, mucho tiempo. Por lo tanto, si al leer una descripción de una función tiene una pregunta "¿Por qué no se ha hecho antes?", La respuesta a esta pregunta es muy probable: "Porque antes no era posible".

¿Qué ha aparecido y / o cambiado en SObjectizer-5.5 en el pasado?


En esta sección, repasaremos una serie de características que han tenido un impacto significativo en SObjectizer. El orden en esta lista es aleatorio y no está relacionado con el "significado" o el "peso" de las características descritas.

Rechazar el espacio de nombres so_5 :: rt


Que pasaba


Inicialmente, en el quinto SObjectizer, todo lo relacionado con el tiempo de ejecución de SObjectizer se definió dentro del espacio de nombres so_5 :: rt. Por ejemplo, tuvimos so_5 :: rt :: environment_t, so_5 :: rt :: agent_t, so_5 :: rt :: message_t, etc. Lo que puede ver, por ejemplo, en el ejemplo tradicional 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; } 

La abreviatura "rt" significa tiempo de ejecución. Y nos pareció que el registro "so_5 :: rt" es mucho mejor y más práctico que "so_5 :: runtime".

Pero resultó que para muchas personas "rt" es solo "tiempo real" y nada más. Y el uso de "rt" como una abreviatura de "tiempo de ejecución" viola sus sentimientos tanto que a veces los anuncios de las versiones de SObjectizer en RuNet se convirtieron en un holivar sobre el tema de la interpretación [no] permisible de "rt" que no sea "tiempo real".

Al final, estamos cansados ​​de eso. Y acabamos de eliminar el espacio de nombres so_5 :: rt.

¿En qué se ha convertido?


Todo lo que se definió dentro de "so_5 :: rt" simplemente cambió a "so_5". Como resultado, el mismo HelloWorld ahora se ve así:

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

Pero los nombres antiguos de "so_5 :: rt" permanecieron disponibles de todos modos, a través del uso habitual de s (typedefs). Por lo tanto, el código escrito para las primeras versiones de SO-5.5 también es viable en versiones recientes de SO-5.5.

Finalmente, el espacio de nombres so_5 :: rt se eliminará en la versión 5.6.

¿Qué impacto tuvo?


Probablemente, el código en SObjectizer ahora es más legible. Aún así, so_5 :: send () se percibe mejor que so_5 :: rt :: send ().

Bueno, aquí, como con los desarrolladores de SObjectizer, el dolor de cabeza ha disminuido. Hubo demasiadas charlas vacías y razonamientos innecesarios en torno a los anuncios de SObjectizer al mismo tiempo (comenzando por las preguntas “¿Por qué se necesitan actores en C ++ en general?” Y terminando por “Por qué no usas PascalCase para nombrar entidades”). Un tema inflamable se volvió menos y fue bueno :)

Simplifique el envío de mensajes y la evolución de los manejadores de mensajes.


Que pasaba


Incluso en las primeras versiones de SObjectizer-5.5, el mensaje habitual se enviaba utilizando el método deliver_message, que debía llamarse en el mbox del destinatario. Para enviar un mensaje pendiente o periódico, era necesario llamar a single_timer / schedule_timer en un objeto de tipo environment_t. Y enviar una solicitud síncrona a otro agente generalmente requería una cadena completa de operaciones. Aquí, por ejemplo, cómo se veía todo hace cuatro años (std :: make_unique (), que aún no estaba disponible en C ++ 11, ya está en uso):

 //   . mbox->deliver_message(std::make_unique<my_message>(...)); //   . env.single_timer(std::make_unique<my_message>(...), mbox, std::chrono::seconds(2)); //   . auto timer_id = env.schedule_timer( std::make_unique<my_message>(...), mbox, std::chrono::seconds(2), std::chrono::seconds(5)); //         10 . auto reply = mbox->get_one<std::string>() .wait_for(std::chrono::seconds(10)) .sync_get(std::make_unique<my_message>(...)); 

Además, el formato de los manejadores de mensajes en SObjectizer ha evolucionado a la versión 5.5. Si inicialmente en SObjectizer-5, todos los controladores deberían tener el formato:

 void evt_handler(const so_5::event_data_t<Msg> & cmd); 

luego, con el tiempo, se agregaron algunos más a los formatos permitidos:

 //  ,  Msg --  ,   . ret_value evt_handler(const Msg & msg); ret_value evt_handler(Msg msg); //  ,     . ret_value evt_handler(); 

Los nuevos formatos de manejador se han utilizado ampliamente desde pintar constantemente "const so_5 :: event_data_t <Msg> &" sigue siendo un placer. Pero, por otro lado, los formatos más simples no eran amigables para los agentes de plantilla. Por ejemplo:

 template<typename Msg_To_Process> class my_actor : public so_5::agent_t { void on_receive(const Msg_To_Process & msg) { // Oops! ... } }; 

Tal agente de plantilla solo funcionará si Msg_To_Process es un tipo de mensaje, no un tipo de señal.

¿En qué se ha convertido?


En la rama 5.5, apareció una familia de funciones de envío que evolucionó significativamente. Para hacer esto, en primer lugar, tuve que poner a mi disposición compiladores con soporte para plantillas variadas. Y, en segundo lugar, acumular suficiente experiencia trabajando tanto con plantillas variadas en general como con las primeras versiones de las funciones de envío. Además, en diferentes contextos: en agentes ordinarios, y en agentes ad-hoc, y en agentes implementados por clases de plantilla, y agentes externos en general. Incluso cuando se utilizan funciones de envío con mchains (se discutirán a continuación).

Además de las funciones de envío, aparecieron las funciones request_future / request_value, que están diseñadas para la interacción síncrona entre agentes.

Como resultado, ahora el envío de mensajes es el siguiente:

 //   . so_5::send<my_message>(mbox, ...); //   . so_5::send_delayed<my_message>(env, mbox, std::chrono::seconds(2), ...); //   . auto timer_id = so_5::send_periodic<my_message>( env, mbox, std::chrono::seconds(2), std::chrono::seconds(5), ...); //         10 . auto reply =so_5::request_value<std::string, my_message>(mbox, std::chrono::seconds(10), ...); 

Se ha agregado otro formato posible para los manejadores de mensajes. Además, es este formato el que se dejará en las próximas versiones principales de SObjectizer como el principal (y, posiblemente, el único). Este es el siguiente formato:

 ret_type evt_handler(so_5::mhood_t<Msg> cmd); 

Donde Msg puede ser un tipo de mensaje o un tipo de señal.

Este formato no solo difumina la línea entre los agentes en forma de clases ordinarias y los agentes en forma de clases de plantilla. Pero también simplifica el reenvío del mensaje / señal (gracias a la familia de funciones de envío):

 void my_agent::on_msg(mhood_t<Some_Msg> cmd) { ... // -  . //       . so_5::send(another_agent, std::move(cmd)); } 

¿Qué impacto tuvo?


La aparición de funciones de envío y manejadores de mensajes que reciben mhood_t <Msg>, podemos decir, cambió fundamentalmente el código en el que se envían y procesan los mensajes. Este es el caso cuando solo queda lamentar que al principio del trabajo en SObjectizer-5 no teníamos compiladores con soporte de plantillas variadas, ni experiencia en el uso de ellos. La familia de funciones de envío y mhood_t debería haber sido desde el principio. Pero la historia se ha desarrollado como se ha desarrollado ...

Soporte para tipos de mensajes personalizados.


Que pasaba


Inicialmente, se suponía que todos los mensajes enviados eran clases descendientes de la clase so_5 :: message_t. Por ejemplo:

 struct my_message : public so_5::message_t { ... //  my_message. my_message(...) : ... {...} //   my_message. }; 

Si bien el quinto SObjectizer fue utilizado solo por nosotros mismos, esto no planteó ninguna pregunta. Bueno, así y así.

Pero tan pronto como los usuarios de terceros comenzaron a interesarse en SObjectizer, inmediatamente nos encontramos con una pregunta que se repetía regularmente: "¿Debo heredar un mensaje de so_5 :: message_t?" Este problema era especialmente relevante en situaciones en las que era necesario enviar objetos de tipos como mensajes en los que el usuario no podía influir en absoluto. Digamos que un usuario usa un SObjectizer y alguna otra biblioteca externa. Y en esta biblioteca externa hay un cierto tipo M, los objetos que el usuario desea enviar como mensajes. Bueno, ¿y cómo en tales condiciones para hacer amigos, escriba M y so_5 :: message_t? Solo envoltorios adicionales que el usuario tuvo que escribir manualmente.

¿En qué se ha convertido?


Hemos agregado la capacidad de enviar mensajes a SObjectizer-5.5 incluso si el tipo de mensaje no se hereda de so_5 :: message_t. Es decir Ahora el usuario puede escribir fácilmente:

 so_5::send<std::string>(mbox, "Hello, World!"); 

So_5 :: message_t permanece bajo el capó de todos modos, solo debido a la plantilla magic send () entiende que std :: string no se hereda de so_5 :: message_t y no se construye un std :: string simple dentro de send, sino un heredero especial de so_5 :: message_t, dentro del cual la cadena std :: del usuario ya está ubicada.

Magia de plantilla similar se aplica a las suscripciones. Cuando SObjectizer ve un manejador de mensajes de la forma:

 void evt_handler(mhood_t<std::string> cmd) {...} 

entonces SObjectizer entiende que, de hecho, vendrá un mensaje especial con el objeto std :: string dentro. Y lo que necesita llamar al controlador con pasarle un enlace a std :: string desde este mensaje especial.

¿Qué impacto tuvo?


Usar SObjectizer se ha vuelto más fácil, especialmente cuando necesita enviar no solo objetos de sus propios tipos como mensajes, sino también objetos de tipo desde bibliotecas externas. Varias personas incluso se tomaron el tiempo para agradecer especialmente esta característica.

Mensajes mutables


Que pasaba


Inicialmente, en SObjectizer-5, solo se utilizó el modelo de interacción 1: N. Es decir un mensaje enviado podría tener más de un destinatario (o podría haber más de uno). Incluso si los agentes necesitaran interactuar en un modo 1: 1, aún se comunicaban a través de un buzón de múltiples productores / consumidores. Es decir en el modo 1: N, solo N en este caso era estrictamente una unidad.

En condiciones donde un mensaje puede ser recibido por más de un agente receptor, los mensajes enviados deben ser inmutables. Es por eso que los manejadores de mensajes tenían los siguientes formatos:

 //       . ret_type evt_handler(const event_data_t<Msg> & cmd); //       . ret_type evt_handler(const Msg & msg); //    . //        . ret_type evt_handler(Msg msg); 

En general, un enfoque simple y comprensible. Sin embargo, no es muy conveniente cuando los agentes necesitan comunicarse entre sí en un modo 1: 1 y, por ejemplo, transferir la propiedad de algunos datos entre sí. Digamos que no se puede hacer un mensaje tan simple si todos los mensajes son objetos estrictamente inmutables:

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

Más precisamente, tal mensaje podría ser enviado. Pero habiéndolo recibido como un objeto constante, no habría sido posible eliminar el contenido de process_image :: image_ para sí mismo. Tendría que marcar un atributo como mutable. Pero entonces perderíamos el control del compilador si process_image se envía por alguna razón en modo 1: N.

¿En qué se ha convertido?


En SObjectizer-5.5, se ha agregado la capacidad de enviar y recibir mensajes mutables. Al mismo tiempo, el usuario debe marcar especialmente el mensaje tanto al enviar como al suscribirse.

Por ejemplo:

 //    . so_5::send<my_message>(mbox, ...); //     my_message. so_5::send<so_5::mutable_msg<my_message>>(mbox, ...); ... //     . void my_agent::on_some_event(mhood_t<my_message> cmd) {...} //      my_message. void my_agent::on_another_event(mhood_t<so_5::mutable_msg<my_message>> cmd) {...} 

Para SObjectizer, my_message y mutable_msg <my_message> son dos tipos diferentes de mensajes.

Cuando una función de envío ve que se le está pidiendo que envíe un mensaje mutable, la función de envío verifica a qué buzón está intentando enviar el mensaje. Si se trata de un cuadro de múltiples consumidores, no se realiza el envío, pero se genera una excepción con el código de error correspondiente. Es decir SObjectizer garantiza que los mensajes mutables solo se pueden usar cuando interactúan en modo 1: 1 (a través de buzones de correo de un solo consumidor o mchains, que son una forma de buzones de un solo consumidor). Para proporcionar esta garantía, por cierto, SObjectizer prohíbe enviar mensajes mutables en forma de mensajes periódicos.

¿Qué impacto tuvo?


Con mensajes mutables resultó inesperadamente. Los agregamos a SObjectizer como resultado de una discusión al margen de un informe sobre SObjectizer en C ++ Rusia-2017 . Con el sentimiento: "Bueno, si preguntan, alguien lo necesita, así que vale la pena intentarlo". Bueno, lo hicieron sin mucha esperanza de una demanda generalizada. Aunque para esto tuve que "fumar bambú" durante mucho tiempo antes de pensar en cómo agregar mensajes mutables a SO-5.5 sin romper la compatibilidad.

Pero cuando aparecieron mensajes mutables en SObjectizer, resultó que no había tan pocas aplicaciones para ellos. Y que los mensajes mutables se usan sorprendentemente a menudo (se puede mencionar esto en la segunda parte de la historia sobre el proyecto de demostración de Shrimp ). En la práctica, esta característica fue más que útil, porque le permite resolver problemas que sin el soporte de mensajes mutables a nivel de SObjectizer, no tendrían una solución normal.

Agentes jerárquicos de máquinas de estado


Que pasaba


Los agentes en SObjectizer eran originalmente máquinas de estado. Los agentes tuvieron que describir explícitamente estados y suscribirse a mensajes en estados específicos.
Por ejemplo:

 class worker : public so_5::agent_t { state_t st_free{this, "free"}; state_t st_bufy{this, "busy"}; ... void so_define_agent() override { //     st_free. so_subscribe(mbox).in(st_free).event(...); //     st_busy. so_subscribe(mbox).in(st_busy).event(...); ... } }; 

Pero estas eran simples máquinas de estado. Los estados no podían estar anidados entre sí. No hubo soporte para los manejadores de entrada y salida del estado. No hubo restricciones en el tiempo pasado en el estado.

Incluso dicho soporte limitado para máquinas de estado fue conveniente y lo usamos durante más de un año. Pero en un momento, queríamos más.

¿En qué se ha convertido?


SObjectizer presenta soporte para máquinas de estado jerárquicas.

Ahora los estados se pueden anidar entre sí. Los controladores de eventos de los estados primarios son "heredados" automáticamente por los estados secundarios.

Se admiten controladores para entrar y salir de un estado.

Es posible establecer un límite en el tiempo que el agente permanece en el estado.

Es posible mantener una historia para el estado.

Para no ser infundado, aquí hay un ejemplo de un agente que no es una máquina de estados jerárquica compleja (el código del ejemplo estándar es intermitente):

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

Ya hemos descrito todo esto en un artículo separado ; no hay necesidad de repetirlo.

Actualmente no hay soporte para estados ortogonales. Pero este hecho tiene dos explicaciones. En primer lugar, tratamos de hacer este apoyo y enfrentamos una serie de dificultades, la superación de las cuales nos pareció demasiado costosa. Y, en segundo lugar, nadie ha pedido estados ortogonales todavía. Cuando se le pregunte, vuelva a este tema.

¿Qué impacto tuvo?


Existe la sensación de que es muy grave (aunque estamos aquí, por supuesto, subjetivos y sesgados). Después de todo, es una cosa cuando, cuando te enfrentas a complejas máquinas de estados finitos en el área temática, comienzas a buscar soluciones, simplificas algo, gastas fuerza extra en algo. Y es un asunto completamente diferente cuando puede asignar objetos de su aplicación a su código C ++ casi 1 en 1.

Además, a juzgar por las preguntas formuladas, por ejemplo, por el comportamiento de los manejadores de entrada / salida dentro / fuera del estado, se utiliza esta funcionalidad.

mchain


Que pasaba


Fue una situación interesante. SObjectizer se usaba a menudo para que solo una parte de la aplicación se escribiera en SObjectizer. El resto del código en la aplicación podría no tener nada que ver con los actores en general, o con SObjectizer en particular. Por ejemplo, una aplicación GUI en la que se utiliza SObjectizer para algunas tareas en segundo plano, mientras que el trabajo principal se realiza en el hilo principal de la aplicación.

Y en tales casos, resultó que desde la parte que no es SObjectizer a la parte SObjectizer, el envío de información es tan simple como simple: es suficiente para llamar a las funciones de envío ordinarias. Pero la difusión de información en la dirección opuesta no es tan simple. Nos pareció que esto no es bueno y que debería tener algunos canales de comunicación convenientes entre las partes SObjectizer de la aplicación y las partes no SObjectizer directamente de fábrica.

¿En qué se ha convertido?


Entonces, las cadenas de mensajes o, en la notación más familiar, aparecieron mchains en SObjectizer.

Mchain es una variante tan específica de un buzón de correo de un solo consumidor donde los mensajes se envían mediante funciones de envío regulares. Pero para extraer mensajes de mchain, no es necesario crear agentes y firmarlos. Hay dos funciones especiales que pueden llamarse incluso dentro de los agentes, incluso fuera de los agentes: recibir () y seleccionar (). El primero lee mensajes de un solo canal, mientras que el segundo puede leer mensajes de varios canales a la vez:

 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>){...}, ... )); 

Ya hemos hablado de mchain varias veces aquí: en agosto de 2017 y en mayo de 2018 . Por lo tanto, especialmente sobre el tema de cómo se ve el trabajo con mchains, no profundizaremos aquí.

¿Qué impacto tuvo?


Después de la aparición de mchains en SObjectizer-5.5, resultó que SObjectizer, de hecho, se convirtió en un marco aún menos "actor" que antes. Además de soportar el Modelo de actor y Pub / Sub, SObjectizer también agregó soporte para el modelo CSP (comunicación de procesos secuenciales). Mchains le permite desarrollar aplicaciones multiproceso bastante complejas en SObjectizer sin ningún actor. Y para algunas tareas esto es más que conveniente. Lo que nosotros mismos usamos de vez en cuando.

Mecanismo de límites de mensajes


Que pasaba


Una de las deficiencias más graves del modelo de actores es su predisposición a la sobrecarga. Es muy fácil encontrarse en una situación en la que el actor emisor envía mensajes al actor receptor a un ritmo más rápido que el que puede procesar los mensajes.

Como regla, enviar mensajes en marcos de actores es una operación sin bloqueo. Por lo tanto, cuando ocurre un par de "productor ágil y consumidor nerd", la cola del actor receptor aumentará mientras haya al menos algún tipo de memoria libre.

La principal dificultad de este problema es que se debe afinar un buen mecanismo de protección contra sobrecarga para la tarea aplicada y las características del área temática. Por ejemplo, para comprender qué mensajes pueden duplicarse (y, por lo tanto, poder descartar duplicados de forma segura). Para entender qué mensajes no se pueden tirar de todos modos. Quién puede ser suspendido y por cuánto, y quién no está permitido en absoluto. Etc., etc.

Otra dificultad es que no siempre es necesario tener un buen mecanismo de defensa. A veces, es suficiente tener algo primitivo, pero efectivo, accesible "listo para usar" y fácil de usar. Para no obligar al usuario a hacer su control de sobrecarga donde es suficiente simplemente lanzar mensajes "extra" o reenviar estos mensajes a otro agente.

¿En qué se ha convertido?


Solo para que en escenarios simples pueda usar herramientas de protección contra sobrecarga ya preparadas, las llamadas Límites del mensaje. Este mecanismo le permite descartar mensajes innecesarios, o enviarlos a otros destinatarios, o incluso simplemente interrumpir la aplicación si se exceden los límites. Por ejemplo:

 class worker : public so_5::agent_t { public: worker(context_t ctx) : so_5::agent_t{ ctx //     100  handle_data, //      //  . + limit_then_redirect<handle_data>(100, [this]{ return another_worker_;}) //     1  check_status, //       . + limit_then_drop<check_status>(1) //     1  reconfigure, //     , ..  reconfigure //        . + limit_then_abore<reconfigure>(1) } {...} ... }; 

Este tema se describe con más detalle en un artículo separado .

¿Qué impacto tuvo?


Esto no quiere decir que la aparición de límites de mensajes se haya convertido en algo que ha cambiado fundamentalmente SObjectizer, los principios de su trabajo o su trabajo con él. Más bien, se puede comparar con un paracaídas de reserva, que se usa solo como último recurso. Pero cuando tienes que usarlo, te alegra que incluso exista.

Mecanismo de rastreo de entrega de mensajes


Que pasaba


SObjectizer-5 fue una caja negra para desarrolladores. En el que se envía el mensaje y ... Y llega al destinatario o no llega.

Si el mensaje no llega al destinatario, el usuario se enfrenta a la necesidad de realizar una búsqueda emocionante en busca de un motivo. En la mayoría de los casos, los motivos son triviales: el mensaje se envió al mbox incorrecto o no se realizó la suscripción (por ejemplo, el usuario realizó una suscripción en un estado del agente, pero olvidó hacerlo en otro). Pero puede haber casos más complejos cuando un mensaje es, por ejemplo, rechazado por un mecanismo de protección contra sobrecarga.

El problema era que el mecanismo de entrega del mensaje estaba oculto en los menudillos de SObjectizer Run-Time y, por lo tanto, era difícil incluso para los desarrolladores de SObjectizer enrutar el mensaje al destinatario, sin mencionar a los usuarios. Especialmente sobre los usuarios novatos que cometieron la mayor cantidad de errores tan triviales.

¿En qué se ha convertido?


En SObjectizer-5.5, se agregó un mecanismo especial para rastrear el proceso de entrega de mensajes llamado rastreo de entrega de mensajes (o simplemente msg_tracing), y luego se finalizó. Este mecanismo y sus capacidades se describieron con más detalle en un artículo separado .

Entonces, si los mensajes se pierden en la entrega, simplemente puede habilitar msg_tracing y ver por qué sucede esto.

¿Qué impacto tuvo?


La depuración de aplicaciones escritas en SObjectizer se ha vuelto mucho más simple y agradable. Incluso para nosotros mismos .

El concepto de env_infrastructure y env_infrastructures de un solo subproceso


Que pasaba


Siempre hemos considerado SObjectizer como una herramienta para simplificar el desarrollo de código multiproceso. Por lo tanto, las primeras versiones de SObjectizer-5 se escribieron para funcionar solo en un entorno de subprocesos múltiples.

Esto se expresó como el uso de primitivas de sincronización dentro del SObjectizer para proteger las partes internas del SObjectizer cuando se trabaja en un entorno de subprocesos múltiples. Por lo tanto, se trata de crear varios subprocesos de trabajo auxiliares dentro del SObjectizer (para realizar operaciones tan importantes como dar servicio al temporizador y completar la cancelación del registro de las cooperaciones de agentes).

Es decirSObjectizer fue creado para la programación de subprocesos múltiples y para su uso en entornos de subprocesos múltiples. Y eso nos vino perfectamente.

Sin embargo, como SObjectizer se usó "en la naturaleza", se descubrieron situaciones en las que la tarea era lo suficientemente difícil como para usar actores en su solución. Pero, al mismo tiempo, todo el trabajo podía y, además, tenía que realizarse en un solo flujo de trabajo.

Y nos enfrentamos a un problema muy interesante: ¿es posible enseñar a SObjectizer a trabajar en un solo hilo de trabajo?

¿En qué se ha convertido?


Resultó que es posible.

Nos costó mucho dinero, nos llevó mucho tiempo y esfuerzo encontrar una solución. Pero la solución fue inventada.

Se introdujo un concepto como infraestructura de entorno (o env_infrastructure en una forma ligeramente abreviada). Env_infrastructure asumió la tarea de administrar la cocina interna de SObjectizer. En particular, resolvió problemas tales como el mantenimiento de temporizadores, el registro y la cancelación del registro de las cooperativas.

Para SObjectizer, se han realizado varias opciones env_infrastructures de subproceso único. Esto nos permitió desarrollar aplicaciones de subproceso único en SObjectizer, dentro de las cuales hay agentes normales que intercambian mensajes regulares entre sí.

Hablamos sobre esta funcionalidad con más detalle en un artículo separado. .

¿Qué impacto tuvo?


Quizás lo más importante que sucedió durante la implementación de esta característica fue la ruptura de nuestras propias plantillas. Una mirada a SObjectizer nunca será la misma. Durante tantos años, considere SObjectizer exclusivamente como una herramienta para desarrollar código multiproceso. Y luego otra vez! Y descubra que también se puede desarrollar código de subproceso único en SObjectizer. La vida está llena de sorpresas.

Herramientas de monitoreo en tiempo de ejecución


Que pasaba


SObjectizer-5 era una caja negra no solo en términos de mecanismo de entrega de mensajes. Pero tampoco había manera de averiguar cuántos agentes trabajan actualmente dentro de la aplicación, cuántos y qué despachadores se crean, cuántos hilos de trabajo están involucrados, cuántos mensajes están esperando en las colas de los despachadores, etc.

Toda esta información es muy útil para monitorear aplicaciones que se ejecutan 24/7. Pero para la depuración, también me gustaría entender de vez en cuando si las colas están creciendo o si el número de agentes está aumentando / disminuyendo.

Desafortunadamente, por el momento, nuestras manos simplemente no llegaron al punto de agregar fondos a SObjectizer para recopilar y difundir dicha información.

¿En qué se ha convertido?


En un momento en SObjectizer-5.5, aparecieron herramientas para monitorear en tiempo de ejecución los componentes internos de SObjectizer . De manera predeterminada, la supervisión en tiempo de ejecución está deshabilitada, pero si la habilita, los mensajes se enviarán regularmente al mbox especial, dentro del cual habrá información sobre el número de agentes y cooperaciones, sobre el número de temporizadores, sobre los hilos de trabajo propiedad de los despachadores (y ya habrá información sobre el número de mensajes en las colas, el número de agentes vinculados a estos hilos).

Además, con el tiempo, fue posible habilitar adicionalmente la recopilación de información sobre cuánto tiempo pasan los agentes dentro de los controladores de eventos. Esto le permite detectar situaciones en las que algún agente es demasiado lento (o pierde tiempo bloqueando llamadas).

¿Qué impacto tuvo?


En nuestra práctica, la supervisión en tiempo de ejecución no se usa con frecuencia. Pero cuando lo necesitas, te das cuenta de su importancia. De hecho, sin dicho mecanismo es imposible (bueno o muy difícil) descubrir qué y cómo [no] funciona.

Por lo tanto, esta es una característica de la categoría "puede hacerlo", pero su presencia, en nuestra opinión, transfiere inmediatamente el instrumento a otra categoría de peso. PorqueHacer un prototipo de un marco de actor "en la rodilla" no es tan difícil. Muchos lo han hecho y muchos más lo harán. Pero luego, para equipar su desarrollo con algo como el monitoreo en tiempo de ejecución ... Por ahora, no todos los borradores de rodilla sobreviven.

Y una cosa más en una línea.


Durante cuatro años, SObjectizer-5.5 ha tenido muchas innovaciones y cambios, cuya descripción, incluso en una sinopsis, ocupará demasiado espacio. Por lo tanto, denotamos parte de ellos literalmente por una línea. En orden aleatorio, sin ninguna prioridad.

SObjectizer-5.5 agrega soporte para el sistema de compilación CMake.

Ahora SObjectizer-5 se puede construir como una biblioteca dinámica y estática.

SObjectizer-5.5 ahora está construido y se ejecuta en Android (tanto a través de CrystaX NDK como a través de Android NDK reciente).

Despachadores privados han aparecido. Ahora puede crear y usar despachadores que nadie más ve.

Implementado el mecanismo de filtros de entrega. Ahora, al suscribirse a mensajes de MPMC-mboxes, puede prohibir la entrega de mensajes cuyo contenido no le interese.

Las herramientas para crear y registrar cooperaciones se han simplificado significativamente: los métodos introducen_coop / introducen_coco_infantil, make_agent / make_agent_with_binder y eso es todo.

Apareció el concepto de una fábrica de objetos de bloqueo y ahora puede elegir qué objetos de bloqueo necesita (en función de mutex, spinlock, combinado u otro).

Ha aparecido la clase wrap_env_t y ahora puede ejecutar SObjectizer en su aplicación no solo con so_5 :: launch ().

El concepto de stop_guards apareció y ahora puede influir en el proceso de apagado de SObjectizer. Por ejemplo, puede evitar que SObjectizer se detenga hasta que algunos agentes hayan completado su trabajo de aplicación.

Ahora puede interceptar los mensajes que se entregaron al agente pero que no fueron procesados ​​por el agente (los llamados controladores de boletines muertos).

Hubo una oportunidad para envolver mensajes en "sobres" especiales. Los sobres pueden llevar información adicional sobre el mensaje y pueden realizar alguna acción cuando el mensaje se entrega al destinatario.

5.5.0 a 5.5.23 en números


También es interesante observar la ruta realizada en términos de código / pruebas / ejemplos. Esto es lo que la utilidad cloc nos dice sobre la cantidad de código del núcleo SObjectizer-5.5.0:

 -------------------------------------------------- -----------------------------
Código de comentario en blanco de archivos de idioma
-------------------------------------------------- -----------------------------
C / C ++ Encabezado 58 2119 5156 5762
C ++ 39 1167 779 4759
Rubí 2 30 2 75
-------------------------------------------------- -----------------------------
SUMA: 99 3316 5937 10596
-------------------------------------------------- -----------------------------

Y aquí está lo mismo, pero para v.5.5.23 (de las cuales 1147 líneas son el código de la biblioteca opcional-lite):
 -------------------------------------------------- -----------------------------
Código de comentario en blanco de archivos de idioma
-------------------------------------------------- -----------------------------
Encabezado C / C ++ 133 6279 22173 21068
C ++ 53 2498 2760 10398
CMake 2 29 0 177
Rubí 4 53 2 129
-------------------------------------------------- -----------------------------
SUMA: 192 8859 24935 31772
-------------------------------------------------- -----------------------------

Volumen de pruebas para v.5.5.0:
 -------------------------------------------------- -----------------------------
Código de comentario en blanco de archivos de idioma
-------------------------------------------------- -----------------------------
C ++ 84 2510 390 11540
Rubí 162496 0 1054
C / C ++ Encabezado 1 11 0 32
-------------------------------------------------- -----------------------------
SUMA: 247 3017 390 12626
-------------------------------------------------- -----------------------------

Pruebas para v.5.5.23:
 -------------------------------------------------- -----------------------------
Código de comentario en blanco de archivos de idioma
-------------------------------------------------- -----------------------------
C ++ 324 7345 1305 35231
Rubí 675 2,353 0 4,671
CMake 338 43 0 955
C / C ++ Encabezado 11107 3 448
-------------------------------------------------- -----------------------------
SUMA: 1348 9848 1308 41305

Bueno, ejemplos para v.5.5.0:
 -------------------------------------------------- -----------------------------
Código de comentario en blanco de archivos de idioma
-------------------------------------------------- -----------------------------
C ++ 27 765 463 3322
Rubí 28 95 0 192
-------------------------------------------------- -----------------------------
SUMA: 55860 463 3514

Lo son, pero ya para v.5.5.23:
 -------------------------------------------------- -----------------------------
Código de comentario en blanco de archivos de idioma
-------------------------------------------------- -----------------------------
C ++ 67 2141 2061 9341
Rubí 133 451 0 868
CMake 67 93 0 595
C / C ++ Encabezado 1 12 11 32
-------------------------------------------------- -----------------------------
SUMA: 268 2697 2072 10836

Casi en todas partes, un aumento de casi tres veces.

Y la cantidad de documentación para SObjectizer probablemente ha aumentado aún más.

Planes para el futuro cercano (y no solo)


Los planes preliminares de desarrollo para SObjectizer después del lanzamiento de la versión 5.5.23 se describieron aquí hace aproximadamente un mes. Fundamentalmente, no han cambiado. Pero tenía la sensación de que la versión 5.6.0, cuyo lanzamiento está previsto para principios de 2019, tendrá que posicionarse como el comienzo de la próxima rama estable de SObjectizer. Teniendo en cuenta el hecho de que durante 2019, SObjectizer se desarrollará bajo la rama 5.6 sin ningún cambio significativo.

Esto permitirá a aquellos que ahora usan SO-5.5 en sus proyectos cambiar gradualmente a SO-5.6 sin temor a que también tengan que cambiar a SO-5.7.

La versión 5.7, en la que queremos permitirnos desviarnos de los principios básicos de SO-5.5 y SO-5.6, se considerará experimental en 2019. Con estabilización y liberación, si todo va bien, ya en el año 2020.

Conclusión


En conclusión, me gustaría agradecer a todos los que nos ayudaron con el desarrollo de SObjectizer todo este tiempo. Y me gustaría dar las gracias por separado a todos los que se atrevieron a intentar trabajar con SObjectizer. Sus comentarios siempre nos han sido muy útiles.

Queremos decirles a aquellos que aún no han usado SObjectizer: pruébalo. Esto no es tan aterrador como podría parecer.

Si no le gustó algo o no tuvo suficiente en SObjectizer, díganos. Siempre escuchamos críticas constructivas. Y, si está en nuestro poder, damos vida a los deseos de los usuarios.

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


All Articles