пятница, 24 июня 2016 г.

[prog.c++11] Как можно получить результат доставки сообщения до агента-адресата?

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

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

Навскидку есть два несложных способа, каждый из которых обладает своими недостатками.

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

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

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

  • уведомление delivered должно отсылаться агентом-адресатом после того, как он завершил обработку интересующего нас сообщения. Это принципиальный момент. Никто кроме агента-адресата не знает, когда эта обработка может считаться законченной;
  • а вот уведомление lost отсылается в деструкторе интересующего нас прикладного сообщения. Деструктор прикладного сообщения вызывается всегда, вне зависимости от того, было оно доставлено до адресата или нет. Соответственно, в деструкторе можно проверить некий флаг и, если этот флаг не выставлен, решить, что сообщение доставлено не было и отослать уведомление lost.

Получается нехитрая логика: в прикладном сообщении заводятся дополнительные поля (как минимум mbox для уведомлений + флаг успешности доставки). Когда агент-адресат получает прикладное сообщение и успешно обрабатывает его, то агент-адресат выставляет флаг и отсылает уведомление delivered. Затем в деструкторе прикладного сообщения флаг успешности доставки проверяется и, при необходимости, отсылается уведомление lost.

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

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

На Bitbucket-е выложен небольшой пример, показывающий, как воплотить второй подход в жизнь: registered_delivery_example. Показанные там классы delivered, lost и message_base из пространства имен registered_delivery вполне можно копипастить к себе и использовать для подобных целей, благо это шаблонные классы, предназначенные именно для этого :)

понедельник, 20 июня 2016 г.

[life.sport.darts] А руки-то помнят... :)

Очень давно не постил ничего подобного, но тут нужно. А то ведь может никогда такого уже и не повторю.

Для тех, кто не в теме -- это закрытие 157 очков, одно из "гроссмейстерских", если можно так сказать :)

ЗЫ. Мишени уже около 2-х лет. В лучшие же годы у меня мишень жила не больше 8-9 месяцев, а в такое состояние я ее приводил месяца за 4.

воскресенье, 19 июня 2016 г.

[prog.c++14] Еще один способ конкатенации строк в compile-time

Недавно у себя в G+ ленте я дал ссылку на свою первую попытку реализации конкатенации строк в compile-time: https://godbolt.org/g/GWLTN8. Эта версия работала под GCC, но не работала под clang. Причем, как мне представляется, прав в этой ситуации именно clang: ведь constexpr-функция может быть запущенна не только в compile-time, но и в run-time. И clang мог посчитать, что в каких-то контекстах операции, выполняющие обработку аргументов функции, не могут быть использованы.

Дабы избавиться от этой проблемы я нашел другой способ подсчета размерности результирующией строки. В результате код собирается компиляторами clang 3.5-3.8 и gcc 5.1-6.1 с ключиком -std=c++14. Поиграться с кодом можно здесь: https://godbolt.org/g/G5wdyD (так же полный код примера под катом).

Отдельно стоит сказать про выхлоп компилятора. Очевидно, что содержимое результирующей строки формируется в compile-time, иначе бы не работали проверки в static_assert. Но вот как это содержимое добавляется в код зависит от компилятора: clang явно размещает результирующую строку как строковый литерал и затем использует адрес этого литерала. А вот gcc использует серию команд movb для инициализации значения в run-time.

Задачка эта всплыла в большущем LOR-овском флейме. Еще один товарищ из этого обсуждения предложил несколько своих вариантов. Перечислены они здесь: https://www.linux.org.ru/forum/development/12649936?cid=12674932 (пояснительный текст с ссылками на godbolt) и здесь: https://www.linux.org.ru/forum/development/12649936?cid=12675192 (полные тексты). Данные решения используют либо ключи -std=c++1z (т.е. требуют фич из не утвержденного еще стандарта C++17), либо используют GNU-расширения языка. Последнее из решений, в котором используется GNU-тое расширение для формирование compile-time строковых литералов посредством суффикса _ct, дает для clang и GCC одинаковый выхлоп: инициализация строки в run-time через movl.

Какой-то практической значимости у этой форумной задачки не видно. Может быть, подобные фокусы смогут оказаться полезными, когда в программу нужно вшивать base64 представления каких-то строк. Или если в программе нужны строки для представления URL или имен MQ-шных топиков, склеенные из частей, которые известны на этапе компиляции. Но лично я бы в таких случаях предпочел бы pre-compile-time генерацию строковых литералов.

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

template< std::size_t ...L >
constexpr auto
ct_concat( const char (&...args)[L] )

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

Ну а теперь, собственно, то, что у меня получилось.