Наследование диспетчера дочерними агентами

Author: Евгений Охотников
Contact: eao197 at intervale dot ru; eao197 at yahoo dot com
Version: 1.0
Date: 2007.09.03

Оглавление

Предисловие

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

Мотивация

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

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

Где это может потребоваться? Например, агент, обрабатывающий серверный TCP/IP сокет, может создавать по одному дочернему агенту на каждое новое клиентское подключение. Если родительский агент не будет указывать диспетчер для своих дочерних агентов, то дочерние агенты будут обычными пассивными агентами. Т.е. они будут выполнять операции ввода-вывода на одной нити, причем делить эту нить им придется с остальными пассивными агентами приложения.

В случае же, если пользователь выделил агенту серверного сокета отдельный контекст (сделал его активным агентом или членом активной группы), дочерние агенты могли бы разделять с родителем его контекст. Т.е., если бы SObjectizer предоставлял возможность наследования диспетчера, то родительский агент при создании дочернего агента просто указывал бы дочернему агенту своего диспетчера.

Препятствия текущей версии SObjectizer

В текущей версии SObjectizer принадлежность агента конкретному диспетчеру задается с помощью trait-объекта. В момент регистрации агента в SObjectizer у всех trait-объектов агента вызывается метод init(). Как раз в методе init() соответствующий trait-объект уведомляет тот или иной диспетчер о принадлежности ему очередного агента.

Сам же агент не знает, какому диспетчеру он принадлежит. Более того, он не знает, какой именно trait-объект отвечает за привязывание к диспетчеру. И еще больше: агент не имеет возможности выполнить итерацию по своим trait-объектам.

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

Возможные способы реализации

Идентификация trait-объектов

Смысл в том, чтобы дать возможность агенту найти среди своих trait-объектов тот trait-объект, который отвечает за привязывания агента к диспетчеру. Если такой trait-объект найдер, то он назначается дочернему агенту.

Проблема этого наивного подхода в том, что он в случае диспетчера с активными агентами будет приводить к увеличению количества активных агентов. Это будет происходить, поскольку trait-объект заставляет диспетчер создавать новую нить для каждого нового агента. Соответственно, копирование trait-объекта дочернему агенту приведет к созданию новой нити диспетчера, но не к работе дочернего агента на уже существующей нити родительского агента.

Наследование trait-объектов

Суть идеи в том, чтобы в классе agent_traits_t завести отдельный метод inherit_to_agent. Этот метод будет отвечать за передачу указанному агенту того же самого свойства (если эта передача возможна в принципе). Т.е. родительский агент уже не ищет среди своих свойств какое-то одно, а просто просит все свойства передать дочернему агенту что-нибудь в наследство. Т.о. trait-объекты, отвечающие за привязку к диспетчеру, смогут попасть к дочернему агенту.

Этот подход хорош тем, что позволит дочерним агентам наследовать не только связанные с диспетчером trait-объекты, но и какие-нибудь другие свойства. С другой стороны, этот подход так же не решает проблемы наследования нити активного агента.

Интерфейс dispatcher_context_t и ссылка на него в агенте

Кроме интерфейса dispatcher_t в SObjectizer добавляется интерфейс dispatcher_context_t (или dispatcher_thread_t). Различия между ними в том, что dispatcher_t используется для управления всем диспетчером (т.е. всеми нитями, которые за ним скрываются), а dispatcher_context_t служит только для идентификации одной нити диспетчера.

А агенте заводится указатель на dispatcher_context_t и методы для получения и изменения этого указателя. Агент самостоятельно может установить dispatcher_context_t перед регистрацией в SObjectizer. Если агент не установил указатель на dispatcher_context_t до регистрации, то SObjectizer устанавливает его в процессе регистрации.

Смысл в том, что любой зарегистрированный агент должен иметь ненулевой указатель на dispatcher_context_t. Этот указатель будет использоваться SObjectizer-ом при диспетчеризации заявок для агента (т.е. заявки будут передаваться не в dispatcher_t, как сейчас, а напрямую в dispatcher_context_t). Как следствие, указатель на dispatcher_context_t не может быть заменен после регистрации агента.

При регистрации агента возможны три ситуации:

  1. У агента нет dispatcher_context_t и нет trait-объектов, которые бы привязывали агента к конкретному диспетчеру. Такой агент становится обычным пассивным агентом и SObjectizer назначает агенту dispatcher_context_t по умолчанию.
  2. У агента есть trait-объект, который отвечает за привязку агента к диспетчеру. В процессе привязки trait-объект устанавливает у агента указатель на dispatcher_context_t. При этом не важно, был ли у агента собственный dispatcher_context_t -- trait-объект все равно заменит его на свое значение (т.е. trait-объект имеет больший приоритет).
  3. У агента нет trait-объекта, но есть dispatcher_context_t. В этом случае SObjectizer считает, что агент сам позаботился о том, чтобы выбрать нужный ему контекст. Именно этот контекст и будет использоваться при диспетчеризации событий агента.

Благодоря такой схеме работы родительский агент при создании дочернего агента может назначить дочернему агенту значение собственного dispatcher_context_t. И когда дочерний агент будет зарегистрирован, он будет работать именно на этом контексте.

Альтернативные подходы

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

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

Однако, реализация prepare_child_agent_t выгодна только в случае, когда агент создается прикладным кодом. Если же агент серверного сокета подгружается в приложение через SoSysConf и настраивается в конфигурационном файле, то возможности гибкого конфигурирования оказываются несколько ограниченными. Хотя и здесь в конфигурационном файле может быть возможность выбора одной из нескольких предопределенных схем (пассивные дочерние агенты, дочерние агенты как одна активная группа, дочерние агенты в N активных группах, дочерние агенты в активных группах из не более чем M агентов в каждой).

Предварительное резюме

Подход на основе dispatcher_context_t мог бы облегчить создание библиотечных агентов, которые нуждаются в тайном создании вспомогательных дочерних агентов (т.е. агентов, о которых не нужно знать остальной части приложения). Поскольку в этом случае родительский агент может заставить дочених агентов использовать собственный контекст.

Интересным следствием данного подхода может стать снижение издержек при диспетчеризации заявок. Если сейчас ряд диспетчеров (с активными агентами и группами, например) вынуждены иметь несколько мутексов (общий для защиты метаинформации и по одному на каждой рабочей нити) и захватывать, как минимум, два из них при диспетчеризации заявки, то в схеме с dispatcher_context_t будет захватываться только один мутекс внутри dispatcher_context_t.

С другой стороны, проблему назначения контекста дочерним агентам можно решать и более гибко. Для этого родительский агент должен определить интерфейс, с помощью которого пользователь сможет воздействовать на дочерних агентов. Этот подход выглядит более мощным (т.к. пользователь может выбирать разные контексты для разных дочерних агентов). И, главное, он не требует модификации SObjectizer Run-Time.

Hosted by uCoz