Завершение рассказа о том, как движется разработка версии 5.6.0. На этот раз речь пойдет о двух вещах, которые хотелось бы реализовать, но пока не понятно каким образом. Да и нужно ли...
Первая вещь -- это возможность повесить на отсылаемое сообщение какой-нибудь коллбэк, который будет вызван автоматически сразу после завершения обработки сообщения.
Такая штука может быть полезна, например, вот в таком сценарии. Агент A нуждается в услугах агента HSM, производящего криптографические операции. Агент A отсылает агенту HSM документ, который должен быть зашифрован и подписан. Получившийся результат должен быть отослан агенту B.
Сейчас такой сценарий может быть реализован двумя способами:
- Посредством ответного сообщения от HSM агенту A. Получается следующая последовательность операций:
В этом случае агент A, отправляя агенту HSM запрос, должен передать в запросе свой mbox для того, чтобы HSM после завершения операции мог отослать на этот mbox результат обработки запроса.A HSM B --- ----- --- |------->| | | | | |<-------| | | | | |----------------->|
Этот способ позволяет агенту A продолжать работу, пока HSM обрабатывает его запрос, что хорошо. Но контроль ошибок в этом случае сложнее. Например, если HSM проигнорировал по какой-то причине запрос агента A (или же HSM "сломался" при обработке запроса), то A ничего об этом не узнает. Единственный выход -- отслеживать внутри A таймауты операций и предпринимать какие-то действия (перепосылать запрос, например).
Данный способ так же позволяет агентам A и HSM работать на одной рабочей нити. - Посредством синхронного запроса к агенту 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(); } catch( const so_5::exception_t & ex ) { ... } catch( const std::exception & ex ) { ... } } }, // Отсылаем сообщение агенту HSM. mbox_hsm, // С какими-то параметрами. ... ); ... } |
Может быть нужно поступать каким-то другим способом, скажем, требовать несколько коллбэков: один будет вызываться при успешной обработке сообщения, остальные при тех или иных ошибках.
Так же остается непонятным, как поступать с коллбэком в разных ненормальных случаях: когда у сообщения оказывается более одного получателя, когда сообщение выбрасывается механизмом overload control, когда у сообщения не оказывается получателя, когда получатель просто его игнорирует, когда коллбэк сам выпускает наружу исключение и т.д. Понятно, что что-то делать нужно. И что можно выписать все такие случаи и определить тип реакции на них. Но это еще нужно проделать, такая работа пока еще не была выполнена.
Вторая хотелка связана с тем, чтобы упростить пользователю SObjectizer-а реализацию идиомы collector+performer.
Ее суть в том, что для выполнения каких-то, обычно ресурсоемких, действий, создается пара агентов, работающих на разных контекстах. Первым является агент-collector, который очень шустро обрабатывает запросы и складирует их в своей внутренней очереди. Возможно отфутболивая повторные и/или лишние запросы. Вторым является агент-performer, который регулярно просит у агента-collector-а следующий запрос для обработки.
Со временем приходит понимание, что данная идиома оказывается одним из ключевых приемов для разработки приложений с использованием SObjectizer. Связка из collector-а и performer-а позволяет делать множество важных с прикладной точки зрения вещей: отсеивать дубликаты, выстраивать очереди в соответствии со значимыми для прикладной области приоритетами, отслеживать тайм-ауты и дедлайны, контролировать нагрузку и т.д.
А посему возникает естественное желание предоставить пользователю готовые инструменты для упрощения реализации пар collector+performer. Но пока не понятно, каким образом это сделать.
Поэтому далеко не факт, что в версии 5.6.0 появится что-нибудь из этой оперы. Но то, что думать в этом направлении нужно и делать какие-то первые шаги уже пора -- это представляется очевидным.
Все вышесказанное является текущим рабочим приближением, которое выглядит довольно осмысленно и реализуемо. Однако, в граните пока ничего не отлито. Поэтому, если у читателей возникнут замечения/предложения или вообще совершенно альтернативные варианты, то сейчас самое время их услышать. Потом будет поздно ;)
Комментариев нет:
Отправить комментарий