Переодически пользователи SObjectizer-а высказывали пожелания в внедрении в SObjectizer понятия "синхронного" события. Но до версии 4.2.7 ничего подобного в SObjectizer-е реализовано не было, т.к. до сих пор сложно представить, каким образом в рамках одного продукта поддержки обмена сообщениями совместить асинхронное и синхронное взаимодействие.
Тем не менее, чистая асинхронность привела к возникновению проблем в самом SObjectizer-е. Более точно, проблемы возникли в реализации такого важного механизма SObjectizer-а, как ретрансляция сообщений глобальных агентов во внешний мир. Ретрансляцией сообщений глобальных агентов занимается агент-коммуникатор, который автоматически регистрируется при старте SObjectizer Run-Time. При регистрации нового глобального агента агент-коммуникатор подписывается на все сообщения этого агента. После чего получает все сообщения глобального агента обычным для всех агентов образом.
Для того, чтобы агент-коммуникатор узнал о появлении глобального агента в so_4::api::make_global_agent() отсылается сообщение so_4::rt::msg_global_agent, на которое подписан агент-коммуникатор. Агент-коммуникатор обрабатывает это сообщение обычным образом. Именно здесь и возникли две проблемы:
Например, можно было внутри ядра SObjectizer-а, при регистрации глобального агента сразу подписать агента-коммуникатора на сообщения глобального агента. Но это бы связало ядро SObjectizer-а с конкретным механизмом ретрансляции сообщений глобальных агентов. Хотелось оставить существующий механизм расширения возможностей SObjectizer-а средствами самого SObjectizer-а, без внесения изменений в ядро.
Можно было бы реализовать понятие hook-ов для сообщений. Т.е. назначения процедур, которые должны были бы запускаться при попытке отослать конкретное сообщение. Причем запускались бы hook-и внутри so_4::api::send_msg() на контексте нити, отсылающей сообщение. Но такой способ можно было бы использовать как альтернативу стандартному способу диспетчеризации сообщений и событий. У разработчиков мог бы появиться соблазн при необходимости выполнения каких-либо синхронных действий создать hook для сообщения.
В результате появилась следующая точка зрения на роль SObjectizer-а: SObjectizer решает задачу распределения вычислительных ресурсов. А если так, то почему не потребовать у SObjectizer-а выделения вычислительного ресурса прямо на контексте нити, отсылающей сообщения? Если какой-то агент не хочет или не может работать на выделенной ему нити, то почему не дать этому агенту возможность захвата "чужих" ресурсов?
Так появилась идея insend-событий. Т.е. событий, для которых:
Как и для обычного события, для insend-события можно указать произвольное количество инцидентов и любой приоритет. Если сообщение приводит к возникновению нескольких insend-событий разных агентов, то события запускаются на обработку в порядке убывания приоритетов. Порядок запуска на обработку insend-событий с одинаковым приоритетом не определен.
Insend-событие можно сделать обычным событием, если переподписать его с помощью so_4::rt::agent_t::so_subscribe() (API-функции so_4::api::subscribe_event() или макроса SOL4_SUBSCR_EVENT_START()). Аналогично, обычное событие можно сделать insend-событием, если переподписать его с помощью so_4::rt::agent_t::so_subscribe_insend_event().
В случае же обычных событий необходимость синхронизации доступа к атрибутам агента зависит от используемого диспетчера. Существующие в версии 4.2.7 диспетчеры (с одной рабочей нитью, с активными агентами, с активными группами, диспетчеры главной нити для Windows и для Qt) гарантируют, что все normal-события агента последовательно запускаются на контексте одной и той же нити. Поэтому никакой синхронизации для normal-событий при использовании этих диспетчеров не нужно.
Конфликт возникает из-за того, что на момент диспетчирования заявок на запуск обработчиков события SObjectizer не может определить, какую заявку нужно ставить в очередь, а какую обрабатывать в рамках send_msg. Дело в том, что два события одного агента от одного инцидента обрабатываются, только если они допустимы к обработке в разных состояниях агента. Проверка же состояния производится непосредственно перед запуском события на обработку, а не при постановке заявки в очередь диспетчера. Поэтому в момент обработки заявок для normal- и insend-событий SObjectizer не имеет права проверять состояние агента и на основании состояния делать вывод о том, как поступать с каждой из заявок.
Обнаружив конфликт, SObjectizer выдает на стандартный поток ошибок сообщение о конфликте, например:
so_4\rt\impl\event_data_only_one_of.cpp:154: Insend and normal events conflict detected! Agent: a_receiver_1 Incident: a_receiver_1.msg_my
Самым ярким примером является уже описанный случай с обработкой сообщения so_4::rt::msg_global_agent. Благодоря insend-событиям агент-коммуникатор получает возможность подписаться на сообщения нового глобального агента еще до того, как регистратор глобального агента узнает, что регистрация завершилась успешно.
Еще одной возможной областью применения insend-событий являются ретрансляторы-маршрутизаторы сообщений. Например, есть агент серверного сокета, который рассылает сообщения о подключении/отключении клиентов и полученных от клиентов данных. Все эти сообщения слушает агент-маршрутизатор, который создает/уничтожает прикладных агентов для каждого соединения и пересылает прикладным агентам полученные от клиентов данные. Все события такого агента-маршрутизатора можно сделать insend-событиями (например, они могут запускаться на контексте активного агента серверного сокета). Такое решение может оказаться более эффективным, т.к. не будут расходоваться ресурсы на диспетчирование событий агента-маршрутизатора только для того, чтобы в этих событиях породить новые события, но уже для прикладных агентов.
Важной чертой insend-событий является то, что отправитель сообщения не знает, как и когда это сообщение будет обработано. Пожелания по выделению ресурсов на обработку события может высказать только агент, который обрабатывает сообщение.
Более того, вряд ли имеет смысл расчитывать на то, что insend-события будут и в следующих версиях запускаться на контектсе нити, на которой вызвана функция so_4::api::send_msg(). Это уже и сейчас не так: для отложенных и переодических сообщений insend-события запускаются так же, как и normal-события. Следует исходить только из того, что обработчик insend-события будет завершен до возврата из so_4::api::send_msg(). А как это будет сделано и на контексте какой нити будет запущен обработчик -- это уже задача SObjectizer-а и диспетчера.
Ниже рассматриваются два варианта неправильного использования insend-событий в попытке обеспечить синхронность.
class A : public so_4::rt::agent_t { public : ... // Сообщение, которое будет отослано агенту B. struct msg_process { // Сюда агент B должен поместить результат. int * m_result; msg_process() : m_result( 0 ) {} msg_process( int * result ) : m_result( result ) {} static bool check( const msg_process * cmd ) { return ( cmd && cmd->m_result ); } }; void evt_do_something() { // Отсылаем сообщение агенту B и ждем результата. int result; so_4::api::send_msg_safely( so_query_name(), "msg_process", new msg_process( &result ) ); // Обрабатываем результат. if( result ) ...; } }; class B : public so_4::rt::agent_t { public : virtual void so_on_subscription() { so_subscribe_insend_event( "evt_process", "A", "msg_process" ); } void evt_process( const A::msg_process & cmd ) { // Выполняем какую-то обработку. ... // Сообщаем результат. *( cmd.m_result ) = r; } };
Главная проблема здесь в том, что в агенте A подразумевается синхронная реакция агента B. Но явно в коде это нигде не отражено. Поэтому при сопровождении данного кода велика вероятность получения проблем в случае, если агент B будет переписан без использования insend-событий.
class A : public so_4::rt::agent_t { private : // Результат обработки в агенте B. int m_result; public : ... // Сообщение, которое будет отослано агенту B. struct msg_process { ... }; // Ответное сообщение, которое отсылается A. struct msg_process_result { ... }; virtual void so_on_subscription() { so_subscribe_insend_event( "evt_process_result", "msg_process_result" ); } void evt_do_something() { // Отсылаем сообщение агенту B и ждем результата. so_4::api::send_msg_safely( so_query_name(), "msg_process", new msg_process( &result ) ); // Обрабатываем результат. if( m_result ) ...; } void evt_process_result( const msg_process_result & cmd ) { m_result = cmd.m_result; } }; class B : public so_4::rt::agent_t { public : virtual void so_on_subscription() { so_subscribe_insend_event( "evt_process", "A", "msg_process" ); } void evt_process( const A::msg_process & cmd ) { // Выполняем какую-то обработку. ... // Сообщаем результат. so_4::api::send_msg_safely( "A", "msg_process_result", new A::msg_process_result( ... ) ); } };
Проблем здесь множество. Начиная от трудоемкости и неочевидности, и заканчивая тем, что агент A расчитан только на работу с одним агентом B. Например, что будет, если агент A вернется из send_msg и перед выполнением if( m_result )
его прервут, а в это время кто-то еще отошлет агенту A сообщение msg_process_result?
Поэтому попытки реализовать средствами SObjectizer-а какое-либо синхронное взаимодействие сейчас обречены на множество проблем. Как правило это:
Поэтому в завершении описания insend-событий хочется еще раз сформулировать основной вывод попыток внедрения синхронности в SObjectizer:
Документация по SObjectizer v.4.4 'Тебуломста'. Последние изменения: Thu Sep 18 10:26:48 2008. Создано системой 1.5.6 |