пятница, 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 вполне можно копипастить к себе и использовать для подобных целей, благо это шаблонные классы, предназначенные именно для этого :)

Отправить комментарий