среда, 15 октября 2014 г.

[prog.c++] Состоялся релиз SObjectizer-5.5.1

SObjectizer -- это реализация модели акторов для C++, позволяющая программировать на C++ с использованием работающих на разных нитях агентов, общающихся между собой посредством сообщений. SObjectizer существенно упрощает разработку событийно-ориентированных приложений, для параллельной и независимой обработки событий в которых требуется многопоточность. SObjectizer использовался для создания как для мелких утилит, так и серьезных распределенных приложений, работающих под большой нагрузкой в режиме 24x7.

Последние несколько лет SObjectizer развивается на SourceForge как OpenSource проект под BSD-лицензией. Подробнее об истории развития SObjectizer можно прочитать здесь.

Версия 5.5.1 является очередным шагом в эволюции проекта. Эта версия не добавляет практически ничего нового к версии 5.5.0 и сохраняет совместимость с ней. Но позволяет писать меньше кода, что должно упростить использование SObjectizer и снизить порог входа для новичков. Более подробная информация о нововведениях в v.5.5.1 доступна в соответствующем разделе Wiki проекта, а так же, тезисно, на русском языке, в продолжении поста под катом.

Версия 5.5.1 может быть загружена из раздела Files или получена из Subversion-репозитория.

В Files для загрузки доступны следующие архивы:

  • so-5.5.1.7z -- исходный текст ядра SObjectizer (включая тесты и примеры);
  • so-5.5.1--doc-html.7z -- сгенерированный посредством Doxygen API Reference Manual;
  • so-5.5.1--bin-msvs2013-x86.7z -- исходные тексты и 32-битовые бинарники для Windows (скомпилированы посредством MS Visual Studio 2013 Express);
  • so-5.5.1--bin-msvs2013-x86_amd64.7z -- исходные тексты и 64-битовые бинарники для Windows (скомпилированы посредством MS Visual Studio 2013 Express).

Примечание. Этот релиз содержит только ядро SObjectizer (т.е. проект so_5). Никакие другие подпроекты (вроде so_log или so_sysconf) в релиз не включены. Возможно, сборка SObjectizer Assembly со всеми подпроектами будет сформирована и опубликована позже (если она действительно кому-то потребуется).

Вкратце перечень изменений выглядит следующим образом:

Для хранения ссылки на mbox теперь можно использовать более короткое имя so_5::rt::mbox_t вместо so_5::rt::mbox_ref_t. Старое имя сохраняется, оно объявлено как deprecated и когда-нибудь оно будет удалено. Пока же старый код продолжит работать, а в новом коде лучше использовать mbox_t.

Вместо имени so_5::rt::smart_atomic_reference_t теперь введено имя so_5::intrusive_ptr_t. Старое имя объявлено как deprecated и когда-нибудь будет удалено. В принципе, это изменение вряд ли кого-то коснется, если только в коде не используются преаллоцированные экземпляры сообщений (хранящиеся посредством вот таких интрузивных указателей).

Вместо довольно распространенной конструкции so_subscribe(so_direct_mbox()) теперь можно писать so_subscribe_self().

При подписке события агента на сигнал теперь можно использовать более компактную запись event<SIGNAL>(handler) вместо старой записи event(so_5::signal<SIGNAL>, handler). Причем это касается как подписки событий обычного агента, так и подписки событий ad-hoc агентов. Старый формат поддерживается сейчас и будет поддерживаться в будущем. Этот новый вариант мог бы быть сделан и раньше, но раньше мы оглядывались на MSVS2012 в которой не было variadic templates, теперь поддержкой MSVS2012 мы не занимаемся, поэтому появилась возможность использовать более компактный вариант подписки.

Теперь подписывать события агента можно не только через цепочку so_subscribe().event(), но и посредством новых методов event класса state_t. Старый способ подписки удобен для случая, когда агент работает всего в одном состоянии (обычно в состоянии "по умолчанию", в которое агента переводит сам SObjectizer при регистрации агента). Если же у агента несколько состояний, то лаконичнее выглядит подписка событий через состояния:

virtual void my_agent::so_define_agent() override
{
  st_disconnected.event( &my_agent::evt_force_connect )
    .event( &my_agent::evt_connect_timeout )
    .event( &my_agent::evt_connect_result )
    .event( &my_agent::evt_get_status_when_disconnected );

  st_connected.event( &my_agent::evt_force_disconnect )
    .event( &my_agent::evt_incoming_data );

  st_session_established.event( &my_agent::evt_force_disconnect )
    .event( &my_agent::evt_incoming_data );
  ...
}

Менять состояние агента теперь можно еще двумя способами. Во-первых, с помощью метода state_t::activate. Во-вторых, с помощью переопределенного operator>>=. Старый способ так же поддерживается. Поэтому эти три строки эквивалентны друг другу:

so_change_state( st_connected );
st_connected.activate();
this >>= st_connected;

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


st_initial.event< msg_start >( [=] {
    this >>= st_disconnected;
    initiate_connection();
  } );

st_disconnected.event( [=]( const msg_connect_result & evt ) {
    if( evt.successful() ) {
      this >>= st_connected;
      initiate_session();
    }
    else
      initiate_reconnect();
  } )
  .event< msg_force_disconnection >( [=] {
    close_connection();
    this >>= st_disconnected;
    initiate_reconnect();
  } );
...

Добавлено целое семейство свободных функций so_5::send для упрощения отсылки сообщений и сигналов: send/send_to_agent, send_delayed/send_delayed_to_agent, send_periodic/send_periodic_to_agent. Вот, для сравнения, одни и те же действия, выполняемые разными способами:

// Старый способ.
auto msg = std::unique_ptr< msg_connect >( new msg_connect( address, port, timeout ) );
mbox->deliver_message( std::move( msg ) );
// Новый способ.
so_5::send< msg_connect >( mbox, address, port, timeout );

// Старый способ.
auto msg = std::unique_ptr< msg_connect >( new msg_connect( address, port, timeout ) );
so_direct_mbox()->deliver_message( std::move( msg ) );
// Новый способ.
so_5::send_to_agent< msg_connect >( *this, address, port, timeout );

// Старый способ.
auto msg = std::unique_ptr< msg_connect >( new msg_connect( address, port, timeout ) );
so_environment().single_timer( std::move( msg ), mbox, pause );
// Новый способ.
so_5::send_delayed< msg_connect >( mbox, pause, address, port, timeout );

// Старый способ.
auto msg = std::unique_ptr< msg_connect >( new msg_connect( address, port, timeout ) );
so_environment().single_timer( std::move( msg ), so_direct_mbox(), pause );
// Новый способ.
so_5::send_delayed_to_agent< msg_connect >( *this, pause, address, port, timeout );

Кроме этого, функции семейства so_5::send позволяют единообразно отсылать как сообщения, так и сигналы. Запись в любом случае будет вида send<MSG>(...), а не вызов deliver_message для сообщений и deliver_signal<MSG> для сигналов. Что делает более простым написание кода внутри шаблонов, где требуется отсылать сообщения/сигналы.

Ну и еще сейчас есть возможность указать SObjectizer-у, что уникальное имя кооперации нужно сгенерировать самостоятельно, пользователь не хочет этим заниматься:

auto coop = env.create_coop( so_5::autoname );
env.register_agent_as_coop( so_5::autoname, std::move( my_agent ) );

Плюс появилось несколько новых примеров в дистрибутиве SObjectizer, которые демонстрируют использование этих возможностей "в полный рост". Один из них я подробнее разберу в блоге в последующие дни.

Вообще говоря, этот релиз полностью эволюционный. Пожалуй, один из немногих релизов за последнее время, в котором не было цели что-то добавить. Главной целью было упростить выполнение повседневных, рутинных операций. Все-таки какой-то опыт работы с SO5 накапливается, появляется возможность его проанализировать и устранить ошибки, а так же внести полезные для пользователя изменения. Надеюсь, это будет первое такое звено в дальнейшей цепочки подобных эволюционных релизов. А там, глядишь, инструмент приобретет и совсем новое качество.

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