É fácil adicionar novos recursos à estrutura antiga? Farinha de escolha no exemplo do desenvolvimento do SObjectizer



O desenvolvimento de uma estrutura livre para as necessidades dos desenvolvedores é um tópico específico. Se, ao mesmo tempo, a estrutura viver e se desenvolver por um período bastante longo, as especificidades serão adicionadas. Hoje vou tentar mostrar isso usando um exemplo de uma tentativa de expandir a funcionalidade de uma estrutura de "ator" para C ++ chamada SObjectizer .

O fato é que esse quadro já é bastante antigo, mudou drasticamente várias vezes. Até sua encarnação atual, SObjectizer-5, passou por muitas mudanças, tanto graves quanto não. Além disso, somos bastante sensíveis à compatibilidade e a introdução de alterações que quebram a compatibilidade é um passo muito sério para que possamos decidir sobre isso.

No momento, precisamos decidir como adicionar um novo recurso à próxima versão. No processo de encontrar uma solução adequada, duas opções surgiram. Ambos parecem bastante realizáveis. Mas eles são muito diferentes um do outro. Tanto em termos de complexidade e complexidade de implementação, quanto em sua "aparência". I.e. o que o desenvolvedor irá lidar será diferente em cada uma das opções. Provavelmente até fundamentalmente diferente.

E agora, como desenvolvedores da estrutura, temos que fazer uma escolha em favor de uma ou outra solução. Ou é preciso admitir que nenhum deles é satisfatório e, portanto, algo mais precisa ser inventado. Tais decisões durante a história do SObjectizer tiveram que ser tomadas mais de uma vez. Se alguém estiver interessado em se sentir no lugar do desenvolvedor de tal estrutura, você será bem-vindo ao gato.

Problema original


Então, brevemente, a essência do problema original. Desde o início de sua existência, o SObjectizer tinha o seguinte recurso: uma mensagem de timer não é tão fácil de cancelar. Sob o temporizador será entendido, em primeiro lugar, uma mensagem atrasada. I.e. uma mensagem que não deve ser enviada imediatamente ao destinatário, mas depois de algum tempo. Por exemplo, enviamos_delayed com uma pausa de 1s. Isso significa que, na realidade, a mensagem será enviada pelo timer 1s após a chamada send_delayed.

Uma mensagem pendente pode, em princípio, ser cancelada. Se a mensagem ainda estiver na posse do cronômetro, a mensagem após o cancelamento não será levada a lugar algum. Será lançado pelo cronômetro e é isso. Mas se o cronômetro já enviou uma mensagem e agora está na fila de solicitações do agente receptor, o cancelamento do cronômetro não funcionará. Não há mecanismo no SObjectizer para remover uma mensagem da fila do aplicativo.

O problema é composto por pelo menos dois fatores.

Em primeiro lugar, o SObjectizer suporta a entrega no modo 1: N, ou seja, se a mensagem foi enviada para a mbox Multi-Consumidor, a mensagem não estará em uma fila, mas em várias filas para N destinatários ao mesmo tempo.

Em segundo lugar, o SObjectizer usa um mecanismo de expedidor e os expedidores podem ser muito diferentes, incluindo aqueles escritos pelo usuário para suas necessidades específicas. As filas de pedidos são gerenciadas pelos despachantes. E na interface do expedidor não há funcionalidade para retirar um aplicativo que já foi transferido para o expedidor. Mas, mesmo que essa funcionalidade tenha sido incorporada à interface, está longe de ser possível implementá-la efetivamente em todos os casos. Sem mencionar o fato de que essa funcionalidade aumentaria a complexidade do desenvolvimento de novos despachantes.

Em geral, objetivamente, se o cronômetro já enviou uma mensagem pendente ao (s) destinatário (s), forçar o SObjectizer a não entregar essa instância da mensagem é atualmente impossível.
De fato, esse problema também é relevante para mensagens periódicas (ou seja, mensagens que o timer deve enviar periodicamente em intervalos de tempo predeterminados). Mas, na prática, cancelar mensagens periódicas é muito menos necessário do que cancelar uma mensagem pendente. Pelo menos em nossa prática é assim.

O que pode ser feito agora?


Portanto, esse problema não é novo e, por um longo tempo, há recomendações sobre como lidar com ele.

ID exclusivo dentro da mensagem pendente


A maneira mais fácil é manter um balcão. O agente possui um contador; ao enviar uma mensagem pendente, o valor atual do contador é enviado na mensagem. Quando uma mensagem é cancelada, o contador no agente é incrementado. Após o recebimento da mensagem, o valor atual do contador no agente é comparado com o valor da mensagem. Se os valores não corresponderem, a mensagem será rejeitada:

class demo_agent : public so_5::agent_t { struct delayed_msg final { int id_; ... }; int expected_msg_id_{}; so_5::timer_id_t timer_; void on_some_event() { //   . //   send_periodic, ..    //  timer_id   . timer_ = so_5::send_periodic<delayed_msg>(*this, 25s, //     . 0s, //    . //      delayed_msg, //      id   . ++expected_msg_id_, ... //  . ); ... } void on_cancel_event() { //   ,        //   .   : timer_.reset(); //     . ++expected_msg_id_; //   id-. ... } void on_delayed_msg(mhood_t<delayed_msg> cmd) { //     id    //  . if(expected_msg_id_ == cmd->id_) { ... //  . } } }; 

O problema com esse método é que o desenvolvedor do agente precisa ficar intrigado mantendo esses contadores. E se, como uma mensagem atrasada, precisamos enviar a mensagem de outra pessoa que outra pessoa enviou e na qual não há campo id_, então nos encontramos em uma situação difícil.

Embora, por outro lado, essa seja a maneira mais eficaz que existe atualmente.

Use mbox exclusivo para mensagens atrasadas


Outra maneira que funciona bem é usar uma caixa de correio exclusiva (mbox) para uma mensagem atrasada. Nesse caso, criamos uma nova mbox para cada mensagem pendente, assinamos e enviamos a mensagem pendente para essa mbox. Quando uma mensagem precisa ser cancelada, simplesmente excluímos as assinaturas da mbox.

 class demo_agent : public so_5::agent_t { struct delayed_msg final { ... //   id_   . }; so_5::mbox_t timer_mbox_; //   . so_5::timer_id_t timer_; void on_some_event() { //        mbox //     . timer_mbox_ = so_environment().create_mbox(); some_state.event(time_mbox_, ...); another_state.event(time_mbox_, ...); ... //    . timer_ = so_5::send_delayed<delayed_msg>( so_environment(), timer_mbox_, //     . 25s, 0s, ... //    delayed_msg. ); } void on_cancel_event() { //        mbox. timer_.reset(); so_drop_subscription_for_all_states(timer_mbox_); } void on_delayed_msg(mhood_t<delayed_msg> cmd) { //     ,   //    . ... } }; 

Esse método já pode funcionar com mensagens de outras pessoas, nas quais não há identificador exclusivo. Mas também requer trabalho e atenção do desenvolvedor.

Por exemplo, na modalidade acima, não há proteção contra o fato de que uma mensagem pendente já foi enviada anteriormente. De uma maneira boa, antes de enviar uma nova mensagem pendente, você sempre deve executar ações de on_cancel_event (), caso contrário, o agente terá assinaturas desnecessárias.

Por que esse problema não foi resolvido antes?


Tudo é bem simples aqui: na verdade, esse não é um problema tão sério quanto possa parecer. Pelo menos na vida real, você não precisa lidar com isso com frequência. Normalmente, as mensagens pendentes e periódicas não são canceladas (e é por isso que, a propósito, a função send_delayed não retorna timer_id). E quando surgir a necessidade de cancelamento, você poderá usar um dos métodos descritos acima. Ou até mesmo usar outro. Por exemplo, crie agentes separados que processarão uma mensagem pendente. Esses agentes podem ser cancelados o registro quando uma mensagem pendente precisar ser cancelada.

Portanto, no contexto de outras tarefas que nos confrontavam, simplificar o cancelamento garantido de uma mensagem pendente não era tão prioritário quanto gastar nossos recursos na solução desse problema.

Por que o problema é relevante agora?


Tudo é tão simples aqui. Por um lado, as mãos finalmente alcançaram.

Por outro lado, quando novas pessoas que não tiveram experiência em trabalhar com ele começaram a usar o SObjectizer, esse recurso com o cancelamento de temporizadores os surpreende bastante. Não que agradavelmente surpreendente. Se sim, gostaria de minimizar as impressões negativas de conhecer nossa ferramenta.

Além disso, tínhamos nossas próprias tarefas, não precisávamos constantemente cancelar mensagens pendentes. E os novos usuários têm suas próprias tarefas, talvez tudo seja o contrário.

Nova declaração do problema


Quase imediatamente, assim que começou a consideração da possibilidade de um "cancelamento garantido do temporizador", pensei em que a tarefa poderia ser expandida. Você pode tentar resolver o problema de recuperar qualquer uma das mensagens enviadas anteriormente, não necessariamente atrasadas e periódicas.

De tempos em tempos, essa oportunidade é procurada. Por exemplo, imagine que temos vários agentes interagindo de dois tipos: entry_point (aceita solicitações de clientes) e processador (processa solicitações):



Os agentes Entry_point enviam solicitações ao agente do processador, que os processa o máximo possível e responde aos agentes do entry_point. Mas, às vezes, o entry_point pode achar que o processamento de uma solicitação enviada anteriormente não é mais necessário. Por exemplo, o cliente enviou um comando de cancelamento ou "caiu" e você não precisa mais processar suas solicitações. Agora, se as mensagens de solicitação estiverem na fila pelo agente do processador, não será possível recuperá-las. E seria útil.

Portanto, a abordagem atual para resolver o problema do "cancelamento garantido do temporizador" é realizada precisamente como adição de suporte para "mensagens de rechamada". Enviamos qualquer mensagem de uma maneira especial, temos uma alça na mão, com a qual você pode recuperar a mensagem. E não é tão importante se uma mensagem regular ou atrasada responde.

Uma tentativa de propor a implementação de "rechamar mensagens"


Portanto, você precisa introduzir o conceito de "mensagem de rechamada" e apoiar esse conceito no SObjectizer. E assim, permanecer no ramo 5.5. A primeira versão deste segmento, 5.5.0, saiu quase quatro anos atrás, em outubro de 2014. Desde então, não houve grandes mudanças no 5.5. Projetos que já foram trocados ou iniciados imediatamente no SObjectize-5.5 podem mudar para novas versões na ramificação 5.5 sem problemas. Essa compatibilidade deve ser mantida neste momento.

Em geral, tudo é simples: você precisa pegar e fazer.

O que está claro como fazer


Após a primeira abordagem do problema, duas coisas ficaram claras sobre a implementação de "rechamar mensagens".

Sinalizador atômico e sua verificação antes do processamento da mensagem


Em primeiro lugar, é óbvio que dentro da estrutura da arquitetura atual do SObjectizer-5.5 (e talvez ainda mais globalmente: dentro da estrutura dos princípios do próprio SObjectizer-5), é impossível remover mensagens das filas de solicitação do despachante, onde as mensagens aguardam até que os agentes receptores as processem. Tentar fazer isso eliminará toda a ideia de despachantes heterogêneos, que até o usuário pode fazer por conta própria, de acordo com as especificidades de sua tarefa (por exemplo, esta ). Além disso, no caso de enviar uma mensagem no modo 1: N, onde N será grande, será caro manter uma lista de ponteiros para uma instância da mensagem enviada em todas as filas.

Isso significa que, juntamente com a mensagem, algum tipo de sinalizador atômico deve ser transmitido, que precisará ser analisado imediatamente após a remoção da mensagem da fila de solicitações, mas antes que a mensagem seja enviada para processamento no agente receptor. I.e. a mensagem entra na fila e não é removida de nenhum lugar. Mas quando chega a vez da mensagem, sua bandeira é verificada. E se o sinalizador indicar que a mensagem foi retirada, a mensagem não será processada.

Consequentemente, a própria rechamada de mensagens consiste em definir um valor especial para o sinalizador atômico dentro da mensagem.

Objeto Revocable_handle_t <M>


Em segundo lugar, até agora (?) É óbvio que, para enviar uma mensagem revogável, não devem ser utilizados os métodos usuais de envio de mensagens, mas um objeto especial com o nome de código revocable_handle_t.

Para enviar uma mensagem revogável, o usuário deve criar uma instância de revocable_handle_t e, em seguida, chamar o método send nessa instância. E se a mensagem precisar ser recuperada, isso será feito usando o método revoke. Algo como:

 struct my_message {...}; ... so_5::revocable_handle_t<my_message> msg; //    . msg.send(target, //  . ... //    my_message. ); ... //   . msg.revoke(); 

Ainda não há detalhes claros da implementação revocable_handle_t, o que não é surpreendente, pois o mecanismo de trabalho das mensagens de rechamada ainda não foi selecionado. Mas o princípio do trabalho é que, em revocable_handle_t, um link inteligente é salvo na mensagem enviada e no sinalizador atômico para ela. O método revoke () tenta substituir o valor do sinalizador. Se isso der certo, a mensagem, após extrair da fila de pedidos, não será mais processada.

O que não vai ser amigo de


Infelizmente, há algumas coisas com as quais a rechamada de mensagens não pode ser adequadamente vinculada. Só porque a mensagem retirada continua a permanecer nas filas onde já chegou.

message_limits


Um recurso tão importante do SObjectizer como message_limits foi projetado para proteger os agentes contra sobrecarga. Limites de mensagens funcionam com base na contagem de mensagens na fila. Enfileirou uma mensagem - aumentou o contador. Saiu da linha - reduzido.

Porque quando uma mensagem é revogada, ela permanece na fila e, em seguida, message_limits não afeta a resposta da mensagem. Portanto, pode acontecer que a fila tenha um limite no número de mensagens do tipo M, mas todas elas foram recuperadas. De fato, nenhum deles será processado. Mas enfileirar uma nova mensagem do tipo M não funcionará, porque o limite é excedido.

A situação não é boa Mas como sair disso? Não está claro.

mchains de fila fixa


No SObjectizer, uma mensagem pode ser enviada não apenas para o mbox, mas também para o mchain (este é o nosso análogo do canal CSP ). E os mchains podem ter um tamanho fixo para suas filas. Uma tentativa de colocar uma nova mensagem para o mchain com um tamanho fixo no mchain completo deve levar a algum tipo de reação. Por exemplo, aguardando a liberação de espaço na fila. Ou para enviar a mensagem mais antiga.

No caso de uma rechamada de mensagem, ela permanecerá dentro da fila do mchain. Acontece que a mensagem não é mais necessária, mas ocupa espaço na fila do mchain. E evita que novas mensagens sejam enviadas para o mchain.

A mesma situação ruim que com message_limits. E, novamente, não está claro como isso pode ser corrigido.

O que não está claro como fazer


Então, escolhemos duas opções (até agora?) Para implementar mensagens de recall. A primeira opção é simples de implementar e não requer alteração dos giblets do SObjectizer. A segunda opção é muito mais complicada, mas nela o destinatário da mensagem nem sabe que está lidando com mensagens revogáveis. Vamos considerar brevemente cada um deles.

Receba mensagens revogáveis ​​como revocable_t <M>


A primeira solução, que parece, em primeiro lugar, viável e, em segundo lugar, bastante prática, é a introdução de um invólucro especial revocable_t <M>. Quando o usuário envia uma mensagem revogável do tipo M via revocable_handle_t <M>, não é a mensagem M que é enviada, mas a mensagem M dentro do invólucro especial revocable_t <M>. E, consequentemente, o usuário não receberá e processará a mensagem do tipo M, mas a mensagem revocable_t <M>. Por exemplo, desta maneira:

 class processor : public so_5::agent_t { public: struct request { ... }; // ,    . void so_define_agent() override { //   . so_subscribe_self().event( //     ,    //   . [this](mhood_t< revocable_t<request> > cmd) { // ,      . cmd->try_handle([this](mhood_t<request> msg) { ... }); }); ... } ... }; 

O método revocable_t <M> :: try_handle () verifica o valor do sinalizador atômico e, se a mensagem não for recuperada, chama a função lambda passada para ele. Se a mensagem for retirada, try_handle () não fará nada.

Prós e contras dessa abordagem


A principal vantagem é que esta viagem é facilmente implementada (pelo menos até agora parece). De fato, revocable_handle_t <M> e revocable_t <M> serão apenas um complemento sutil para o SObjectizer.

Pode ser necessária uma intervenção nas partes internas do SObjectizer para fazer amigos revocable_t e mutable_msg. O fato é que no SObjectizer existe o conceito de mensagens imutáveis ​​(elas podem ser enviadas no modo 1: 1 e no modo 1: N). E existe o conceito de mensagens mutáveis que só podem ser enviadas no modo 1: 1. Nesse caso, o SObjectizer trata de maneira especial o marcador mutable_msg <M> e executa as verificações correspondentes no tempo de execução. No caso de revocable_t <mutable_msg <M>>, você precisará ensinar o SObjectizer a tratar essa construção como mutable_msg <M>.

Outra vantagem é que a sobrecarga adicional (tanto nos metadados da mensagem revogável quanto na verificação da bandeira atômica) estará apenas em locais onde você não pode ficar sem ela. Onde as mensagens de rechamada não são usadas, não haverá sobrecarga adicional.

Mas o principal menos é ideológico. Nessa abordagem, o fato de usar mensagens revogáveis ​​afeta tanto o remetente (usando revocable_handle_t <M>) quanto o destinatário (usando revocable_t <M>). Mas o destinatário simplesmente não precisa saber que está recebendo mensagens de recall. Além disso, como destinatário, você pode ter um agente de terceiros pronto que é gravado sem revocable_t <M>.

Além disso, permanecem questões ideológicas sobre, por exemplo, a possibilidade de encaminhar essas mensagens. Mas, de acordo com as primeiras estimativas, esses problemas foram resolvidos.

Receba mensagens de rechamada como mensagens regulares


A segunda abordagem é ver apenas a mensagem do tipo M no lado do receptor e não ter uma idéia da existência de revocable_handle_t <M> e revocable_t <M>. I.e. se o processador receber uma solicitação, deverá ver apenas uma solicitação, sem nenhum invólucro adicional.

Na verdade, não se pode prescindir de alguns invólucros nessa abordagem, mas eles serão ocultados no SObjectizer e o usuário não deverá vê-los. Depois que o aplicativo é recuperado da fila, o SObjectizer determinará por si só que essa é uma mensagem revogável especialmente encapsulada, verificará o sinalizador da relevância da mensagem e expandirá a mensagem, se ainda for relevante. Em seguida, ele enviará uma mensagem ao agente para processamento como se fosse uma mensagem regular.

Prós e contras dessa abordagem


A principal vantagem dessa abordagem é óbvia - o destinatário da mensagem não sabe com quais mensagens ele trabalha. Isso permite que o remetente da mensagem retire calmamente as mensagens de quaisquer agentes, mesmo aqueles que foram gravados por outros desenvolvedores.

Outra vantagem importante é a capacidade de integrar-se ao mecanismo de rastreamento de entrega de mensagens ( aqui, a função desse mecanismo é descrita em mais detalhes ). I.e. se msg_tracing estiver ativado e o remetente retirar a mensagem, os traços poderão ser encontrados no log msg_tracing. O que é muito conveniente ao depurar.

Mas a principal desvantagem é a complexidade da implementação dessa abordagem. Em que vários fatores precisarão ser considerados.

Primeiro, sobrecarga. Todo o tipo de coisas.

Digamos que você possa fazer um sinalizador especial dentro de uma mensagem que indique se essa mensagem é revogável ou não. E, em seguida, verifique esse sinalizador antes de começar a processar cada mensagem. Grosso modo, outro se é adicionado ao mecanismo de entrega da mensagem, que funcionará durante o processamento de cada mensagem (!).

Estou certo de que em aplicações reais a perda disso será quase imperceptível. Mas o rebaixamento nos benchmarks sintéticos certamente aparecerá. Além disso, quanto mais abstrato o benchmark, menos trabalho real ele realiza, mais ele afunda. E isso é ruim do ponto de vista de marketing, porque Existem várias pessoas que tiram conclusões sobre a estrutura em termos de parâmetros de referência sintéticos. E eles fazem isso especificamente: não entendendo que tipo de referência é, que basicamente mostra em qual hardware trabalha, mas comparando os totais com o desempenho de alguma ferramenta especializada, em outro cenário, em outro hardware, etc. ., etc.

Em geral, como estamos criando uma estrutura universal, que, como se vê, é julgada por números abstratos em benchmarks abstratos, não queremos perder, digamos, 5% do desempenho no mecanismo de entrega de todas as mensagens devido à adição de um recurso que levará apenas tempo de tempos em tempos e nem para todos os usuários.

Portanto, você precisa garantir que, ao enviar a mensagem ao destinatário, o SObjectizer entenda que, ao extrair a mensagem, você deve lidar com isso de uma maneira especial. Em princípio, quando uma mensagem é entregue a um agente, o SObjectizer armazena com a mensagem um ponteiro para uma função que será usada ao processar a mensagem. Isso é necessário agora para lidar com mensagens assíncronas e solicitações síncronas de maneiras diferentes. Na verdade, é assim que a solicitação da mensagem endereçada ao agente se parece:

 struct execution_demand_t { //! Receiver of demand. agent_t * m_receiver; //! Optional message limit for that message. const message_limit::control_block_t * m_limit; //! ID of mbox. mbox_id_t m_mbox_id; //! Type of the message. std::type_index m_msg_type; //! Event incident. message_ref_t m_message_ref; //! Demand handler. demand_handler_pfn_t m_demand_handler; ... }; 

Onde demand_handler_pfn_t é um ponteiro de função regular:
 typedef void (*demand_handler_pfn_t)( current_thread_id_t, execution_demand_t & ); 

O mesmo mecanismo também pode ser usado para processar especialmente a mensagem que está sendo retirada. I.e. Quando o mbox envia uma mensagem ao agente, ele sabe se uma mensagem assíncrona ou uma solicitação síncrona é enviada para ele. Da mesma forma, um agente pode receber uma mensagem de retorno de chamada assíncrona de uma maneira especial. E o agente salvará, junto com a mensagem, um ponteiro para uma função que saiba como deve lidar com mensagens revogadas.

Tudo parece estar bem, mas existem dois grandes "buts" ... :(

Em primeiro lugar, a interface mbox existente (a classe abstract_message_mbox_t ) não possui métodos para enviar mensagens de rechamada. Portanto, essa interface precisa ser expandida. E para que as implementações de mbox de outras pessoas vinculadas a abstract_message_box_t do SObjectizer-5.5 não sejam interrompidas (em particular, a série mbox é implementada em so_5_extra e eu apenas não as quero).

Em segundo lugar, as mensagens podem ser enviadas não apenas para mbox-s, atrás das quais os agentes estão ocultos, mas também para mchain-s. Quais são as nossas contrapartes nos canais CSP . E até agora, os aplicativos permaneciam sem indicadores adicionais para as funções. Para introduzir um ponteiro adicional em cada elemento do mchain da fila de aplicativos ... Você pode, é claro, mas parece uma solução bastante cara. Além disso, as próprias implementações do mchain até agora não previram uma situação em que a mensagem extraída precisa ser verificada e possivelmente descartada.

Se você tentar resumir todos os problemas descritos acima, o principal problema dessa abordagem é que não é tão fácil fazer sua implementação, por isso é barato nos casos em que as mensagens de rechamada não são usadas.

Mas e quanto ao cancelamento garantido de mensagens pendentes?


Receio que o problema original tenha se perdido na natureza de detalhes técnicos. Suponha que haja mensagens revogáveis, como ocorrerá o cancelamento de mensagens pendentes / periódicas?

Aqui, como se costuma dizer, são possíveis opções. Por exemplo, trabalhar com mensagens pendentes / periódicas pode fazer parte da funcionalidade revocable_handle_t <M>:

 revocable_handle_t<my_mesage> msg; msg.send_delayed(target, 15s, ...); ... msg.revoke(); 

Ou você pode criar sobre revocable_handle_t <M> uma classe auxiliar adicional cancelable_timer_t <M>, que fornecerá os métodos send_delayed / send_periodic.

Ponto branco: solicitações síncronas


O SObjectizer-5 suporta não apenas interação assíncrona entre entidades no programa (enviando mensagens para mbox e mchain), mas também interação síncrona através de request_value / request_future. Essa interação síncrona não funciona apenas para agentes. I.e.Você não pode apenas enviar uma solicitação síncrona para um agente por meio de sua mbox. No caso de mchains, você também pode fazer solicitações síncronas, por exemplo, para outro encadeamento de trabalho, no qual receive () ou select () foi chamado para mchain.

Portanto, ainda não está claro se é permitido usar solicitações síncronas em conjunto com mensagens revogáveis. Por um lado, talvez isso faça algum sentido. E pode parecer, por exemplo, assim:

 revocable_handle_t<my_request> msg; auto f = msg.request_future<my_reply>(target, ...); ... if(some_condition) msg.revoke(); ... f.get(); //      revoke(). 

Por outro lado, ainda existem muitas mensagens incompreensíveis com mensagens de rechamada, portanto a questão da interação síncrona foi adiada para tempos melhores.

Escolha, mas tenha cuidado. Mas escolha


Portanto, há uma compreensão do problema. Existem duas opções para resolvê-lo. O que no momento parece viável. Mas eles diferem muito no nível de conveniência fornecido ao usuário e, ainda mais fortemente, diferem no custo de implementação.

Você tem que escolher entre essas duas opções. Ou invente outra coisa.

Qual é a dificuldade de escolher?

A dificuldade é que o SObjectizer é uma estrutura livre. Ele não nos traz dinheiro diretamente. Fazemos isso, como dizem, por nós mesmos. Portanto, puramente por preferências econômicas, uma opção mais simples e rápida de implementar é mais lucrativa.

Mas, por outro lado, nem tudo é medido em dinheiro e, a longo prazo, uma ferramenta bem-feita, cujos recursos normalmente estão ligados entre si, é melhor do que um patchwork feito de remendos, de alguma forma colados. A qualidade é avaliada pelos usuários e por nós mesmos, quando subsequentemente acompanhamos nosso desenvolvimento e adicionamos novos recursos a ele.

Assim, a escolha, de fato, varia entre benefícios de curto prazo e perspectivas de longo prazo. É verdade que, no mundo moderno, as ferramentas C ++ com perspectivas de longo prazo são de alguma forma nebulosas. O que torna a escolha ainda mais difícil.

É nessas condições que você tem que escolher. Cuidado Mas escolha.

Conclusão


Neste artigo, tentamos mostrar um pouco o processo de projetar e implementar novos recursos em nossa estrutura. Esse processo ocorre regularmente conosco. Anteriormente porque Em 2014-2016, o SObjectizer se desenvolveu muito mais ativamente. Agora, o ritmo de lançamento de novas versões diminuiu. O que é objetivo, inclusive porque, ao adicionar novas funcionalidades sem interromper nada, fica mais difícil a cada nova versão.

Espero que tenha sido interessante olhar nos bastidores para nós. Obrigado pela atenção!

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


All Articles