Novo no SObjectizer-5.5.23: realização de desejos ou caixa de Pandora?



Este artigo é uma continuação de um artigo de reflexão publicado há um mês: " É fácil adicionar novos recursos à estrutura antiga? A agonia da escolha baseada no exemplo do desenvolvimento do SObjectizer ". Esse artigo descreveu a tarefa que queríamos resolver na próxima versão do SObjectizer, examinou duas abordagens para sua solução e listou as vantagens e desvantagens de cada uma das abordagens.

Com o passar do tempo, uma das abordagens foi implementada e novas versões do SObjectizer , bem como o projeto anexo so_5_extra , já chamado de " respirar profundamente ". Você pode literalmente pegar e tentar.

Hoje falaremos sobre o que foi feito, por que foi feito, o que levou a isso. Se alguém estiver interessado em acompanhar o desenvolvimento de uma das poucas estruturas de atores ao vivo, de plataforma cruzada e de plataforma aberta para C ++, você é bem-vindo ao gato.

Como tudo começou?


Tudo começou com uma tentativa de resolver o problema do cancelamento garantido de temporizadores. A essência do problema é que, quando uma mensagem periódica ou atrasada é enviada, o programador pode cancelar a entrega da mensagem. Por exemplo:

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

Depois de chamar timer_id.release (), o timer não enviará mais novas instâncias da mensagem my_message. Mas as cópias que já foram enviadas e estão na fila de destinatários não serão levadas a lugar algum. Com o tempo, eles serão extraídos dessas mesmas filas e transferidos para os agentes destinatários para processamento.

Esse problema é uma conseqüência dos princípios básicos de operação do SObjectizer-5 e não possui uma solução simples, pois o SObjectizer não pode extrair mensagens das filas. Não é possível porque as filas no SObjectizer pertencem a despachantes, despachantes são diferentes, suas filas também são organizadas de maneira diferente. Inclusive, existem despachantes que não fazem parte do SObjectizer e o SObjectizer, em princípio, não pode saber como esses despachantes funcionam.

Em geral, existe esse recurso nos temporizadores nativos do SObjectizer. Não que isso estrague demais os desenvolvedores. Mas alguns cuidados extras devem ser tomados. Especialmente para iniciantes que estão apenas se familiarizando com a estrutura.

E então, finalmente, as mãos chegaram ao ponto de propor uma solução para esse problema.

Qual caminho da solução foi escolhido?


Em um artigo anterior , duas opções possíveis foram consideradas. A primeira opção não exigiu modificações no mecanismo de entrega de mensagens no SObjectizer, mas exigiu que o programador alterasse explicitamente o tipo de mensagem enviada / recebida.

A segunda opção exigia a modificação do mecanismo de entrega de mensagens do SObjectizer. Foi esse caminho escolhido, pois permitiu ocultar do destinatário da mensagem o fato de que a mensagem foi enviada de alguma maneira específica.

O que mudou no SObjectizer?


Novo conceito: envelope com mensagem dentro


O primeiro componente da solução implementada é a adição de um conceito como um envelope ao SObjectizer. Um envelope é uma mensagem especial, dentro da qual está a mensagem atual (carga útil). O SObjectizer entrega o envelope com a mensagem ao destinatário quase da maneira usual. A diferença fundamental no processamento de envelopes é detectada apenas no último estágio de entrega:

  • na entrega de uma mensagem regular, o agente destinatário simplesmente procura um manipulador para esse tipo de mensagem e, se esse manipulador for encontrado, o manipulador encontrado será chamado e a mensagem entregue retornará como parâmetro;
  • e após a entrega do envelope com a mensagem após a localização do manipulador, primeiro é feita uma tentativa de tirar a mensagem do envelope. E somente se o envelope fornecer a mensagem armazenada nele, somente o manipulador será chamado.

Existem dois pontos principais aqui que têm um grande impacto sobre o porquê e como os envelopes de mensagens podem ser usados.

O primeiro ponto importante é que uma mensagem é solicitada a partir de um envelope somente quando um manipulador de mensagem é encontrado no destinatário. I.e. somente quando a mensagem realmente foi entregue ao destinatário e o destinatário estará aqui e agora processará essa mensagem.

O segundo ponto-chave aqui é que o envelope pode não revelar a mensagem. Ou seja, por exemplo, um envelope pode verificar o horário atual e decidir que todas as datas de entrega foram perdidas e, portanto, a mensagem deixou de ser relevante e não pode ser processada. Portanto, o envelope não dará a mensagem. Assim, o SObjectizer simplesmente ignorará esse envelope e não executará nenhuma ação adicional.

Como é um envelope?


Um envelope é uma implementação da interface envelope_t, definida da seguinte maneira:

 class SO_5_TYPE envelope_t : public message_t { public: ... // -. //   ,       //    . virtual void handler_found_hook( handler_invoker_t & invoker ) noexcept = 0; //   ,      //     . virtual void transformation_hook( handler_invoker_t & invoker ) noexcept = 0; private : kind_t so5_message_kind() const noexcept override { return kind_t::enveloped_msg; } }; 

I.e. Um envelope é essencialmente a mesma mensagem que todos os outros. Mas com um atributo especial, retornado pelo método so5_message_kind ().

O programador pode desenvolver seus envelopes herdando de envelope_t (ou, mais convenientemente, de so_5 :: extra :: enveloped_msg :: just_envelope_t ) e substituindo os métodos de gancho handler_found_hook () e transform_hook ().

Dentro dos métodos do gancho, o desenvolvedor do envelope decide se deseja enviar a mensagem dentro do envelope para processamento / transformação ou não. Se ele quiser, o desenvolvedor deve chamar o método invoke () e o objeto invoker. Se ele não quiser, não liga. Nesse caso, o envelope e seu conteúdo serão ignorados.

Como os envelopes resolvem o problema de cancelar temporizadores?


A solução, que agora é implementada em so_5_extra na forma do namespace so_5 :: extra :: revocable_timer, é muito simples: quando uma mensagem periódica ou pendente é enviada especialmente, um envelope especial é criado, dentro do qual está localizada não apenas a mensagem em si, mas também a bandeira atômica revogada. Se esse sinalizador estiver limpo, a mensagem será considerada relevante. Se definida, a mensagem é considerada retirada.

Quando o método do gancho é chamado no envelope, o envelope verifica o valor do sinalizador revogado. Se o sinalizador estiver definido, o envelope não enviará uma mensagem. Portanto, a mensagem não é processada, mesmo que o timer já tenha conseguido colocar a mensagem na fila do destinatário.

Extensão da interface abstract_message_box_t


Adicionar a interface envelope_t é apenas uma parte da implementação de envelopes no SObjectizer. A segunda parte está levando em consideração o fato da existência de envelopes no mecanismo de entrega de mensagens dentro do SObjectizer.

Aqui, infelizmente, não poderia deixar de tornar as alterações visíveis para o usuário. Em particular, na classe abstract_message_box_t, que define a interface de todas as caixas de correio no SObjectizer, foi necessário adicionar outro método virtual:

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

Este método é responsável por entregar um envelope de mensagem com uma mensagem do tipo msg_type ao destinatário. Essa entrega pode diferir nos detalhes da implementação, dependendo do tipo de mbox que é.

Ao adicionar do_deliver_enveloped_msg () a abstract_message_box_t, tivemos uma opção: torná-lo um método virtual puro ou oferecer algum tipo de implementação padrão.

Se tornássemos do_deliver_enveloped_msg () um método virtual puro, quebraríamos a compatibilidade entre as versões do SObjectizer na ramificação 5.5. Afinal, os usuários que escreveram suas próprias implementações de mbox teriam que modificar suas próprias mboxes ao mudar para o SObjectizer-5.5.23; caso contrário, não seriam capazes de compilar com a nova versão do SObjectizer.

Como não queríamos isso, não transformamos do_deliver_enveloped_msg () em um método virtual puro na v.5.5.23. Ele tem uma implementação padrão que apenas lança uma exceção. Assim, o usuário mbox-s personalizado poderá continuar trabalhando normalmente com mensagens regulares, mas se recusará automaticamente a aceitar envelopes. Achamos esse comportamento mais aceitável. Além disso, no estágio inicial, é improvável que os envelopes com mensagens sejam amplamente utilizados e é improvável que nas implementações personalizadas "selvagens" das mboxes SObjectizer sejam frequentemente encontradas;)

Além disso, existe uma chance zero de que nas versões principais subsequentes do SObjectizer, onde não veremos a compatibilidade com a ramificação 5.5, a interface abstract_message_box_t sofra grandes mudanças. Mas já estamos nos adiantando ...

Como enviar envelopes com mensagens


O SObjectizer-5.5.23 em si não fornece um meio simples de enviar envelopes. Supõe-se que um tipo específico de envelope e ferramentas apropriadas estejam sendo desenvolvidas para uma tarefa específica para enviar convenientemente envelopes de um tipo específico. Um exemplo disso pode ser visto em so_5 :: extra :: revocable_timer , onde você precisa não apenas enviar o envelope, mas também fornecer ao usuário um timer_id especial.

Para situações mais simples, você pode usar as ferramentas de so_5 :: extra :: enveloped_msg . Por exemplo, é assim que uma mensagem é enviada com uma determinada restrição no momento da entrega:

 // make     . so_5::extra::enveloped_msg::make<my_message>(... /*    */) // envelope         . //  5s        . .envelope<so_5::extra::enveloped_msg::time_limited_delivery_t>(5s) //        . .send_to(destination); 

Para tornar tudo divertido: envelopes em envelopes


Os envelopes foram projetados para transportar algumas mensagens dentro de si. Mas quais?

Qualquer.

E isso nos leva a uma pergunta interessante: é possível colocar um envelope dentro de outro envelope?

Sim você pode. Tanto quanto você quiser. A profundidade de aninhamento é limitada apenas pelo senso comum do desenvolvedor e pela profundidade da pilha para a chamada recursiva handler_found_hook / transform_hook.

Ao mesmo tempo, o SObjectizer vai para os desenvolvedores de seus próprios envelopes: o envelope não deve pensar no que está dentro dele - uma mensagem específica ou outro envelope. Quando o método hook é chamado no envelope e o envelope decide que pode fornecer seu conteúdo, o envelope simplesmente chama invoke () em handler_invoker_t e passa um link para seu conteúdo em invoke (). E já invoke () por dentro descobrirá com o que está lidando. E se esse for outro envelope, invoke () chamará o método de gancho necessário nesse envelope.

Usando o kit de ferramentas mostrado acima em so_5 :: extra :: enveloped_msg, o usuário pode criar vários envelopes aninhados como este:

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

Alguns exemplos de uso de envelopes


Agora, depois de examinarmos as partes internas do SObjectizer-5.5.23, é hora de passar para a parte mais útil do aplicativo para os usuários. Abaixo estão alguns exemplos que são baseados no que já está implementado em so_5_extra ou usam as ferramentas de so_5_extra.

Temporizadores revogáveis


Como toda essa cozinha com envelopes foi concebida para resolver o problema da recuperação garantida de mensagens temporizadas, vamos ver o que aconteceu no final. Usaremos o exemplo de so_5_extra-1.2.0, que usa as ferramentas do novo espaço de nome so_5 :: extra :: revocable_timer:

Exemplo de código com temporizadores revogáveis
 #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; } 


O que temos aqui?

Temos um agente que primeiro inicia várias mensagens de timer e depois bloqueia seu thread de trabalho por um tempo. Durante esse período, o cronômetro consegue enfileirar várias solicitações no agente como resultado dos cronômetros acionados: várias instâncias periódicas, uma instância de first_delayed e second_delayed.

Portanto, quando um agente desbloqueia seu encadeamento, ele deve receber o primeiro periódico e o primeiro_delayed. Ao processar first_delayed, o agente cancela a entrega periódica e second_delayed. Portanto, esses sinais não devem alcançar o agente, independentemente de eles já estarem na fila do agente ou não (e estão).

Vemos o resultado do exemplo:

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

É sim. Obteve o primeiro periódico e o primeiro_delayed. Então não há periódico nem segundo_delayed.

Mas se no exemplo substituirmos os "temporizadores" de so_5 :: extra :: revocable_timer pelos temporizadores padrão do SObjectizer, o resultado será diferente: todas as instâncias de sinal periódicas e de segundo atraso que já entraram na fila do agente chegarão ao agente.

Mensagens restritas de tempo de entrega


Outra coisa útil, às vezes, que ficará disponível no so_5_extra-1.2.0 é a entrega de mensagens com um limite de tempo. Por exemplo, o agente request_handler envia uma mensagem confirm_signature ao agente crypto_master. Ao mesmo tempo, request_handler deseja que o check_signature seja entregue dentro de 5 segundos. Se isso não acontecer, não haverá sentido no processamento da verity_signature, o agente request_handler já interromperá seu trabalho.

E o agente crypto_master é um camarada que gosta de se tornar um "gargalo": às vezes ele começa a desacelerar. Nesse momento, as mensagens são acumuladas na fila, como a acima verifique a assinatura, que pode esperar até que o crypto_master seja liberado.

Suponha que o request_handler tenha enviado uma mensagem confirm_signature ao agente crypto_master, mas o crypto_master ficou atolado e ficou preso por 10 segundos. O agente request_handler já "caiu", ou seja, já enviou a todos uma negação de serviço e concluiu seu trabalho. Mas a mensagem confirm_signature permanece na fila crypto_master! Portanto, quando o crypto_master "desmarcar", ela receberá esta mensagem e a processará. Embora isso não seja mais necessário.

Utilizando o novo envelope so_5 :: extra :: enveloped_msg :: time_limited_delivery_t, podemos resolver este problema: o agente request_handler enviará a verificação_signature time_limited_delivery_t entre o envelope com um prazo de entrega:

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

Agora, se o crypto_master "gruda" e não consegue verificar a assinatura em 5 segundos, o envelope simplesmente não envia essa mensagem para processamento. E o crypto_master não fará o trabalho que ninguém mais precisa.

Relatórios de entrega de destinatários


E, finalmente, um exemplo de algo curioso que não é implementado regularmente no SObjectizer ou no so_5_extra, mas que pode ser feito independentemente.

Às vezes, você deseja receber do SObjectizer algo como uma mensagem de "relatório de entrega" para o destinatário. Afinal, uma coisa é quando a mensagem chegou ao destinatário, mas o destinatário, por algum motivo, não respondeu a ela. Outra coisa é quando a mensagem não chegou ao destinatário. Por exemplo, foi bloqueado por um mecanismo de proteção contra sobrecarga do agente . No primeiro caso, uma mensagem para a qual não esperamos uma resposta pode ser omitida. Mas no segundo caso, pode fazer sentido reenviar a mensagem depois de algum tempo.

Agora vamos considerar como o mecanismo mais simples de "relatórios de entrega" pode ser implementado usando envelopes.

Então, primeiro fazemos as etapas preparatórias necessárias:

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

Agora podemos definir as mensagens que serão usadas no exemplo. A primeira mensagem é uma solicitação para executar algumas ações que precisamos. E a segunda mensagem é uma confirmação de que a primeira mensagem chegou ao destinatário:

 struct request_t final { request_id_t m_id; std::string m_data; }; struct delivery_receipt_t final { //   request_t::m_id   request_t. request_id_t m_id; }; 

Em seguida, podemos definir um agente processador_t que processará mensagens do tipo request_t. Mas o processamento será feito com uma imitação de "aderência". I.e. processa request_t, após o qual altera seu estado de st_normal para st_busy. No estado st_busy, ele não faz nada e ignora todas as mensagens que chegam a ele.

Isso significa que, se o agente do processador_t enviar três mensagens request_t consecutivas, ele processará a primeira e as outras duas serão lançadas, porque ao processar a primeira mensagem, o agente irá para st_busy e ignorará o que virá enquanto estiver em st_busy.

No st_busy, o agente processador_t passará 2 segundos, após o qual retornará novamente ao st_normal e estará pronto para processar novas mensagens.

Aqui está a aparência do agente processor_t:

 class processor_t final : public so_5::agent_t { //   .     //   . state_t st_normal{this, "normal"}; //  " ".   . state_t st_busy{this, "busy"}; public: processor_t(context_t ctx) : so_5::agent_t{std::move(ctx)} { this >>= st_normal; st_normal.event(&processor_t::on_request); //     ,    . //  2   ,    st_normal. st_busy.time_limit(2s, st_normal); } private: void on_request(mhood_t<request_t> cmd) { std::cout << "processor: on_request(" << cmd->m_id << ", " << cmd->m_data << ")" << std::endl; this >>= st_busy; } }; 

Agora podemos definir o agente orders_generator_t, que possui várias solicitações que precisam ser entregues ao processador_t. O agente request_generator_t envia o pacote inteiro a cada 3 segundos e aguarda a confirmação da entrega na forma de delivery_receipt_t.

Quando delivery_recept_t chega, o agente orders_generator_t lança a solicitação entregue fora do pacote configurável. Se o pacote estiver completamente vazio, o exemplo será concluído. Se algo mais restar, o pacote restante será enviado novamente na próxima vez que o reenvio chegar.

Então, aqui está o código do agente request_generator_t. É bastante volumoso, mas primitivo. Você só pode prestar atenção às informações internas do método send_requests (), no qual as mensagens request_t são enviadas, incluídas em um envelope especial.

Código do agente Requests_generator_t
 class requests_generator_t final : public so_5::agent_t { //    . const so_5::mbox_t m_processor; //  ,       . std::map<request_id_t, std::string> m_requests; struct resend_requests final : public so_5::signal_t {}; public: requests_generator_t(context_t ctx, so_5::mbox_t processor) : so_5::agent_t{std::move(ctx)} , m_processor{std::move(processor)} { so_subscribe_self() .event(&requests_generator_t::on_delivery_receipt) .event(&requests_generator_t::on_resend); } void so_evt_start() override { //    . m_requests.emplace(0, "First"); m_requests.emplace(1, "Second"); m_requests.emplace(2, "Third"); m_requests.emplace(3, "Four"); //  . send_requests(); } private: void on_delivery_receipt(mhood_t<delivery_receipt_t> cmd) { std::cout << "request delivered: " << cmd->m_id << std::endl; m_requests.erase(cmd->m_id); if(m_requests.empty()) //    .  . so_deregister_agent_coop_normally(); } void on_resend(mhood_t<resend_requests>) { std::cout << "time to resend requests, pending requests: " << m_requests.size() << std::endl; send_requests(); } void send_requests() { for(const auto & item : m_requests) { std::cout << "sending request: (" << item.first << ", " << item.second << ")" << std::endl; envelope_ns::make<request_t>(item.first, item.second) .envelope<custom_envelope_t>(so_direct_mbox(), item.first) .send_to(m_processor); } //       3 . so_5::send_delayed<resend_requests>(*this, 3s); } }; 

Agora, temos mensagens e agentes que devem usar essas mensagens para se comunicar. Restava apenas uma pequena coisa - de alguma forma, fazendo com que as mensagens delivery_receipt_t chegassem ao entregar request_t ao processador_t.

Isso é feito usando este envelope:

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

Em geral, não há nada complicado. Herdamos de so_5 :: extra :: enveloped_msg :: just_envelope_t. Esse é um tipo auxiliar de envelope que armazena a mensagem incluída nele e fornece a implementação básica dos ganchos
handler_found_hook () e transformation_hook (). Portanto, podemos salvar apenas os atributos que precisamos dentro de custom_envelope_t e enviar delivery_receipt_t dentro do gancho handler_found_hook ().

Isso, de fato, é tudo. Se executarmos este exemplo, obtemos o seguinte:

 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 

Além disso, devo dizer que, na prática, um custom_envelope_t simples para gerar relatórios de entrega dificilmente é adequado. Mas se alguém estiver interessado neste tópico, ele poderá ser discutido nos comentários, e não aumentar o volume do artigo.

O que mais poderia ser feito com envelopes?


Ótima pergunta!Para o qual nós mesmos não temos uma resposta abrangente. Provavelmente, as possibilidades são limitadas apenas pela imaginação dos usuários. Bem, se para a realização de fantasias no SObjectizer algo está faltando, isso pode ser dito para nós. Nós sempre ouvimos. E, mais importante, às vezes até fazemos :)

Integração de agentes com mchain


Falando um pouco mais a sério, esse é outro recurso que eu gostaria de ter de tempos em tempos e que foi planejado para so_5_extra-1.2.0. Mas o que, provavelmente, não será lançado na versão 1.2.0.

Trata-se de simplificar a integração de mchains e agentes.

O fato é que inicialmente os mchains foram adicionados ao SObjectizer para simplificar a comunicação de agentes com outras partes do aplicativo que são gravadas sem agentes. Por exemplo, existe o encadeamento principal do aplicativo, no qual o usuário interage usando a GUI. E há vários agentes-trabalhadores que fazem o trabalho "duro" em segundo plano. Enviar uma mensagem a um agente a partir do encadeamento principal não é um problema: basta ligar para o envio regular. Mas como transferir informações de volta?

Para isso, mchain-s foram adicionados.

Mas com o tempo, descobriu-se que os mchains podem desempenhar um papel muito maior. É possível, em princípio, criar aplicativos multithread no SObjectizer sem nenhum agente, apenas em mchain-ahs (mais detalhes aqui ). E você pode usar mchain-s como um meio de equilibrar a carga nos agentes. Como um mecanismo para resolver problemas produtor-consumidor.

O problema com o produtor-consumidor é que, se o produtor gera mensagens mais rapidamente do que o consumidor pode lidar com elas, então estamos com problemas. As filas de mensagens aumentam, o desempenho pode diminuir com o tempo ou o aplicativo falha completamente devido ao esgotamento da memória.

A solução usual que propusemos usar neste caso é usarum par de agentes colecionadores-intérpretes . Você também pode usar limites de mensagens (como o principal mecanismo de proteção ou como complemento do coletor-executor). Mas escrever um colecionador-executor requer um trabalho adicional do programador.

Mas mchains pode ser usado para esses propósitos com o mínimo esforço do desenvolvedor. Portanto, o produtor colocaria a próxima mensagem no mchain e o consumidor receberia mensagens desse mchain.

Mas o problema é que, quando o consumidor é um agente, não é muito conveniente para um agente trabalhar com o mchain através das funções de recebimento () e seleção () disponíveis. E esse inconveniente pode ser tentado ser eliminado com a ajuda de alguma ferramenta para integrar agentes e mchain-s.

Ao desenvolver essa ferramenta, será necessário resolver vários problemas. Por exemplo, quando uma mensagem chega no mchain, em que ponto deve ser extraída do mchain? Se o consumidor é gratuito e não processa nada, você pode pegar a mensagem do mchain imediatamente e entregá-la ao agente do consumidor. Se uma mensagem já foi enviada ao consumidor do mchain, ele ainda não conseguiu processar essa mensagem, mas uma nova mensagem já chegou no mchain ... O que deve ser feito neste caso?

Há especulações de que envelopes podem ajudar nesse caso. Portanto, quando pegamos a primeira mensagem do mchain e a enviamos ao consumidor, envolvemos essa mensagem em um envelope especial. Quando o envelope vê que a mensagem foi entregue e processada, solicita a próxima mensagem do mchain (se houver).

Claro, nem tudo é tão simples aqui. Mas até agora parece bastante solucionável. E, espero, um mecanismo semelhante aparecerá em uma das próximas versões do so_5_extra.

Vamos abrir a caixa de Pandora?


Deve-se notar que, conosco, as próprias capacidades adicionadas causam sentimentos duplos.

Por um lado, os envelopes já permitiram / permitiram fazer coisas que foram mencionadas anteriormente (mas apenas sonhavam com algo). Por exemplo, este é um cancelamento garantido de cronômetros e uma restrição no tempo de entrega, relatórios de entrega, a capacidade de recuperar uma mensagem enviada anteriormente.

Por outro lado, não está claro o que isso levará posteriormente. Afinal, você pode criar um problema a partir de qualquer oportunidade se começar a usá-la quando e onde não precisar. Então talvez abrimos a caixa de Pandora e ainda não imaginemos o que nos espera?

Resta apenas ter paciência e ver aonde tudo isso nos levará.

Sobre os planos de desenvolvimento imediato do SObjectizer em vez de concluir


Em vez de uma conclusão, quero falar sobre como vemos o futuro muito próximo (e não apenas) do SObjectizer. Se alguém não estiver satisfeito com algo em nossos planos, você poderá falar e influenciar como o SObjectizer-5 se desenvolverá.

As primeiras versões beta do SObjectizer-5.5.23 e so_5_extra-1.2.0 já estão corrigidas e disponíveis para download e experiências. Ainda haverá muito trabalho a ser feito na área de documentação e casos de uso. Portanto, o lançamento oficial está previsto para a primeira década de novembro. Se funcionar mais cedo, faremos mais cedo.

O lançamento do SObjectizer-5.5.23 parece significar que a evolução do ramo 5.5 está chegando ao fim. O primeiro lançamento desse segmento ocorreu quatro anos atrás, em outubro de 2014.. Desde então, o SObjectizer-5 evoluiu dentro do ramo 5.5 sem grandes alterações de quebra entre as versões. Não foi fácil. Especialmente considerando o fato de que durante todo esse tempo tivemos que olhar para os compiladores que estavam longe do suporte ideal para C ++ 11.

Agora, não vemos motivo para rever a compatibilidade na ramificação 5.5 e, especialmente, nos compiladores C ++ mais antigos. O que poderia ser justificado em 2014, quando o C ++ 14 estava se preparando para ser adotado oficialmente e o C ++ 17 ainda não estava no horizonte, agora parece completamente diferente.

Além disso, no próprio SObjectizer-5.5, ele já acumulou uma quantidade razoável de rake e backups, que apareceram devido a essa mesma compatibilidade e complicam o desenvolvimento do SObjectizer.

Portanto, nos próximos meses, agiremos de acordo com o seguinte cenário:

1. Desenvolvimento da próxima versão do so_5_extra, na qual desejo adicionar ferramentas para simplificar a gravação de testes para agentes. Ainda não está claro se será_5_extra-1.3.0 (ou seja, com alterações recentes em relação à 1.2.0) ou se será_5_extra-1.2.1 (ou seja, sem alterações). Vamos ver como vai. É claro que a próxima versão do so_5_extra será baseada no SObjectizer-5.5.

1a. Se para a próxima versão do so_5_extra você precisar fazer algo adicional no SObjectizer-5.5, a próxima versão 5.5.24 será lançada. Se, para so_5_extra, não for necessário fazer melhorias no núcleo do SObjectizer, a versão 5.5.23 será a última versão significativa na estrutura da ramificação 5.5. Lançamentos menores de correção de bugs serão lançados. Mas o próprio desenvolvimento da ramificação 5.5 para na versão 5.5.23 ou 5.5.24.

2. Em seguida, será lançada uma versão do SObjectizer-5.6.0, que abrirá uma nova ramificação. Na ramificação 5.6, limparemos o código do SObjectizer de todas as muletas e backups acumulados, bem como do lixo antigo que há muito tempo é marcado como obsoleto. É provável que algumas coisas passem por refatoração (por exemplo, abstract_message_box_t pode ser alterado), mas dificilmente cardeal. Os princípios básicos do trabalho e os recursos característicos do SObjectizer-5.5 no SObjectizer-5.6 permanecerão na mesma forma.

O SObjectizer-5.6 já exigirá C ++ 14 (pelo menos no nível GCC-5.5). Não há suporte para compiladores do Visual C ++ abaixo do VC ++ 15 (que é do Visual Studio 2017).

Consideramos a ramificação 5.6 como uma ramificação estável do SObjectizer, que será relevante até a primeira versão do SObjectizer-5.7 aparecer.

Gostaria de lançar a versão 5.6.0 no início de 2019, provisoriamente em fevereiro.

3. Após estabilizar o ramo 5.6, gostaríamos de começar a trabalhar no ramo 5.7, no qual poderíamos revisar alguns princípios básicos do trabalho do SObjectizer. Por exemplo, abandone completamente os expedidores públicos, deixando apenas os particulares. Refaça o mecanismo das cooperativas e suas relações pai-filho, eliminando assim o gargalo durante o registro / cancelamento de registro de cooperativas. Remova a divisão por mensagem / sinal. Permita que apenas send / send_delayed / send_periodic envie mensagens e oculte os métodos deliver_message e schedule_timer "under the hood". Modifique o mecanismo de envio de mensagens para remover completamente dynamic_casts desse processo ou reduzi-las ao mínimo.

Em geral, há para onde se virar. Ao mesmo tempo, o SObjectizer-5.7 já exigirá C ++ 17, independentemente do C ++ 14.

Se você observar as coisas sem óculos cor de rosa, é bom que a versão 5.7.0 ocorra no final do outono de 2019. a principal versão de trabalho do SObjectizer para 2019 será a ramificação 5.6.

4. Paralelamente a tudo isso, o so_5_extra se desenvolverá. Provavelmente, a versão so_5_extra-2 será lançada junto com o SObjectizer-5.6, que durante 2019 incorporará novas funcionalidades, mas com base no SObjectizer-5.6.

Assim, nós mesmos vemos evolução progressiva para o SObjectizer-5 com uma revisão gradual de alguns dos princípios básicos do SObjectizer-5. Ao mesmo tempo, tentaremos fazer isso da maneira mais suave possível, para que seja possível alternar de uma versão para outra com o mínimo de esforço.

No entanto, se alguém quiser mudanças mais dramáticas e significativas do SObjectizer, temos algumas reflexões sobre isso . Em poucas palavras: você pode refazer o SObjectizer como quiser, até implementar o SObjectizer-6 para outra linguagem de programação. Mas não faremos isso completamente às nossas próprias custas, como isso acontece com a evolução do SObjectizer-5.

Provavelmente é tudo.Os comentários ao artigo anterior acabaram sendo uma discussão boa e construtiva. Seria útil se uma discussão semelhante acontecesse desta vez. Como sempre, estamos prontos para responder a quaisquer perguntas, mas às mais sensatas e com prazer.

E aos leitores mais pacientes que alcançaram essas linhas, muito obrigado pelo tempo gasto lendo o artigo.

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


All Articles