понедельник, 21 февраля 2022 г.

[prog.c++] Еще о возможных направлениях развития SObjectizer

Давеча поймал себя на том, что работы над SObjectizer-5.6 начались где-то приблизительно в это время три года назад, в феврале 2019.

То событие было довольно таки знаковым, т.к. с 2014-го года развивалась ветка 5.5, в которой не было серьезных ломающих изменений и переход на новые версии в рамках ветки 5.5 происходил практически безболезненно. И после 4.5 лет сохранения совместимости между версиями SObjectizer мы таки решились эту совместимость поломать. Как в плане API самого SObjectizer-а, так и в плане минимальных требований к стандарту C++.

Формально говоря, ветка 5.7 сломала совместимость с веткой 5.6. Но, по сути, 5.7 -- это все тот же SO-5.6, с минимальными косметическими изменениями в именовании пары функций. Так что можно сказать, SO-5.6 продолжает свое развитие уже на протяжении трех лет подряд, просто сейчас под именем SO-5.7.

Неожиданно быстро пролетевшие три года поступательного развития -- это серьезно. Это такой срок, который позволяет задуматься о том, что можно было пойти на выделение ветки 5.8, в которой за счет отказа от 100% совместимости с 5.6/5.7 можно было бы добавить что-то новое и полезное. Что-то, что вряд ли получится сделать в рамках API версий 5.6/5.7.

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


Первая идея, уже зафиксированная на GitHub-е в виде issue -- это возможность подписывать на сообщения из mbox-ов не только агентов, но и другие сущности, если эти сущности реализуют некоторый новый интерфейс message_sink_t.

Выгода от этой фичи будет в том, что она позволит выстраивать цепочки из mbox-ов разных типов. Например, может быть обычный MPMC-mbox, одним из подписчиков которого станет round_robin mbox, а одним из подписчиков этого round_robin mbox-а станет, скажем, retained_mbox.

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

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

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

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


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

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

Вопросы о поддержке короутин в SObjectizer всплывали раньше, но всерьез я эту задачу не рассматривал потому, что:

  • нам самим это пока что было не нужно, а с запросами на добавление такой функциональности пока никто из пользователей не обращался;
  • в C++ нет поддержки stackful-короутин "из коробки". Нужно будет завязываться на какую-то внешнюю библиотеку, что не есть хорошо, т.к. ядро SObjectizer-а хотелось оставить минимальным и без внешних зависимостей.

Но вот в 2020-ом году при разработке arataga был первый звоночек о том, что stackful-короутины могли бы стать одним из подходов к решению проблемы поддержки множества подключений к прокси-серверу. А в прошлом году мы начали делать прототип нового приложения для нового заказчика, в котором также одним из возможных решений на горизонте маячат stackful-короутины (не факт, что до них дело дойдет, но в чем-то они выглядят привлекательно).

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

Как именно должна выглядеть поддержка короутин в SObjectizer мне пока не очень понятно.

Есть мысль о том, что вызовы receive и select, предназначенные для работы с mchain-ами, должны проверять, вызываются ли они на контексте диспетчеров coro_one_thread/coro_thread_pool. Если да, то при необходимости заснуть на пустом mchain-е, происходит не только перевод текущей короутины в режим сна, но и попытка "разбудить" какую-то из готовых к исполнению короутин. Для чего также потребуется модифицировать реализацию mchain-ов, чтобы там не простые std::mutex/std::condition_variable использовались, а что-то, что дружит с короутинами.

Возможно, в so_environment_t нужно добавить какой-то вариант yield, чтобы агент, работающий на coro_one_thread/coro_thread_pool мог сам дать команду "разбудить" какую-то из других короутин. Но на этот счет я пока не уверен.

Короче говоря, тут есть о чем подумать. Очевидно то, что думать в этом направлении нужно. А вот к чему именно следует прийти пока не понятно.

Непонятно и то, следует ли закладываться на поддержку stackless-короутин из C++20. Тут нужно будет еще раз проштудировать документацию по этим самым stackless-короутинам, освежить их в памяти. Может какие-то полезные идеи и появятся.

-----

Однако, с C++20 есть и другой момент: не думаю, что в 2022-ом году имеет смысл делать SObjectizer-5.8, который будет требовать для себя C++20 в качестве минимального стандарта. ИМХО, года до 2024-го правильным было бы оставаться на C++17.

Так что в ближайшей перспективе планов и, главное, желания переходить на C++20 нет. Посему C++17 -- это "наше все" на ближайшие пару лет.


В качестве дисклаймера: пока не представляю когда представиться возможность заняться разработкой SObjectizer-5.8. Как и не представляю, представиться ли такая возможность вообще. Только надеюсь то, что когда-нибудь представится.

Данный пост написан прежде всего в качестве напоминалки самому себе. Чтобы было к чему вернуться со временем.

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

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