A primeira versão do SObjectizer na estrutura da ramificação 5.5 foi lançada há pouco mais de quatro anos - no início de outubro de 2014. E hoje
a próxima versão foi lançada sob o número 5.5.23 , que possivelmente fechará a história do SObjectizer-5.5. Na minha opinião, esse é um ótimo motivo para olhar para trás e ver o que foi feito nos últimos quatro anos.
Neste artigo, tentarei analisar de maneira abstrata as mudanças e inovações mais importantes e significativas: o que foi adicionado, por que, como isso afetou o próprio SObjectizer ou seu uso.
Talvez alguém se interesse por essa história do ponto de vista da arqueologia. E alguém, talvez, será dissuadido de uma aventura tão duvidosa como desenvolver sua própria estrutura de ator para C ++;)
Um pouco de digressão lírica sobre o papel dos antigos compiladores C ++
A história do SObjectizer-5 começou em meados de 2010. Ao mesmo tempo, focamos imediatamente no C ++ 0x. Já em 2011, as primeiras versões do SObjectizer-5 começaram a ser usadas para escrever código de produção. É claro que não tínhamos compiladores com suporte normal ao C ++ 11.
Por um longo tempo, não pudemos usar totalmente todos os recursos do "C ++ moderno": modelos variados, noexcept, constexpr, etc. Isso não pode deixar de afetar a API do SObjectizer. E isso afetou por muito, muito tempo. Portanto, se ao ler uma descrição de um recurso, você tiver uma pergunta "Por que isso nunca foi feito antes?", A resposta a esta pergunta é mais provável: "Porque isso não era possível antes".
O que apareceu e / ou mudou no SObjectizer-5.5 no passado?
Nesta seção, abordaremos vários recursos que tiveram um impacto significativo no SObjectizer. A ordem nesta lista é aleatória e não está relacionada ao "significado" ou "peso" dos recursos descritos.
Rejeitando o espaço para nome so_5 :: rt
O que houve?
Inicialmente, no quinto SObjectizer, tudo relacionado ao tempo de execução do SObjectizer era definido dentro do namespace so_5 :: rt. Por exemplo, tivemos so_5 :: rt :: environment_t, so_5 :: rt :: agent_t, so_5 :: rt :: message_t, etc. O que você pode ver, por exemplo, no exemplo tradicional do HelloWorld do 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; }
A abreviação "rt" significa tempo de execução. E pareceu-nos que o registro "so_5 :: rt" é muito melhor e mais prático do que "so_5 :: runtime".
Mas, para muitas pessoas, "rt" é apenas "tempo real" e nada mais. E o uso de "rt" como abreviação de "tempo de execução" viola seus sentimentos tanto que, às vezes, os anúncios das versões do SObjectizer no RuNet se transformavam em holivar sobre o assunto de [não] interpretação permissível de "rt", exceto "em tempo real".
No final, estamos cansados disso. E nós não destruímos o namespace so_5 :: rt.
O que se tornou?
Tudo o que foi definido dentro de "so_5 :: rt" simplesmente mudou para "so_5". Como resultado, o mesmo HelloWorld agora fica assim:
#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; }
Mas os nomes antigos de "so_5 :: rt" permaneceram disponíveis de qualquer maneira, através do uso usual de s (typedefs). Portanto, o código escrito para as primeiras versões do SO-5.5 também é viável nas versões recentes do SO-5.5.
Finalmente, o espaço para nome so_5 :: rt será removido na versão 5.6.
Que impacto isso teve?
Provavelmente, o código no SObjectizer agora está mais legível. Ainda assim, so_5 :: send () é melhor percebido que so_5 :: rt :: send ().
Bem, aqui, como nos desenvolvedores do SObjectizer, a dor de cabeça diminuiu. Havia muitas conversas vazias e raciocínios desnecessários nos anúncios do SObjectizer de uma só vez (começando pelas perguntas “Por que os atores são necessários em C ++ em geral” e terminando com “Por que você não usa o PascalCase para nomear entidades”). Um tópico inflamável tornou-se menor e foi bom :)
Simplifique o envio de mensagens e a evolução dos manipuladores de mensagens
O que houve?
Mesmo nas primeiras versões do SObjectizer-5.5, a mensagem usual era enviada usando o método deliver_message, que precisava ser chamado na mbox do destinatário. Para enviar uma mensagem pendente ou periódica, era necessário chamar single_timer / schedule_timer em um objeto do tipo environment_t. E já enviar uma solicitação síncrona para outro agente geralmente exigia toda uma cadeia de operações. Aqui, por exemplo, como tudo poderia parecer há quatro anos (std :: make_unique (), que ainda não estava disponível no C ++ 11, já é usado):
Além disso, o formato dos manipuladores de mensagens no SObjectizer evoluiu para a versão 5.5. Se inicialmente no SObjectizer-5, todos os manipuladores devem ter o formato:
void evt_handler(const so_5::event_data_t<Msg> & cmd);
com o tempo, mais alguns foram adicionados aos formatos permitidos:
Novos formatos de manipulador tornaram-se amplamente utilizados desde pintar constantemente “const so_5 :: event_data_t <Msg> &” ainda é um prazer. Mas, por outro lado, formatos mais simples não eram amigáveis para os agentes de modelo. Por exemplo:
template<typename Msg_To_Process> class my_actor : public so_5::agent_t { void on_receive(const Msg_To_Process & msg) {
Esse agente de modelo funcionará apenas se Msg_To_Process for um tipo de mensagem, não um tipo de sinal.
O que se tornou?
Na ramificação 5.5, uma família de funções de envio apareceu e evoluiu significativamente. Para fazer isso, em primeiro lugar, tive que disponibilizar compiladores à disposição com suporte para modelos variados. E, em segundo lugar, acumular experiência suficiente trabalhando com modelos variados em geral e com as primeiras versões das funções de envio. Além disso, em diferentes contextos: em agentes comuns e em agentes ad-hoc e em agentes implementados por classes de modelo e por agentes externos em geral. Inclusive ao usar funções de envio com mchains (elas serão discutidas abaixo).
Além das funções de envio, apareceram as funções request_future / request_value, projetadas para interação síncrona entre agentes.
Como resultado, agora o envio de mensagens é o seguinte:
Outro formato possível para manipuladores de mensagens foi adicionado. Além disso, é esse formato que será deixado nas próximas versões principais do SObjectizer como o principal (e, possivelmente, o único). Este é o seguinte formato:
ret_type evt_handler(so_5::mhood_t<Msg> cmd);
Onde Msg pode ser um tipo de mensagem ou um tipo de sinal.
Esse formato não apenas desfoca a linha entre agentes na forma de classes comuns e agentes na forma de classes de modelo. Mas também simplifica o encaminhamento da mensagem / sinal (graças à família de funções de envio):
void my_agent::on_msg(mhood_t<Some_Msg> cmd) { ...
Que impacto isso teve?
A aparência das funções de envio e manipuladores de mensagens que recebem mhood_t <Msg>, podemos dizer, mudou fundamentalmente o código no qual as mensagens são enviadas e processadas. Este é apenas o caso quando resta apenas lamentar que, no início do trabalho no SObjectizer-5, não tínhamos compiladores com suporte para modelos variados, nem experiência em usá-los. A família de funções de envio e mhood_t deveria ter sido desde o início. Mas a história se desenvolveu como se desenvolveu ...
Suporte para tipos de mensagem personalizados
O que houve?
Inicialmente, todas as mensagens enviadas deveriam ser classes descendentes da classe so_5 :: message_t. Por exemplo:
struct my_message : public so_5::message_t { ...
Embora o quinto SObjectizer tenha sido usado apenas por nós mesmos, isso não levantou nenhuma dúvida. Bem, assim e assim.
Porém, assim que usuários de terceiros começaram a se interessar pelo SObjectizer, deparamos imediatamente com uma pergunta repetida regularmente: "Devo herdar uma mensagem de so_5 :: message_t?" Esse problema foi especialmente relevante em situações em que era necessário enviar objetos de tipos como mensagens que o usuário não podia influenciar. Digamos que um usuário use um SObjectizer e alguma outra biblioteca externa. E nesta biblioteca externa, existe um certo tipo M, cujos objetos o usuário gostaria de enviar como mensagens. Bem e como, em tais condições, fazer amigos digitar M e so_5 :: message_t? Somente invólucros adicionais que o usuário precisou escrever manualmente.
O que se tornou?
Adicionamos a capacidade de enviar mensagens para o SObjectizer-5.5, mesmo que o tipo de mensagem não seja herdado de so_5 :: message_t. I.e. Agora o usuário pode escrever facilmente:
so_5::send<std::string>(mbox, "Hello, World!");
So_5 :: message_t permanece sob o capô de qualquer maneira, apenas devido ao modelo que a magic send () entende que std :: string não é herdada de so_5 :: message_t e não uma simples std :: string é construída dentro de send, mas um herdeiro especial de so_5 :: message_t, dentro do qual a std :: string desejada pelo usuário já está localizada.
Magia de modelo semelhante se aplica a assinaturas. Quando o SObjectizer vê um manipulador de mensagens no formulário:
void evt_handler(mhood_t<std::string> cmd) {...}
o SObjectizer entende que, de fato, uma mensagem especial virá com o objeto std :: string dentro. E o que você precisa chamar de manipulador passando para ele um link para std :: string dessa mensagem especial.
Que impacto isso teve?
O uso do SObjectizer ficou mais fácil, especialmente quando você precisa enviar não apenas objetos de seus próprios tipos como mensagens, mas também digitar objetos de bibliotecas externas. Várias pessoas chegaram a dizer um agradecimento especial por esse recurso.
Mensagens mutáveis
O que houve?
Inicialmente, no SObjectizer-5, apenas o modelo de interação 1: N foi utilizado. I.e. uma mensagem enviada pode ter mais de um destinatário (ou pode haver mais de um). Mesmo se os agentes precisassem interagir no modo 1: 1, eles ainda se comunicariam por meio de uma caixa de correio com vários produtores / vários consumidores. I.e. no modo 1: N, apenas N nesse caso era estritamente uma unidade.
Nas condições em que uma mensagem pode ser recebida por mais de um agente destinatário, as mensagens enviadas devem ser imutáveis. É por isso que os manipuladores de mensagens tinham os seguintes formatos:
Em geral, uma abordagem simples e compreensível. No entanto, não é muito conveniente quando os agentes precisam se comunicar no modo 1: 1 e, por exemplo, transferir a propriedade de alguns dados entre si. Digamos que uma mensagem tão simples não possa ser feita se todas as mensagens forem objetos estritamente imutáveis:
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)) {} };
Mais precisamente, essa mensagem poderia ser enviada. Mas, tendo recebido como um objeto constante, não seria possível remover o conteúdo de process_image :: image_ para si mesmo. Eu teria que marcar esse atributo como mutável. Mas perderíamos o controle do compilador se process_image for, por algum motivo, enviado no modo 1: N.
O que se tornou?
No SObjectizer-5.5, a capacidade de enviar e receber mensagens mutáveis foi adicionada. Ao mesmo tempo, o usuário deve marcar especialmente a mensagem ao enviar e ao se inscrever nela.
Por exemplo:
No SObjectizer, my_message e mutable_msg <my_message> são dois tipos diferentes de mensagens.
Quando uma função de envio vê que está sendo solicitado a enviar uma mensagem mutável, a função de envio verifica para qual caixa de correio está tentando enviar a mensagem. Se for uma caixa de vários consumidores, o envio não será realizado, mas uma exceção será lançada com o código de erro correspondente. I.e. O SObjectizer garante que as mensagens mutáveis somente possam ser usadas ao interagir no modo 1: 1 (via caixas de correio de consumidor único ou mchains, que são uma forma de caixas de correio de consumidor único). A fim de fornecer essa garantia, o SObjectizer proíbe o envio de mensagens mutáveis na forma de mensagens periódicas.
Que impacto isso teve?
Com mensagens mutáveis, saiu inesperadamente. Nós os adicionamos ao SObjectizer como resultado de uma discussão à margem de um
relatório sobre o SObjectizer no C ++ Russia-2017 . Com o sentimento: "Bem, se eles perguntarem, alguém precisa, então vale a pena tentar". Bem, eles fizeram sem muita esperança de demanda generalizada. Apesar disso, tive que "fumar bambu" por um longo tempo antes de pensar em como adicionar mensagens mutáveis ao SO-5.5 sem quebrar a compatibilidade.
Mas quando mensagens mutáveis apareceram no SObjectizer, descobriu-se que não havia tão poucos aplicativos para elas. E que mensagens mutáveis são usadas surpreendentemente com frequência (menção a isso pode ser encontrada
na segunda parte da história sobre o projeto de demonstração do Shrimp ). Portanto, na prática, esse recurso foi mais do que útil, porque permite resolver problemas que, sem o suporte de mensagens mutáveis no nível do SObjectizer, eles não tinham uma solução normal.
Agentes de máquinas de estado hierárquico
O que houve?
Os agentes no SObjectizer eram originalmente máquinas de estado. Os agentes precisavam descrever explicitamente estados e assinar mensagens em estados específicos.
Por exemplo:
class worker : public so_5::agent_t { state_t st_free{this, "free"}; state_t st_bufy{this, "busy"}; ... void so_define_agent() override {
Mas estas eram simples máquinas de estado. Os estados não podiam ser aninhados um no outro. Não havia suporte para manipuladores de entrada e saída de estado. Não houve restrições quanto ao tempo gasto no estado.
Mesmo esse suporte limitado para máquinas de estado era conveniente e o usamos por mais de um ano. Mas a certa altura, queríamos mais.
O que se tornou?
O SObjectizer apresenta suporte para máquinas de estado hierárquico.
Agora, os estados podem ser aninhados um no outro. Os manipuladores de eventos dos estados pai são automaticamente "herdados" pelos estados filhos.
Manipuladores para entrar e sair de um estado são suportados.
É possível definir um limite para o tempo em que o agente permanece no estado.
É possível manter uma história para o estado.
Para não ser infundado, eis um exemplo de um agente que não é uma máquina de estado hierárquica complexa (o código do exemplo padrão está piscando_led):
class blinking_led final : public so_5::agent_t { state_t off{ this }, blinking{ this }, blink_on{ initial_substate_of{ blinking } }, blink_off{ substate_of{ blinking } }; public : struct turn_on_off final : public so_5::signal_t {}; blinking_led( context_t ctx ) : so_5::agent_t{ ctx } { this >>= off; off.just_switch_to< turn_on_off >( blinking ); blinking.just_switch_to< turn_on_off >( off ); blink_on .on_enter( []{ std::cout << "ON" << std::endl; } ) .on_exit( []{ std::cout << "off" << std::endl; } ) .time_limit( std::chrono::milliseconds{1250}, blink_off ); blink_off .time_limit( std::chrono::milliseconds{750}, blink_on ); } };
Já descrevemos tudo isso
em um artigo separado , não há necessidade de repeti-lo.
Atualmente, não há suporte para estados ortogonais. Mas esse fato tem duas explicações. Em primeiro lugar, tentamos dar esse apoio e enfrentamos várias dificuldades, superando-as muito caras. E, em segundo lugar, ninguém pediu estados ortogonais ainda. Quando solicitado, volte para este tópico.
Que impacto isso teve?
Há um sentimento de que é muito sério (embora estejamos aqui, é claro, subjetivo e tendencioso). Afinal, uma coisa é quando, quando confrontado com máquinas complexas de estados finitos na área de assunto, você começa a procurar soluções alternativas, simplifica alguma coisa, gasta força extra em alguma coisa. E é uma questão completamente diferente quando você pode mapear objetos do seu aplicativo para o seu código C ++ quase 1 em 1.
Além disso, a julgar pelas perguntas feitas, por exemplo, pelo comportamento dos manipuladores de entrada / saída dentro / fora do estado, essa funcionalidade é usada.
mchain's
O que houve?
Foi uma situação interessante. O SObjectizer costumava ser usado para que apenas parte do aplicativo fosse escrita no SObjectizer. O restante do código no aplicativo pode não ter nada a ver com atores em geral ou com o SObjectizer em particular. Por exemplo, um aplicativo GUI no qual um SObjectizer é usado para algumas tarefas em segundo plano, enquanto o trabalho principal é executado no encadeamento principal do aplicativo.
E nesses casos, descobriu-se que da parte não SObjectizer para a parte SObjectizer, o envio de informações é tão simples quanto simples: basta chamar funções de envio comuns. Mas a disseminação de informações na direção oposta não é tão simples. Pareceu-nos que isso não é bom e que você deve ter alguns canais de comunicação convenientes entre as partes do SObjectizer do aplicativo e as partes que não são do SObjectizer diretamente fora da caixa.
O que se tornou?
Portanto, cadeias de mensagens ou, na notação mais familiar, mchains apareceram no SObjectizer.
Mchain é uma variante tão específica de uma caixa de correio de consumidor único onde as mensagens são enviadas por funções de envio regulares. Mas, para extrair mensagens do mchain, você não precisa criar agentes e assiná-los. Existem duas funções especiais que podem ser chamadas mesmo dentro de agentes, mesmo fora de agentes: receive () e select (). O primeiro lê mensagens de apenas um canal, enquanto o segundo pode ler mensagens de vários canais ao mesmo tempo:
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>){...}, ... ));
Já falamos sobre mchain várias vezes aqui:
em agosto de 2017 e
maio de 2018 . Portanto, especialmente no tópico de como funciona o mchains, não vamos aprofundar aqui.
Que impacto isso teve?
Após o aparecimento de mchains no SObjectizer-5.5, descobriu-se que o SObjectizer tornou-se ainda menos uma estrutura de "ator" do que era antes. Além de oferecer suporte ao modelo de ator e Pub / Sub, o SObjectizer também adicionou suporte ao modelo CSP (comunicando processos sequenciais). Os Mchains permitem que você desenvolva aplicativos multithread bastante complexos no SObjectizer sem nenhum ator. E para algumas tarefas isso é mais do que conveniente. O que nós mesmos usamos de tempos em tempos.
Mecanismo de limites de mensagens
O que houve?
Uma das deficiências mais graves do modelo de atores é a predisposição para a ocorrência de sobrecargas. É muito fácil encontrar-se em uma situação em que o agente remetente envia mensagens ao agente receptor em um ritmo mais rápido do que o agente receptor pode processar as mensagens.
Como regra, o envio de mensagens em estruturas de ator é uma operação sem bloqueio. Portanto, quando ocorre um par de “produtor ágil e consumidor nerd”, a fila do ator destinatário aumentará enquanto houver pelo menos algum tipo de memória livre.
A principal dificuldade desse problema é que um bom mecanismo de proteção contra sobrecarga deve ser aprimorado para a tarefa aplicada e as características da área de assunto. Por exemplo, para entender quais mensagens podem ser duplicadas (e, portanto, poder descartar com segurança as duplicatas). Para entender quais mensagens não podem ser jogadas fora de qualquer maneira. Quem pode ser suspenso e por quanto e quem não é permitido. Etc., etc.
Outra dificuldade é que nem sempre é necessário ter um bom mecanismo de defesa. Às vezes, basta ter algo primitivo, mas eficaz, acessível "pronto para uso" e fácil de usar.
Para não forçar o usuário a controlar sua sobrecarga, basta simplesmente enviar mensagens “extras” ou encaminhá-las para algum outro agente.O que se tornou?
Apenas para que, em cenários simples, você possa usar ferramentas de proteção contra sobrecarga prontas, as chamadas limites de mensagem. Esse mecanismo permite descartar mensagens desnecessárias, enviá-las para outros destinatários ou simplesmente interromper o aplicativo se os limites forem excedidos. Por exemplo:
class worker : public so_5::agent_t { public: worker(context_t ctx) : so_5::agent_t{ ctx
Este tópico é descrito em mais detalhes em um artigo separado .Que impacto isso teve?
Isso não quer dizer que o surgimento de limites de mensagens tenha se tornado algo que mudou fundamentalmente o SObjectizer, os princípios de seu trabalho ou de seu trabalho. Em vez disso, pode ser comparado com um pára-quedas de reserva, usado apenas como último recurso. Mas quando você precisa usá-lo, fica feliz que ele exista.Mecanismo de rastreamento de entrega de mensagens
O que houve?
SObjectizer-5 era uma caixa preta para desenvolvedores. Em que a mensagem é enviada e ... E ela chega ao destinatário ou não vem.Se a mensagem não chegar ao destinatário, o usuário se depara com a necessidade de realizar uma emocionante busca em busca de um motivo. Na maioria dos casos, os motivos são triviais: a mensagem foi enviada para a mbox errada ou a assinatura não foi feita (por exemplo, o usuário fez uma assinatura em um estado do agente, mas esqueceu de fazê-lo em outro). Mas pode haver casos mais complexos quando uma mensagem é, por exemplo, rejeitada por um mecanismo de proteção contra sobrecarga.O problema era que o mecanismo de entrega de mensagens estava oculto nos grupos do SObjectizer Run-Time e, portanto, era difícil mesmo para os desenvolvedores do SObjectizer rotearem a mensagem para o destinatário, sem mencionar os usuários. Especialmente sobre usuários iniciantes que cometeram o maior número de erros triviais.O que se tornou?
No SObjectizer-5.5, um mecanismo especial para rastrear o processo de entrega de mensagens chamado rastreamento de entrega de mensagens (ou simplesmente msg_tracing) foi adicionado e finalizado. Esse mecanismo e seus recursos foram descritos em mais detalhes em um artigo separado .Portanto, agora, se as mensagens forem perdidas na entrega, você pode simplesmente ativar o msg_tracing e ver por que isso acontece.Que impacto isso teve?
A depuração de aplicativos escritos no SObjectizer tornou-se muito mais simples e agradável. Mesmo para nós mesmos .O conceito de env_infrastructure e env_infrastructure do single-threaded
O que houve?
Sempre consideramos o SObjectizer como uma ferramenta para simplificar o desenvolvimento de código multiencadeado. Portanto, as primeiras versões do SObjectizer-5 foram gravadas para funcionar apenas em um ambiente multithread.Isso foi expresso como o uso de primitivas de sincronização dentro do SObjectizer para proteger as partes internas do SObjectizer ao trabalhar em um ambiente multithread. O mesmo ocorre na criação de vários threads de trabalho auxiliares dentro do próprio SObjectizer (para executar operações importantes como atender o timer e concluir o cancelamento do registro de cooperações de agentes).I.e.
O SObjectizer foi criado para programação multithread e para uso em ambientes multithread. E isso nos convinha perfeitamente.No entanto, como o SObjectizer foi usado “em estado selvagem”, foram descobertas situações em que a tarefa era difícil o suficiente para usar atores em sua solução. Mas, ao mesmo tempo, todo o trabalho podia e, além disso, tinha que ser executado em um único fluxo de trabalho.E enfrentamos um problema muito interessante: é possível ensinar o SObjectizer a trabalhar em um único segmento de trabalho?O que se tornou?
Descobriu-se que é possível.Custou-nos muito dinheiro, demorou muito tempo e esforço para encontrar uma solução. Mas a solução foi inventada.Um conceito como infraestrutura do ambiente foi introduzido (ou env_infrastructure de uma forma ligeiramente abreviada). Env_infrastructure assumiu a tarefa de gerenciar a cozinha interna do SObjectizer. Em particular, ele resolveu questões como manutenção de cronômetros, registro e cancelamento de registro de cooperativas.Para o SObjectizer, várias opções env_infrastructures de thread único foram feitas. Isso nos permitiu desenvolver aplicativos single-threaded no SObjectizer, dentro dos quais existem agentes normais trocando mensagens regulares entre si.Falamos sobre essa funcionalidade em mais detalhes em um artigo separado. .Que impacto isso teve?
Talvez a coisa mais importante que aconteceu durante a implementação desse recurso tenha sido a quebra de nossos próprios modelos. Uma olhada no SObjectizer nunca mais será a mesma. Por muitos anos, considere o SObjectizer exclusivamente como uma ferramenta para o desenvolvimento de código multiencadeado. E então novamente! E descubra que o código de thread único no SObjectizer também pode ser desenvolvido. A vida é cheia de surpresas.Ferramentas de monitoramento em tempo de execução
O que houve?
O SObjectizer-5 era uma caixa preta, não apenas em termos de mecanismo de entrega de mensagens. Mas também não havia como descobrir quantos agentes estão trabalhando atualmente no aplicativo, quantos e quais expedidores são criados, quantos encadeamentos de trabalho envolvidos, quantas mensagens estão aguardando nas filas dos expedidores etc.Toda essa informação é muito útil para monitorar aplicativos em execução 24/7. Mas para depuração, eu também gostaria de entender de tempos em tempos se as filas estão crescendo ou se o número de agentes está aumentando / diminuindo.Infelizmente, por enquanto, nossas mãos simplesmente não chegaram ao ponto de adicionar fundos ao SObjectizer para coletar e disseminar essas informações.O que se tornou?
Em um ponto do SObjectizer-5.5, surgiram ferramentas para o monitoramento em tempo de execução dos componentes internos do SObjectizer . Por padrão, o monitoramento em tempo de execução está desabilitado, mas se você o habilitar, as mensagens serão enviadas regularmente para a mbox especial, dentro da qual haverá informações sobre o número de agentes e cooperações, sobre o número de temporizadores, sobre threads de trabalho pertencentes a expedidores (e já haverá informações sobre o número de mensagens nas filas, o número de agentes vinculados a esses encadeamentos).Além disso, com o tempo, tornou-se possível habilitar adicionalmente a coleta de informações sobre quanto tempo os agentes gastam em manipuladores de eventos. Isso permite detectar situações em que algum agente está muito lento (ou está perdendo tempo bloqueando chamadas).Que impacto isso teve?
Em nossa prática, o monitoramento em tempo de execução não é frequentemente usado. Mas quando você precisar, você perceberá sua importância. De fato, sem esse mecanismo, é impossível (bem ou muito difícil) descobrir o que e como [não] funciona.Portanto, esse é um recurso da categoria "você pode fazê-lo", mas sua presença, em nossa opinião, transfere imediatamente o instrumento para outra categoria de peso. Porque
fazer um protótipo de uma estrutura de ator "no joelho" não é tão difícil. Muitos fizeram isso e muitos outros o farão. Mas, então, para equipar seu desenvolvimento com o monitoramento em tempo de execução ... Até agora, nem todos os rascunhos de joelhos sobrevivem.E mais uma coisa em uma linha
Durante quatro anos, o SObjectizer-5.5 recebeu muitas inovações e mudanças, cuja descrição, mesmo em sinopse, vai ocupar muito espaço. Portanto, denotamos parte deles literalmente em uma linha. Em ordem aleatória, sem prioridades.O SObjectizer-5.5 adiciona suporte ao sistema de criação do CMake.Agora o SObjectizer-5 pode ser construído como uma biblioteca dinâmica e estática.Agora, o SObjectizer-5.5 foi desenvolvido e executado no Android (através do CrystaX NDK e através do Android NDK).Despachantes particulares apareceram. Agora você pode criar e usar despachantes que ninguém mais vê.Implementou o mecanismo de entrega de filtros. Agora, ao assinar mensagens de MPMC-mboxes, você pode proibir a entrega de mensagens cujo conteúdo não lhe interessa.As ferramentas para criar e registrar cooperações foram significativamente simplificadas: os métodos Introduzir_Coop / Introduzir_child_coop, Make_agent / Make_agent_with_binder e isso é tudo.O conceito de uma fábrica de objetos de bloqueio apareceu e agora você pode escolher quais objetos de bloqueio precisam (com base em mutex, spinlock, combinado ou outro).A classe wrap_env_t apareceu e agora você pode executar o SObjectizer no seu aplicativo, não apenas com so_5 :: launch ().O conceito de stop_guards apareceu e agora você pode influenciar o processo de desligamento do SObjectizer. Por exemplo, você pode impedir que o SObjectizer pare até que alguns agentes concluam seu trabalho de aplicativo.Agora você pode interceptar as mensagens que foram entregues ao agente, mas não foram processadas pelo agente (os chamados dead_letter_handlers).Houve uma oportunidade de embrulhar mensagens em "envelopes" especiais. Os envelopes podem transportar informações adicionais sobre a mensagem e podem executar alguma ação quando a mensagem é entregue ao destinatário.5.5.0 a 5.5.23 em números
Também é interessante observar o caminho feito em termos de código / testes / exemplos. Aqui está o que o utilitário cloc nos diz sobre a quantidade de código do kernel do SObjectizer-5.5.0: -------------------------------------------------- -----------------------------
Arquivos de idioma em branco código de comentário
-------------------------------------------------- -----------------------------
Cabeçalho C / C ++ 58 2119 5156 5762
C ++ 39 1167 779 4759
Rubi 2 30 2 75
-------------------------------------------------- -----------------------------
SUM: 99 3316 5937 10596
-------------------------------------------------- -----------------------------
E aqui está a mesma coisa, mas para a v.5.5.23 (das quais 1147 linhas são o código da biblioteca opcional-lite): -------------------------------------------------- -----------------------------
Arquivos de idioma em branco código de comentário
-------------------------------------------------- -----------------------------
Cabeçalho C / C ++ 133 6279 22173 21068
C ++ 53 2498 2760 10398
CMake 2 29 0 177
Ruby 4 53 2 129
-------------------------------------------------- -----------------------------
SUM: 192 8859 24935 31772
-------------------------------------------------- -----------------------------
Volume de testes para v.5.5.0: -------------------------------------------------- -----------------------------
Arquivos de idioma em branco código de comentário
-------------------------------------------------- -----------------------------
C ++ 84 2510 390 11540
Rubi 162 496 0 1054
Cabeçalho C / C ++ 1 11 0 32
-------------------------------------------------- -----------------------------
SUM: 247 3017 390 12626
-------------------------------------------------- -----------------------------
Testes para v.5.5.23: -------------------------------------------------- -----------------------------
Arquivos de idioma em branco código de comentário
-------------------------------------------------- -----------------------------
C ++ 324 7345 1305 35231
Ruby 675 2.353 0 4.671
CMake 338 43 0 955
Cabeçalho C / C ++ 11 107 3 448
-------------------------------------------------- -----------------------------
SUM: 1348 9848 1308 41305
Bem, exemplos para v.5.5.0: -------------------------------------------------- -----------------------------
Arquivos de idioma em branco código de comentário
-------------------------------------------------- -----------------------------
C ++ 27 765 463 3322
Rubi 28 95 0 192
-------------------------------------------------- -----------------------------
SUM: 55 860 463 3514
Eles são, mas já para a v.5.5.23: -------------------------------------------------- -----------------------------
Arquivos de idioma em branco código de comentário
-------------------------------------------------- -----------------------------
C ++ 67 2141 2061 9341
Rubi 133 451 0 868
CMake 67 93 0 595
Cabeçalho C / C ++ 1 12 11 32
-------------------------------------------------- -----------------------------
SUM: 268 2697 2072 10836
Quase todos os lugares, um aumento de quase três vezes.E a quantidade de documentação para o SObjectizer provavelmente aumentou ainda mais.Planos para o futuro próximo (e não apenas)
Os planos preliminares de desenvolvimento do SObjectizer após o lançamento da versão 5.5.23 foram descritos aqui há cerca de um mês. Fundamentalmente, eles não mudaram. Mas havia a sensação de que a versão 5.6.0, cujo lançamento está agendado para o início de 2019, precisará ser posicionado como o início da próxima ramificação estável do SObjectizer. De olho no fato de que, durante 2019, o SObjectizer se desenvolverá no ramo 5.6, sem alterações significativas.Isso proporcionará uma oportunidade para aqueles que estão atualmente usando o SO-5.5 em seus projetos para mudarem gradualmente para o SO-5.6 sem medo de que terão que mudar para o SO-5.7 a seguir.A versão 5.7, na qual queremos nos afastar dos princípios básicos do SO-5.5 e SO-5.6, será considerada experimental em 2019. Com estabilização e liberação, se tudo correr bem, já no ano 2020.Conclusão
Concluindo, gostaria de agradecer a todos que nos ajudaram com o desenvolvimento do SObjectizer esse tempo todo. E gostaria de agradecer separadamente a todos aqueles que ousaram tentar trabalhar com o SObjectizer. Seus comentários sempre foram muito úteis para nós.Queremos dizer para aqueles que ainda não usaram o SObjectizer: experimente. Isso não é tão assustador quanto pode parecer.Se você não gostou de algo ou não teve o suficiente no SObjectizer, informe-nos. Sempre ouvimos críticas construtivas. E, se estiver ao nosso alcance, damos vida aos desejos dos usuários.