среда, 18 ноября 2020 г.

[prog.c++] Обработка событий агента с учетом приоритетов в SObjectizer: нужно, не нужно, если нужно, то как именно?

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

Некоторое время назад на GitHub-е был открыт issue с предложением сделать привязку какой-то дополнительной информации для подписки. В частности, информации о приоритете подписанного сообщения.

Сейчас появилась возможность вплотную заняться данным issue. Хотя с этой темой не так все просто.

Суть проблемы в том, что в SObjectizer-5 все события у агентов равноприоритетны. Т.е. если агенту отсылаются сообщения вот в таком порядке: msg_A, msg_B и msg_C, то обрабатываться эти сообщения будут в таком же порядке.

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

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

Получается, что у кого-то из пользователей SObjectizer есть потребность в приоритизированной обработке сообщений для одного агента. Которую либо нужно удовлетворить в самом SObjectizer (или в so5extra), либо нужно показать, как этого же эффекта достичь без допиливания SObjectizer-а под себя.

Пока есть более-менее свободное время хочется либо закрыть issue 17 добавлением в SO-5/so5extra новой функциональности, либо написанием примера (примеров) с демонстрацией получения искомого результата имеющимися средствами.

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

Так что если кого-то интересует SObjectizer и его возможности, то приглашаю присоединится к разговору. Либо прямо в обсуждении issue 17 на GitHub, либо же здесь в комментариях. А я под катом попытаюсь рассказать, что думаю по этому поводу.

Лирическое отступление про приоритеты для обработчиков событий

В SObjectizer-4 была поддержка приоритетов для событий. Там можно было подписать один обработчик сообщения msg_A с приоритетом 1, а второй обработчик этого же сообщения для этого же агента с приоритетом 2.

Фича эта была крайне неоднозначной.

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

Во-вторых, приоритеты событий требовали особого внимания. Например, пусть у агента в состоянии st_one есть два обработчика для msg_A с разными приоритетами. По идее, сперва должен отработать один, затем второй. Но что, если первый заменит состояние агента?

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

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

Приоритеты не для событий, а для сообщений

В открытом на GitHub-е issue речь идет о том, чтобы приоритеты назначались не отдельным событиям, а сообщениям.

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

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

Т.е., представим себе, что агенту отсылаются сообщения вот в таком порядке: msg_A, msg_B и msg_C. Но обрабатываются они агентом уже в другом порядке: msg_C, msg_A, msg_B. Происходит это потому, что у msg_C приоритет 3, у msg_A -- приоритет 2, а у msg_B -- приоритет 1.

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

Лично мне не нравится схема с приоритетами для подписки

Мне не нравится идея добавить в методы подписки дополнительный параметр, посредством которого задавались бы приоритеты. Тому есть ряд причин:

  • если приоритет подписки события используется для того, чтобы определить местоположения сообщения в очереди заявок, то текущее состояние агента будет влиять на то, куда и как встанет сообщение. Например, в текущем состоянии агента вообще нет подписки на сообщение. И что делать в этом случае?
  • ничто не запрещает программисту задать разные приоритеты для разных событий агента в разных состояниях. Тогда при получении сообщения место заявки на обработку сообщения будет определяться текущим состоянием агента и заявка может получать разные приоритеты. Что вряд ли найдет понимание у большинства пользователей;
  • если уж расширять информацию, которая хранится с подпиской, то зачем ограничиваться только приоритетами? Может кому-то потребуется еще что-то, какие-то дополнительные атрибуты, специфичные для предметной области? Да и сами приоритеты в каком виде должны быть? Скорее всего должно хватить и просто int8_t. Но мало ли, вдруг приоритет -- это какое-то составное значение, для хранения которого нужен uint64_t... Соответственно, если уж расширять информацию о подписке, то так, чтобы можно было разные пользовательские данные ассоциировать с подпиской. Да так, чтобы в одном приложении без проблем можно было интегрировать фрагменты кода, каждый из которых расширяет информацию о подписке разными типами данных. А это не так уж просто сделать эффективно. И об этом пока не просили.

Подход, который не взлетел

До написания этого поста я попробовал сделать один подход к снаряду и попытался проработать следующую идею: реализовать базовый тип агента agent_with_custom_own_queue_t, который бы хранил заявки в своей собственной очереди.

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

Диспетчер со временем извлекает из своей очереди "псевдо" заявку и запускает ее. А она дергает агента и агент извлекает актуальную заявку с исходным сообщением из собственной очереди.

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

При этом такой подход был бы ортогонален уже существующим в SObjectizer-е диспетчерам. Что позволяет сделать приоритетную обработку сообщений хоть на one_thread, хоть на thread_pool, хоть на active_group диспетчерах.

К сожалению, на практике это решение выглядит подтверждением афоризма про то, что любая проблема имеет простое, очевидное и неправильное решение ;)

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

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

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

Какой-то специализированный диспетчер все-таки нужен

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

Основной вопрос в том, кто будет делать этот диспетчер? Прикладной разработчик, которому потребуется подобная функциональность в его проекте? Или же подобный диспетчер должен стать частью SO-5/so5extra?

При этом ключевым вопросом будет способ информирования этого диспетчера о приоритетах сообщений.

В каких-то случаях приоритет будет зависеть только от типа сообщения. Например, msg_alarm будет иметь больший приоритет, чем msg_warn, а msg_warn будет важнее, чем msg_notice. Значения этих приоритетов могут быть зафиксированы в compile-time.

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

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

Можно высказывать все, что думаете по этому поводу, в том числе и бросаться гнилыми помидорами

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

В общем, любые конструктивные соображения приветствуются. Как ни странно, но "мне пока это не было нужно" в данном контексте также будет выглядеть конструктивно :)

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