пятница, 17 сентября 2021 г.

[prog.thoughts] Что могло бы войти в SObjectizer-5.7.3 (SO-5.8?)

SObjectizer давно уже достиг состояния, когда его возможностей хватает для закрытия наших потребностей. Да и потребностей у нас сейчас заметно поменьше, чем в былые годы. В прошлом году мы задействовали SObjectizer только в aragata. В этом году относительно недавно стали его применять в разработке прототипа нового заказного приложения. Что из этого получится и будет ли SObjectizer применяться там впоследствии -- открытый вопрос, на который сейчас сложно дать ответ. Однако же, пока что возможностей SObjectizer (+so5extra) и в этом новом проекте хватает.

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

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

Итак, милости прошу под кат тех, кому интересно следить за SObjectizer-ом.

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

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

Это не гибко.

В каких-то задачах может потребоваться манипулировать параметрами новых рабочих потоков. Скажем, задавать размер стека для потока. В других задачах может потребоваться выполнять какие-то начальные действия при запуске новой рабочей нити (например, вызывать sigprocmask/pthread_sigmask).

Ничего подобного сейчас в ядре SObjectizer-а не предусмотрено (тогда как в диспетчерах asio_one_thread/asio_thread_pool из so5extra пользователь может задать для диспетчера собственный тип рабочей нити). И, наверное, имеет смысл это поменять.

Сделать создание кастомных рабочих нитей вместо std::thread можно несколькими способами.

Во-первых, можно пойти по тому же пути, что и в so5extra, т.е. можно параметризовать тип диспетчера типом рабочей нити. По умолчанию это будет std::thread, но пользователь сможет подсовывать и любой собственный тип, если этот тип мимикрирует под std::thread.

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

Во-вторых, можно поступить так же, как это уже сделано с механизмом блокировок: SObjectizer в диспетчерах использует не std::mutex-ы, а интерфейсы disp::mpsc_queue_traits::lock_t и disp::mpmc_queue_traits::lock_t, плюс фабрики для создания экземпляров для этих интерфейсов.

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

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

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

Какой именно из подходов предпочтительнее пока мне лично непонятно. Определимся с этим тогда, когда до реализации подобной функциональности дойдут руки.


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

Суть вот в чем. В SObjectizer можно делать обработчики входа/выхода в/из состояния (например). Также можно цеплять к кооперациям нотификаторы окончательной регистрации/дерегистрации кооперации. Общее между обработчиками и нотификаторами то, что они должны быть noexcept, т.е. не должны выпускать наружу исключения.

Проблема же состоит в том, что внутри таких обработчиков/нотификаторов зачастую следует отсылать какие-то сообщения посредством send-а. А send может бросать исключения.

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

Можно, конечно, просто обрамить все try..catch(...){} и забыть. Но это не есть хорошо, т.к. сообщения же не зря отсылаются. Ведь требуется какая-то реакция на смену состояния или на факт регистрации/дерегистрации кооперации. А этой реакции-то и не будет. Проблема будет просто заметена под ковер и непонятно как это скажется впоследствии (скорее всего плохо скажется).

Поэтому, как это ни странно, самой лучшей реакцией на возникающее в обработчике/нотификаторе исключение, является выпуск этого исключения наружу.

Да, приложение при этом упадет. Но это лучше, чем если оно продолжит работу в непонятно каком состоянии.

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

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

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

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

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

Возможно, эта проблема останется неразрешенной в рамках ветки 5.7. Но если хорошее решение для этой проблемы будет найдено, то это вполне себе повод открыть новую ветку 5.8 немного поломав совместимость с предыдущими версиями. Думаю, это будет оправдано.

Но сперва такое решение нужно найти.


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

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