суббота, 24 сентября 2016 г.

[prog.c++] Переполенные mchains и доставка отложенных/периодических сообщений

Пока готовил очередную статью для Хабра, выяснил, что в SObjectizer при добавлении message chains (это нечто вроде CSP-шных каналов) был допущен серьезный просчет. Дело вот в чем: mchain-ы могут использоваться для отсылки отложенных и периодических сообщений. Т.е. можно вызывать send_delayed или send_periodic, а в качестве адресата указать mchain. И сообщение "упадет" в этот mchain спустя указанное время.

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

  • можно подождать какое-то время на send-е. Если за это время место в mchain-е освободилось, то просто добавить сообщение в mchain и все. А вот если мы подождали, но места не нашлось, тогда идем к следующему пункту. Впрочем, можно сконфигурировать mchain так, чтобы ожидания вообще не было. Тогда мы сразу же идем к следующему пункту;
  • т.к. места в mchain-е нет, то SObjectizer смотрит на параметр overflow_reaction для mchain и:
    • в случае drop_newest просто игнорирует новое сообщение, которые мы пытаемся добавить в mchain;
    • в случае remove_oldest выбрасывает самое старое сообщение из mchain-а, а новое -- добавляет в mchain;
    • в случае throw_exception выбрасывает самое новое сообщение и генерирует исключение;
    • в случае abort_app просто вызывает std::abort.

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

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

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

Во-вторых, на нити таймера нельзя бросать исключения. В этом нет смысла, т.к. таймер понятия не имеет, что делать с исключением о переполнении какого-то mchain-а. Любое такое исключение просто приведет к вызову std::abort.

Тем не менее, все версии SO-5 с поддержкой mchain-ов, включая последнюю стабильную 5.5.17.1, не учитывают этих ограничений для контекста таймерной нити. И, если пользователь вызывает send_delayed для ограниченного по размеру mchain-а с ожиданием на переполнении и с реакций throw_exception, то когда время доставки сообщения наступит, а mchain будет полон, то сперва нить таймера заснет на этом mchain-е, затем будет брошено исключение, от которого все приложение упадет из-за вызова std::abort.

Такой вот недосмотр.

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

  • если нить таймера обнаруживает, что отложенное/периодическое сообщение идет в переполненный mchain, то ожидание на этом mchain-е не производится, даже если такое ожидание предписано в параметрах mchain-а. Нить таймера просто сразу начнет обрабатывать overflow_reaction. Без каких-либо задержек и ожиданий;
  • вместо throw_exceptio нить таймера выполняет реакцию drop_newest, т.е. простое выбрасывание сообщения, как будто его и не было.

Эти исправления уже реализованы и протестированы. Но в основную ветку я их пока не включил. Хочу послушать другие мнения. Может есть какие-то другие подходы к решению проблемы выполнения overflow_reaction на контексте нити таймера?

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