вторник, 21 июля 2015 г.

[prog.c++11] Прогресс в разработке SObjectizer-5.6.0. Часть 3: пара не до конца понятных хотелок

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


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

Такая штука может быть полезна, например, вот в таком сценарии. Агент A нуждается в услугах агента HSM, производящего криптографические операции. Агент A отсылает агенту HSM документ, который должен быть зашифрован и подписан. Получившийся результат должен быть отослан агенту B.

Сейчас такой сценарий может быть реализован двумя способами:

  1. Посредством ответного сообщения от HSM агенту A. Получается следующая последовательность операций:
     A       HSM        B
    ---     -----      ---
     |------->|         |
     |        |         |
     |<-------|         |
     |        |         |
     |----------------->|
    
    В этом случае агент A, отправляя агенту HSM запрос, должен передать в запросе свой mbox для того, чтобы HSM после завершения операции мог отослать на этот mbox результат обработки запроса.
    Этот способ позволяет агенту A продолжать работу, пока HSM обрабатывает его запрос, что хорошо. Но контроль ошибок в этом случае сложнее. Например, если HSM проигнорировал по какой-то причине запрос агента A (или же HSM "сломался" при обработке запроса), то A ничего об этом не узнает. Единственный выход -- отслеживать внутри A таймауты операций и предпринимать какие-то действия (перепосылать запрос, например).
    Данный способ так же позволяет агентам A и HSM работать на одной рабочей нити.
  2. Посредством синхронного запроса к агенту HSM. В этом случае A делает синхронный запрос к HSM и ждет результата. Такой способ дает возможность агенту A узнать, что же произошло с его запросом: был ли он обработан или же возникла какая-то проблема (скажем, запрос был выброшен из-за overload control или же в процессе его обработки возникла ошибка). Но агент A не сможет работать, пока не получит ответ на свой запрос.
    Кроме того, агенты A и HSM должны будут работать на разных нитях.

В случае же с коллбэком можно осуществить такой сценарий работы:

 A       HSM     Callback     B
---     -----   ----------   ---
 |------->|                   |
 |        |                   |
 |        |                   |
 |        |-------->|         |
 |        |         |-------->|

Т.е. агент A отсылает агенту HSM сообщение с коллбэком. Когда обработка этого запроса завершается, то автоматически вызывается коллбэк, в котором делается пересылка результата агенту B. Выглядеть это может, например, таким образом:

// Отсылка запроса в агенте A.
void
A::some_event_handler()
{
   ...
   so_5::send_with_callback< msg_encrypt_and_sign >(
         // Коллбэк, который будет вызван после обработки запроса.
         [mbox_b]( encrypted_and_signed_doc && doc ) {
            // Результат работы HSM должен быть передан агенту B.
            so_5::send< msg_continue_processing >( mbox_b, std::move(doc) );
         },
         // Отсылаем сообщение агенту HSM.
         mbox_hsm,
         // С какими-то параметрами.
         ... );
   ...
}

// Обработка запроса в агенте HSM.
encrypted_and_signed_doc
HSM::evt_encrypt_and_sign( const msg_encrypt_and_sign & evt )
{
   ... // Какие-то ресурсно-затратные действия.
   return encrypted_and_signed_doc{ ... };
}

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

Думается, что метод send_with_callback мог бы оказаться полезным в разных ситуациях. Иногда бывает необходимо узнать, получил ли агент адресованное ему сообщение. Сейчас такого способа нет (ну кроме синхронного запроса). А вот с send_with_callback это решается запросто.

Однако, тут есть еще белые пятна, над устранениями которых еще предстоит поработать.

Например, как в коллбэк передавать информацию об ошибках обслуживания запроса?

Можно вот так:

// Отсылка запроса в агенте A.
void
A::some_event_handler()
{
   ...
   so_5::send_with_callback< msg_encrypt_and_sign >(
         // Коллбэк, который будет вызван после обработки запроса.
         [mbox_b]( so_5::msg_processing_result< encrypted_and_signed_doc > r ) {
            if( r )
               // Результат работы HSM должен быть передан агенту B.
               so_5::send< msg_continue_processing >( mbox_b, std::move(*r) );
            else
            {
               // Возникла какая-то ошибка, обрабатываем ее путем
               // перевыбрасывания исключения.
               try
               {
                  r.rethrow_error();
               }
               catchconst so_5::exception_t & ex )
               {
                  ...
               }
               catchconst std::exception & ex )
               {
                  ...
               }
            }
         },
         // Отсылаем сообщение агенту HSM.
         mbox_hsm,
         // С какими-то параметрами.
         ... );
   ...
}

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

Так же остается непонятным, как поступать с коллбэком в разных ненормальных случаях: когда у сообщения оказывается более одного получателя, когда сообщение выбрасывается механизмом overload control, когда у сообщения не оказывается получателя, когда получатель просто его игнорирует, когда коллбэк сам выпускает наружу исключение и т.д. Понятно, что что-то делать нужно. И что можно выписать все такие случаи и определить тип реакции на них. Но это еще нужно проделать, такая работа пока еще не была выполнена.


Вторая хотелка связана с тем, чтобы упростить пользователю SObjectizer-а реализацию идиомы collector+performer.

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

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

А посему возникает естественное желание предоставить пользователю готовые инструменты для упрощения реализации пар collector+performer. Но пока не понятно, каким образом это сделать.

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


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

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