пятница, 7 апреля 2017 г.

[prog.c++] Тема movable/modifiable сообщений в SObjectizer задышала

В конце февраля, после C++ Russia 2017, возникла тема добавления в SObjectizer поддержки не константных экземпляров сообщений. Суть в том, что в SObjectizer из-за первоначальной направленности на взаимодействие в режиме 1:N, все сообщения доставлялись получателям в виде константных объектов. Получатель имел константную ссылку на пришедшее к нему сообщение и не должен был менять этот объект, т.к. в это же время то же самое сообщение мог обрабатывать еще кто-то.

Для некоторых сценариев это оказалось не очень хорошо. Например, если цепочка агентов выстраивается в конвейер, по которому движется большой объект, а каждый из агентов должен что-то в этом объекте поменять и передать измененный объект дальше. Или когда один агент должен передать "тяжелый" объект другому агенту, но использовать shared_ptr для этих целей не выгодно.

В итоге мы начали думать, как дать возможность пересылать не константные объекты-сообщения при взаимодействии агентов в режиме 1:1. Сначала идеи крутились вокруг movable-сообщений, т.е. сообщений, содержимое которых можно перемещать между экземплярами (например, когда кто-то делает send, то содержимое сообщение сразу же конструируется где-то в заранее созданном буфере у агента-получателя). Но затем была выбрана, как мне представляется, более простая идея: мы применили к сообщениям тот же подход, который в C++ используется для динамически-созданных объектов и unique_ptr. Класс unique_ptr говорит разработчику, что есть только один "владеющий" указатель на объект. Нельзя расплодить unique_ptr, можно лишь передавать право владения так, что только один unique_ptr будет владеть динамически созданным объектом.

Мы добавили в SObjectizer такое понятие, как unique_mbox. Т.е. специальный тип mbox-а, отсылка сообщения в который приводит к передаче не константного объекта-сообщения. Для того, чтобы получить не константное сообщение из unique_mbox-а, обработчик сообщения должен использовать специальный новый тип unique_mhood_t<Msg>. Это тонкая шаблонная обертка, похожая на уже существующий mhood_t, но она позволяет модифицировать полученный объект-сообщение. Так же unique_mhood_t позволяет переслать не константное сообщение дальше.

Вот небольшой кусок реального unit-теста из SObjectizer, который проверяет работу этого механизма. Там агент отправляет самому себе сообщение типа std::string. Получив сообщение, агент меняет текст сообщения и перепосылает измененный объект. При этом проверяется то, что повторно приходит именно тот же самый std::string, но с другим содержимым.

Все самые важные фрагменты выделены жирным. Поскольку взаимодействие посредством unique-сообщениями строится исключительно в режиме 1:1, то unique_mbox создается под конкретного агента посредством метода agent_t::so_create_unique_mbox(). Только тот агент, для которого был вызван so_create_unique_mbox() сможет получать unique-сообщения, отправленные в unique_mbox.

class user_message_tester final : public so_5::agent_t
{
public :
   user_message_tester(context_t ctx)
      :  so_5::agent_t(std::move(ctx))
      ,  m_umbox(so_create_unique_mbox())
   {
      so_subscribe(m_umbox).event(&user_message_tester::on_user_type_message);
   }

   virtual void
   so_evt_start() override
   {
      so_5::send<std::string>(m_umbox, "hello!");
   }

private :
   const so_5::unique_mbox_t m_umbox;

   std::string * m_received_ptr{nullptr};

   void
   on_user_type_message(unique_mhood_t<std::string> cmd)
   {
      std::cout << "user_type_message: " << *cmd << std::endl;
      if("bye!" == *cmd)
      {
         ensure(cmd.get() == m_received_ptr, "cmd must have the same pointer");
         so_deregister_agent_coop_normally();
      }
      else
      {
         m_received_ptr = cmd.get();
         *cmd = "bye!";
         so_5::send(m_umbox, std::move(cmd));
      }
   }
};

Комментариев нет: