LINUX.ORG.RU

SObjectizer-5.8.5

 , , , ,


0

4

Вышла очередная версия библиотеки SObjectizer.

Подробно об изменениях можно прочитать здесь: https://github.com/Stiffstream/sobjectizer/wiki/v.5.8.5

Если же кратко о главном, то:

  • у агента появился новый метод so_drop_all_subscriptions_and_filters;
  • новая опция skip_demands_on_dereg для более тонкой настройки агентов;
  • для mchain-ов появился empty_notificator;
  • в структуру so_5::stats::activity_stats_t добавлено новое поле, которое упрощает контроль за временем работы event-handler-ов.

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

Для тех, кто не знает меня и не слышал про этот проект, вкратце:

SObjectizer – это инструмент для упрощения разработки некоторых типов многопоточных приложений на C++. Основная идея в построении приложения из мелких сущностей-агентов (акторов), которые взаимодействуют между собой через обмен сообщениями, а SObjectizer берет на себя:

  • доставку сообщений агентам-получателям внутри одного процесса;
  • управление рабочими нитями, на которых агенты обрабатывают адресованные им сообщения;
  • механизм таймеров (в виде отложенных и периодических сообщений);
  • возможности настройки параметров работы перечисленных выше механизмов.

Подробнее о SObjectizer и о том, почему он такой, можно прочитать здесь.

ЗЫ. А еще в октябре SObjectizer-5 исполнилось пятнадцать лет 🥳


Сразу хочу извинится за созданный топик по такому минорному релизу. Я и сам бы предпочел обойтись без этого и, по традиции, добавил бы комментарий к старой теме. Но увы, та тема уже в архиве :( Так что прошу по этому поводу гнилыми помидорами в меня не кидаться :)

★★★★★

Последнее исправление: eao197 (всего исправлений: 1)
Ответ на: комментарий от ox55ff

Корутины будут запиливаться в проект для синхронных запросов?

Это пока открытый вопрос.

С одной стороны, я когда-то, года 3 или 4 назад, проштудировал инфо про короутины из C++20, но применять их на практике пока возможности не было, так что успел забыть даже то, что знал. Тут нужно восстановить в памяти что и как.

С другой стороны, если синхронный запрос делает агент внутри своего event-handler-а, то вряд ли здесь короутины помогут, т.к. event-handler занимает нить диспетчера и отдать эту нить кому-то еще проблематично.

А вот если сихронный запрос делается с нити, которая SO-5 не принадлежит (т.е. запрос делается не из SO-5 части приложения), то здесь, вероятно, какие-то варианты возможны.

Будем посмотреть.

eao197 ★★★★★
() автор топика
Ответ на: комментарий от eao197

если синхронный запрос делает агент внутри своего event-handler-а, то вряд ли здесь короутины помогут

Помогут. Корутина делает запрос (кладёт в очередь или mailbox или как там называется) и засыпает, поток возвращается диспетчеру. Когда результат будет готов, диспетчер пробуждает корутину на том месте где остановились. Будет работать даже на одном потоке.

ox55ff ★★★★★
()
Ответ на: комментарий от ox55ff

Корутина делает запрос (кладёт в очередь или mailbox или как там называется) и засыпает, поток возвращается диспетчеру.

Я не настолько копенгаген в короутинах чтобы сейчас как-то это прокомментировать.

eao197 ★★★★★
() автор топика
Ответ на: комментарий от eao197

Не могу сказать.

По корутинам у меня сейчас два представления:

  1. сервера на Asio получаются очень лаконичные
  2. внутреннее устройство не rocket science, но много деталей, за которыми, для меня, трудно видеть более абстрактные и прикладные применения.

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

К примеру, параллельно работающий таймер примет вид (тут мешанина псевдокода с реальными конструкциями):

asio::awaitable<void> timer_loop(asio::cancellation_signal &stop, const asio::steady_timer::duration &dur) {
  asio::steady_timer timer{co_await asio::this_coro::executor};
  auto slot = stop.slot();
  slop.assign([&](asio::cancellation_type) { timer.cancel(); });

  for (;;) {
    std::error_code ec;
    timer.expires_from_now(dur);
    co_await timer.async_wait(asio::redirect_error(asio::use_awaitable, ec));

    if (ec) 
      break;
    
    // do some periodical work
  }
}

void start_timer(asio::io_context &ioc, asio::cancellation_signal &stop, const asio::steady_timer::duration &dur)
{
  asio::co_spawn(ioc, timer_loop(stop, dur), asio::detached);
}

void stop_timer(asio::cancellation_signal &stop)
{
  stop.emit(asio::cancellation_type::all);
}

Т.е. если в каком-то агенте запускается серия асинхронных операций, когда запуск последующей зависит от окончания предыдущей, то можно превратить из лапши колбеков в вызов аналога co_spawn с «линейной» обработкой.

В общем, без конкретики. Надо думать куда и как это впиндюривать.

hatred ★★★
()
Ответ на: комментарий от hatred

В SObjectizer-е же немного другая ситуация. Те же самые периодические действия делаются обычными периодическими сообщениями, которые прилетают к агенту привычным образом, т.е. показанный выше пример в SO-5 выглядел бы как-то так:

class demo_agent : public so_5::agent_t {
  struct msg_next_turn final : public so_5::signal_t {};
  so_5::timer_id_t m_timer;
  ...
public:
  void so_define_agent() override {
    // Подписка на периодическое сообщение.
    so_subscribe_self().event(&demo_agent::evt_next_turn);
    ...
  }
  void so_evt_start() override {
    // Запускаем таймер.
    m_timer = so_5::send_periodic<msg_next_turn>(*this, 125ms, 125ms);
    ...
  }
  ...
  void evt_next_turn(mhood_t<msg_next_turn>) {
    ... // Реакция на таймер. Делаем то, что нам нужно.
  }
  ...
  void evt_some_other_case(mhood_t<some_msg>) {
    // Решили, что таймер нам больше не нужен.
    m_timer.release();
  }
}

С своем вопросе ув.тов. @ox55ff поднял важную тему, которая лежит в несколько иной плоскости.

Синхронное взаимодействие есть и сейчас. Но когда это синхронное взаимодействие используется внутри обработчика события агента, т.е. что-то вроде:

class another_demo_agent : public so_5::agent_t {
  ...
  void evt_some_action(mhood_t<some_msg>) {
    // Нужно что-то узнать у другого агента и продолжить только когда
    // ответ будет получен.
    auto reply = my_request_reply::ask_value(m_target, 10s, ...);
    ... // Продолжаем что-то делать с ответом.
  }
};

то рабочая нить, на которой SObjectizer запустил another_demo_agent::evt_some_action, блокируется. И если этот another_demo_agent делит рабочую нить с другими агентами, то и все его «соседи» по рабочему контексту будут заблокированы.

Чтобы такой блокировки не было подобные evt_some_action приходится распиливать, минимум, на два отдельных события:

class another_demo_agent : public so_5::agent_t {
  ...
  void evt_some_action(mhood_t<some_msg>) {
    // Нужно что-то узнать у другого агента и продолжить только когда
    // ответ будет получен. Поэтому сперва отправляем запрос.
    so_5::send<my_request>(m_target, 10s, ...);
    // И завершаем этот обработчик, чтобы диспетчер мог запустить
    // что-то еще на контексте данной нити.
  }
  void evt_reply_received(mhood_t<my_reply>) {
    ... // Продолжаем что-то делать с ответом.
  }
};

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

И есть некая надежда, что привлечение короутин может существенным образом снизить актуальность данной проблемы.

Но о том, что связано с использованием stackless coroutines из С++20, напишу в следующем комментарии, а то этот и так уже очень большой получается.

eao197 ★★★★★
() автор топика
Ответ на: комментарий от eao197

Так вот, если для синхронного взаимодействия агентов привлекать C++20-короутины, то, как я понимаю, придется менять формат event-handler-ов. Появится дополнительный:

so_5::awaitable_t evt_some_event(mhood_t<some_msg>) {
  ...
  auto my_reply = co_await my_request_reply::ask_value(m_target, 10s, ...);
  ...
}

И нужно будет поддерживать как старый формат, так и новый. Есть в этом тот негативный момент, что мы пока что не собираемся переводить SObjectizer с C++17 на C++20 (по крайней мере еще в течении года SO-5 будет оставаться на 17-ом стандарте, а может и два года). А значит придется в коде сильно обкладываться #if-ами, чтобы в C++17 поддерживать один набор фич, а в C++20 – другой.

Это неприятно в проекте, который никем не спонсируется, но не смертельно.

Пока что самым непонятным местом является поведение диспетчера в случае, когда event-handler у агента оказывается короутиной.

Сейчас все диспетчеры работают по очень простому принципу – у них есть очереди из execution_demand_t. И все, что диспетчеру нужно делать – это брать очередной execution_demand, вызывать для него call_handler, ждать пока call_handler вернет исполнение и переходить к следующему execution_demand.

Но в случае с короутинами ситуация усложняется – нельзя просто взять и дождаться завершения call_handler-а. По сути, execution_demand превращается в объект, для которого call_handler нужно будет вызывать несколько раз. Вызвали в первый раз – он возвращает признак «короутина приостановлена» и диспетчер должен этот execution_demand куда-то отложить на время, чтобы перейти к обработке остальных execution_demand-ов.

И тут нужно будет учесть, что следующий execution_demand в очереди относится к тому же агенту. А значит этот execution_demand также нужно отложить на какое-то время. И другие последующие execution_demand-ы, относящиеся к тому же агенту.

Более того, могут быть ситуации, когда несколько агентов как бы завязаны на одну общую очередь заявок. Т.е., например, в очереди стоит d1(agent1), d2(agent1), d3(agent2), d4(agent1). И если обработчик d1 у агента agent1 – это короутина, то до ее завершения должна быть приостановлена обработка не только d1/d2/d4, но и d3. Т.к. из-за завязки на общую очередь d3 для agent2 не может запустить до того, как будет закончена обработка d1 и d2.

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

В общем, непросто все с внедрением короутин. Поэтому в условиях ограниченных ресурсов есть серьезная дилемма – тратить силы на попытки вкорячить короутины в SO-5 или же добавить что-то более насущное (например).

eao197 ★★★★★
() автор топика

so_drop_all_subscriptions_and_filters

Префикс с именами, являющимися предложениями, звучит забавно.

Читаю как команды «так сбрось уже все подписки и фильтры», «так определи агента», «так подпиши себя», … and so on …

monk ★★★★★
()
Ответ на: комментарий от monk

Префикс с именами, являющимися предложениями, звучит забавно.

Видимо, мое незнание английского языка позволяет смотреть на это дело спокойнее :)

Ноги растут из SObjectizer-4 где агентов уже нужно было наследовать от общего базового класса. Но где в производном классе человек мог захотеть иметь метод с именем, которое уже есть в базовом классе. Типа query_name. Местами это было неудобно.

Поэтому в SO-5 мы специально сделали так, что все публичные (или виртуальные) методы класса agent_t, которые могут быть видны в производном классе, имеют префикс so_ (сокращение от SObjectizer).

Делать префикс so5_ не стали, т.к. тогда пришлось бы кучу кода перелопачивать при переходе на условный SO-6.

eao197 ★★★★★
() автор топика
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.