Жесткая проверка доступности событий в состояниях агентов

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

Оглавление

Мотивация

Одной из ключевых особенностей SObjectizer является то, что если событие не разрешено к обработке в текущем состоянии агента, то это событие просто игнорируется и никаких действий не происходит. Например, пусть есть агент, который обладает двумя состояниями: st_closed и st_open. В состоянии st_closed агент обрабатывает события evt_open, evt_timeout, а остальные события игнорирует. В состоянии st_open обрабатываются события evt_disconnect, evt_read и evt_write, событие evt_timeout игнорируется, а события evt_open не должно быть в принципе. В SObjectizer 4 такой агент описывается следующим образом:

SOL4_CLASS_START( a_my_agent )
  SOL4_EVENT( evt_open )
  SOL4_EVENT( evt_disconnect )
  SOL4_EVENT( evt_timeout )
  SOL4_EVENT( evt_read )
  SOL4_EVENT( evt_write )

  SOL4_STATE_START( st_closed )
    SOL4_STATE_EVENT( evt_open )
    SOL4_STATE_EVENT( evt_timeout )
  SOL4_STATE_FINISH()

  SOL4_STATE_START( st_open )
    SOL4_STATE_EVENT( evt_disconnect )
    SOL4_STATE_EVENT( evt_read )
    SOL4_STATE_EVENT( evt_write )
  SOL4_STATE_FINISH()
SOL4_CLASS_FINISH()

Т.е. при описании состояния программист должен перечислить все разрешенные в данном состоянии события.

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

К сожалению, в подобном подходе к описанию состояний нельзя проконтролировать корректность сделанных пользователем описаний. А программисты имеют свойство ошибаться. Можно просто забыть описать событие в списке разрешенных событий состояния, что особенно плохо при сопровождении ранее написанного кем-то агента, когда агент расширяется новыми сообщениями, событиями или состояниями. Так же можно не сообразить, что какое-то событие может инициироваться в некотором состоянии агента.

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

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

Возможные подходы к организации жесткого контроля

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

Вариант 1: перечисление разрешенных и запрещенных событий

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

Например, для обсуждаемого здесь примера описание класса агента могло бы иметь вид:

agent_class :a_my_agent do |c|
  ...
  c.state :st_closed do |st|
    # Перечисление разрешенных событий.
    st.enabled :evt_open, :evt_timeout

    # Перечисление запрещенных событий.
    st.disabled :evt_disconnect, :evt_read, :evt_write
  end

  c.state :st_open do |st|
    st.enabled :evt_disconnect, :evt_read, :evt_write
    st.disabled :evt_open, :evt_timeout
  end
end

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

Вариант 2: на первом месте инциденты событий

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

Описание обсуждаемого здесь класса агента может выглядеть следующим образом:

agent_class :a_my_agent do |c|
  c.msg_slot :msg_connected
  c.msg_slot :msg_disconnected
  c.msg_slot :msg_timeout
  c.msg_slot :msg_write
  c.msg_slot :msg_read

  c.state :st_closed do |st|
    st.on :msg_connected => :evt_open
    st.on :msg_disconnected => UNEXPECTED
    st.on :msg_timeout => :evt_timeout
    st.on :msg_read => IGNORE
    st.on :msg_write => IGNORE
  end

  c.state :st_open do |st|
    st.on :msg_connected => UNEXPECTED
    st.on :msg_disconnected => :evt_disconnect
    st.on :msg_timeout => IGNORE
    st.on :msg_read => :evt_read
    st.on :msg_write => :evt_write
  end
end

Данное описание говорит о том, что:

  • агент заинтересован в получении пяти разных сообщений (msg_connected, msg_disconnected, msg_timeout, msg_write, msg_read);
  • в состоянии st_closed сообщение msg_connected должно приводить к срабатыванию события evt_open, а сообщения msg_timeout к событию evt_timeout. Сообщения msg_write и msg_read в этом состоянии просто игнорируются;
  • в состоянии st_open сообщение msg_disconnected должно приводить к срабатыванию события evt_disconnect, msg_read -- к событию evt_read, а msg_write -- к evt_write. Сообщение msg_timeout игнорируется;
  • сообщения msg_disconnected в состоянии st_closed и msg_connected в состоянии st_open вообще не должны отсылаться агенту.

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

Слоты сообщений

Описание:

agent_class :a_my_agent do |c|
  c.msg_slot :msg_connected
  ...
end

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

Понятие слота введено из-за того, что не все сообщения могут быть известны для агента в момент трансляции описания его класса. Например, подписка агента (т.е. имена сообщений-инцидентов) определяется непосредственно во время работы по каким-то алгоритмам или параметрам конфигурации. В этом случае слот должен быть определен во время работы агента. При этом агент определяет значение своего слота, а SObjectizer автоматически обеспечивает подписку заинтересованных в этом слоте событий. Т.е. в таком варианте программист должен определять слоты сообщений, а не подписывать события агента, как в текущей версии SObjectizer 4.

Если же значение слота сообщения известно еще до запуска агента (например, агент подписывается только на собственные сообщения), то этот факт можно отразить непосредственно в описании слота. Например:

agent_class :a_my_agent do |c|
  c.msg_slot :msg_connected, own_message
  c.msg_slot :msg_disconnected,
      own_message( :fields => { :reason => STRING } )
  ...
end

Такое описание говорит о том, что слот msg_connected будет связан с сообщением msg_connected, которым владеет сам агент типа a_my_agent. Сообщение msg_connected не имеет доступных для SOP полей. А вот сообщение msg_disconnected того же агента, связанное со слотом msg_disconnected, имеет строковое поле reason, которое доступно для SOP. Т.е. эти описания эквивалентны описаниями в SObjectizer 4:

SOL4_CLASS_START( a_my_agent )
  SOL4_MSG_START( msg_connected, a_my_agent::msg_connected )
  SOL4_MSG_FINISH()

  SOL4_MSG_START( msg_disconnected, a_my_agent::msg_disconnected )
    SOL4_MSG_FIELD( reason )
  SOL4_MSG_FINISH()
  ...
SOL4_CLASS_FINISH()

Примеры использования предложенных подходов для реального большого агента

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

Исходное описание агента в SObjectizer 4 (244 строки).

Описание агента с помощью первого варианта (318 строк).

Описание агента с помощью второго варианта (246 строк).

По мнению автора, описание с помощью второго варианта более информативно, чем два других описания, посколько оно позволяет легко определить:

Но при этом само описание по объему практически равно первоначальному описанию с помощью нотации SObjectizer 4.

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

По замыслу, необходимость строго перечисления состояний/сообщений в каждом из событий агента должно играть такую же роль, как контроль pattern matching в функциональных языках программирования со стороны компилятора: компилятор в состоянии указать программисту о том, что не все варианты сопоставления с образцом программист определил. А во время исполнения программы стандартная библиотека порождает исключение, если в pattern matching-выражении ни один из вариантов не сработал. В сумме эти два свойства pattern matching-а положительно сказываются на качестве программы.

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

Hosted by uCoz