среда, 11 декабря 2013 г.

[prog.c++] Зафиксирована версия SObjectizer-5.2.3 "Мижирги"

Очередная версия SObjectizer -- 5.2.3 -- зафиксирована в svn-репозитории на SourceForge в виде тега. Изменений по сравнению с предыдущей версией 2.2.2 довольно много, полная совместимость не сохранена. Но изменения внесены существенные, так что на такой шаг мы пошли сознательно.

Релиз версии 5.2.3 разбит на два этапа. На первом этапе зафиксирована вся функциональность и оформлен тег 5.2.3.0, но в нем нет подробных описаний новвоведений в doxygen-документации. Это описание будет создаваться параллельно с переводом других SObjectizer-подпроектов на версию 5.2.3. И затем будет зафиксирован тег 5.2.3.1, в котором функциональность останется точно такой же, но зато появятся подробные описания в doxygen-представлении. И в декабрьскую сборку SObjectizer, я надеюсь, войдет именно версия 5.2.3.1.

Под катом же краткое описание того, что было сделано в 5.2.3, несколько слов о том, что будет в развитии SObjectizer дальше и разъяснения по поводу необычных названий релизов SObjectizer (таких так Ушба, Джимара, Тетнульд и Мижирги).


В версии 5.2.3 изменен подход к обработке исключений, которые агенты выпускают наружу. В предыдущих версиях SO-5 в этих случаях запускался т.н. event_exception_handler -- объект, специально предназначенный для таких целей. SO-5 предоставлял свою штатную реализацию event_exception_handler. Эта реализация инициировала дерегистрацию кооперации, к которой принадлежал проблемный агент. Пользователь мог назначить свою реализацию event_exception_handler. Однако, такой объект был один на весь инстанс SObjectizer Environment (грубо говоря один на все приложение). Поэтому если несколько библиотек захотели бы установить свои обработчики исключений, то они бы помешали друг другу. Ну и, как выяснилось, особой надобности в кастомных event_exception_handler-ах не было, т.к. круг вариантов в случае выскочившего из агента исключения совсем небольшой.

В версии 5.2.3 от event_exception_handler мы отказались. Теперь каждый агент может сказать SObjectizer-у о том, что нужно делать, если из этого агента выскочит необработанное исключение. Для этого в базовый класс agent_t добавлен виртуальный метод:

virtual exception_reaction_t so_exception_reaction() const;

Этот метод возвращает элемент перечисления exception_reaction_t в котором, на данный момент, есть следующие варианты:

  • abort_on_exception - сразу же вызывается abort() и работа приложения аварийно прерывается;
  • shutdown_sobjectizer_on_exception - проблемный агент переводится в специальное состояние, в котором он не может получать новые сообщения, после чего штатным образом инициируется завершение работы SObjectizer Environment;
  • deregister_coop_on_exception - проблемный агент переводится в специальное состояние, в котором он не может получать новые сообщения, после чего штатным образом инициируется дерегистрация кооперации, к которой принадлежит проблемный агент;
  • ignore_exception - исключение игнорируется, агент продолжает свою работу.

Возвращаемое значение метода so_exception_reaction(), по сути, говорит о том, какую гарантию безопасности исключений обеспечивает агент. И SObjectizer поступает соответствующим образом. Так, если агент возвращает abort_on_exception, значит никакой гарантии он вообще не дает, в каком состоянии оказался агент (и не только агент, но и связанная с ним часть приложения) непонятно. И лучшее, что можно сделать, не спровоцировав кучу наведенных ошибок -- это прервать работу приложения посредством abort.

Значение shutdown_sobjectizer_on_exception указывает, что агент не допустит утечки ресурсов, порчи памяти и пр. отрицательных вещей. Но, тем не менее, приложение не может продолжать свою работу и должно быть завершено.

Значение deregister_coop_on_exception, на мой взгляд, является аналогом basic exception guarantee. Т.е. агент не допускает утечки или порчи ресурсов, но сам может оказаться в не совсем корректном состоянии. Продолжать работу с таким агентом опасно, т.к. это может привести к неожиданным результатам. Поэтому нужно дерегистрировать его кооперацию. Но выскочившее из агента исключение не оказывает влияния на другие кооперации в приложении, поэтому нет необходимости завершать все приложение.

Значение ignore_exception соответствует strong exception guarantee. Т.е. не смотря на то, что агент выпустил исключение наружу, он остается полностью работоспособным и может продолжать обработку сообщений.

Вообще, агенты в SObjectizer должны обеспечивать no throw guarantee. Т.е. все исключения, которые могут возникать во время работы агентов, должны обрабатываться самими агентами и наружу выходить не должны. Однако, программ без ошибок не бывает и если исключение из агента таки выскочит, нужно понимать, что должен делать SObjectizer в этом случае. В версии 5.2.3 SObjectizer вызовет у проблемного агента метод so_exception_reaction() и предпримет соответствующие действия. По умолчанию, so_exception_reaction() возвращает deregister_coop_on_exception. Т.е. будет "убита" лишь проблемная кооперация, а приложение продолжит работать.


Второе "нововведение" -- это возвращение в SO-5 взаимоотношений родитель-потомок для коопераций, которые поддерживались в SO-4. Теперь в SO-5 при регистрации кооперации можно указать, что она является дочерней для какой-то из ранее зарегистрированных коопераций. И при дерегистрации кооперации SObjectizer будет сначала дерегистрировать все ее дочерние кооперации (а так же дочерние кооперации дочерних коопераций и т.д.), а лишь затем указанную в методе deregister_coop() кооперацию.

Данная функциональность очень нужна, если родительская кооперация создает или захватывает какие-то ресурсы, ссылки на которые передаются в дочерние кооперации. Например, создается подключение к БД и открывается файл, а соответствующий дескриптор отдается дочерней кооперации. Если родительская кооперация завершает свою работу (по команде пользователя или из-за исключения), это подключение к БД/файл закрывается и переданный в дочернюю кооперацию дескриптор оказывается недействительным. Дочерняя кооперация в таких условиях работать уже не может и должна быть дерегистрированна.

Сейчас пользователю не нужно реализовывать такие механизмы вручную. Достаточно только при регистрации кооперации указать имя родительской кооперации. Остальное сделает SObjectizer: он гарантирует, что дочерние кооперации будут дерегистрированы до своих родителей.

Такая функциональность нужна не только для контроля за ресурсами. Но и для обеспечения логической целостности. Например, родительская кооперация может получать внешние запросы, и создавать дочерние кооперации для их обработки. Если родитель дерегистрируется, то нет смысла сохранять дочерние кооперации. Они должны исчезнуть вместе со своей родительской кооперацией. Это напоминает дерево процессов в ОС: уничтожение родительского процесса приводит к уничтожению порожденных им дочерних процессов.


Следующее нововведение состоит в том, что сейчас при дерегистрации кооперации нужно указывать причину дерегистрации. Например, что кооперация дерегистрируется из-за исключения. Или из-за того завершения работы родительской кооперации. Или из-за завершения работы всего SObjectizer-а.

Эта причина затем становится доступна т.н. нотификаторам и может быть проанализирована пользователем. А об этом речь пойдет дальше.


В SO-5.2.3 с кооперацией могут быть связаны "нотификаторы" двух типов: нотификатор операции регистрации кооперации и нотификатор дерегистрации. Нотификаторы -- это объекты следующих типов:

typedef std::function<
            void(so_environment_t &, const std::string &) >
      coop_reg_notificator_t;

typedef std::function<
            void(
                  so_environment_t &,
                  const std::string &,
                  const coop_dereg_reason_t &) >
      coop_dereg_notificator_t;

Нотификатор coop_reg_notificator -- это функция, получающая два аргумента: SObjectizer Environment и имя зарегистрированной кооперации. А нотификатор coop_dereg_notificator -- это функция, получающая на один аргумент больше: ей дополнительно передается еще и причина дерегистрации кооперации.

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

В состав SO-5.2.3 включены две штатные реализации нотификаторов. Первый штатный нотификатор создается функцией:

coop_reg_notificator_t make_coop_reg_notificator(
   const mbox_ref_t & mbox );

Он отсылает сообщение msg_coop_registered в указанный mbox.

Второй штатный нотификатор создается функцией:

coop_dereg_notificator_t make_coop_dereg_notificator(
   const mbox_ref_t & mbox );

И он отсылает на указанный mbox сообщение msg_coop_deregistered.

Агенты родительской кооперации могут использовать эти нотификаторы для того, чтобы отслеживать, что именно происходит с их дочерними кооперациями. И предпринимать соответствующие действия, например, пересоздание дочерних коопераций. Как это показано в новом примере в составе SO-5.2.3, coop_notification:

// A class of parent agent.
class a_parent_t
   :  public so_5::rt::agent_t
{
   typedef so_5::rt::agent_t base_type_t;

   public :
      a_parent_t(
         so_5::rt::so_environment_t & env )
         :  base_type_t( env )
         ,  m_self_mbox( env.create_local_mbox() )
         ,  m_counter( 0 )
         ,  m_max_counter( 3 )
      {}

      virtual void
      so_define_agent()
      {
         so_subscribe( m_self_mbox ).event( &a_parent_t::evt_child_created );
         so_subscribe( m_self_mbox ).event( &a_parent_t::evt_child_destroyed );
      }

      void
      so_evt_start()
      {
         register_child_coop();
      }

      void
      evt_child_created(
         const so_5::rt::event_data_t<
               so_5::rt::msg_coop_registered > & evt )
      {
         std::cout << "coop_reg: " << evt->m_coop_name << std::endl;

         if( m_counter >= m_max_counter )
            so_environment().stop();

         // Otherwise should wait for cooperation shutdown.
      }

      void
      evt_child_destroyed(
         const so_5::rt::event_data_t<
               so_5::rt::msg_coop_deregistered > & evt )
      {
         std::cout << "coop_dereg: " << evt->m_coop_name
            << ", reason: " << evt->m_reason.reason() << std::endl;

         ++m_counter;
         register_child_coop();
      }

   private :
      so_5::rt::mbox_ref_t m_self_mbox;

      int m_counter;
      const int m_max_counter;

      void
      register_child_coop()
      {
         auto coop = so_environment().create_coop( "child" );
         coop->set_parent_coop_name( so_coop_name() );
         coop->add_reg_notificator(
               so_5::rt::make_coop_reg_notificator( m_self_mbox ) );
         coop->add_dereg_notificator(
               so_5::rt::make_coop_dereg_notificator( m_self_mbox ) );

         coop->add_agent(
               new a_child_t(
                     so_environment(),
                     m_counter < m_max_counter ) );

         std::cout << "registering coop: " << coop->query_coop_name()
               << std::endl;

         so_environment().register_coop( std::move( coop ) );
      }
};

Взаимоотношения родитель-потомок вкупе с нотификаторами дают возможность реализовывать что-то вроде схемы супервизоров из Erlang-а. В готовом виде в SO-5 таких супервизоров пока нет. Но, возможно, это направление развития SObjectizer в будущих версиях.


В предыдущих версиях SO-5 нельзя было создавать своих наследников класса agent_coop_t. Однако, практика показала, что в ряде случаев это могло бы быть полезным. Например, для сохранения в кооперации ресурсов, совместно используемых всеми агентами кооперации. Теперь можно создавать своих наследников agent_coop_t. Единственное требование: экземпляры коопераций должны быть динамически созданы посредством new, т.к. владение объектом-кооперацией передается SObjectizer-у и он уничтожает кооперацию посредством delete.


Однако, для привязывания ресурсов к кооперации теперь есть еще один способ. В agent_coop_t добавлен шаблонный метод take_under_control(), который получает динамически созданный объект и сохраняет его внутри кооперации. Этот объект будет разрушен в деструкторе кооперации и лишь после того, как агентам кооперации будет дана команда на уничтожение.

Принцип использования take_under_control() показан в новом примере в составе SO-5.2.3, coop_user_resources:

void
init( so_5::rt::so_environment_t & env )
{
   // Создается кооперация, к которой будет привязан ресурс.
   auto coop = env.create_coop( "parent" );
   // Создается и привязывается ресурс logger.
   // Контроль за временем его жизни теперь осуществляет кооперация.
   logger_t * logger = coop->take_under_control( new logger_t() );
   // Ссылка на logger передается агенту кооперации. 
   // Агент не знает, кто контролирует время жизни logger-а.
   // Агент просто знает, что эта ссылка будет оставаться валидной.
   coop->add_agent( new a_parent_t( env, *logger, 2 ) );

   env.register_coop( std::move( coop ) );
}

Так же внутри SObjectizer был произведен небольшой рефакторинг. Какой-то код был удален за ненадобность. В каких-то местах для повышения эффективности стали храниться голые указатели вместо умных ссылок. И еще кой чего по мелочам.

Ближайшее развитие SObjectizer пойдет по следующему сценарию:

  • сначала на SO-5.2.3 будут портированы другие SObjectizer-подпроекты (в таком порядке: so_5_transport, mbapi_4, so_sysconf_4; подпроект so_log_2 уже протестирован совместно с SO-5.2.3). После чего будет выпущена декабрьская сборка SObjectizer, 201312-00;
  • после выпуска сборки 201312-00, скорее всего, будут начаты работы над SO-5.2.4, основной целью которых будет повышение производительности ядра SObjectizer. Этот вопрос пока не был в центре внимания, сейчас ситуация изменится. Так же в 5.2.4 будут вноситься исправления и дополнения, которые всплывут по ходу использования 5.2.3;
  • так же будет создана ветка 5.3, в которой начнется разработка версии 5.3.0, для которой уже высказано несколько интересных идей. Но, вероятно, работы над 5.3 начнутся только когда будет достигнут ощутимый прогресс в работе над 5.2.4. Т.е. не раньше января-февраля 2014.

Ну и планы -- это планы. Как оно пойдет в реальной жизни нужно будет еще посмотреть.


А теперь время рассказа о загадочных названиях релизов SObjectizer. На самом деле все просто: это названия вершин Кавказских гор. Начали мы когда-то с Домбая и теперь, потихонечку, "поднимаемся все выше и выше": Базардюзю, Тебуломста, Ушба, Джимара, Тетнульд. Теперь вот Мижирги (Западная). Когда-нибудь доберемся и до Эльбруса. А там будет видно :)

Ну а горные вершины -- это дань тому очарованию Кавказа, под которое я попал, когда в молодости оказался на верхушке Домбая и склоне Эльбруса (я не альпинист, все благодаря канатным дорогам).

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