Поддержка исключений в SObjectizer

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

Оглавление

Мотивация

Одним из основных просчетов, допущенных при проектировании SObjectizer-4, является отсутствие поддержки исключений. К сожалению, я уже не помню точных причин принятия такого решения. Вероятно, сказалась сложность написания устойчивого к исключениям кода в C++. Как бы то ни было, исключения в SObjectizer-4 не поддерживаются и это ведет, как минимум, к двум негативным последствиям.

Во-первых, использующий API SObjectizer-а код может содержать ошибки, вызванные отсутствием проверок кодов возврата:

void
a_client_manager_t::evt_connect_attempt(
    const msg_client_description & cmd )
  {
    // ...код по проверке клиента и занесению клиента
    // в список подключенных клиентов...

    // Информирование о подключении нового клиента.
    so_4::api::send_msg_safely(
        so_query_name(),
        "msg_client_accepted",
        new msg_client_accepted( ... ) );
    // Запуск цикла проверки состояния клиента.
    so_4::api::send_msg_safely(
        so_query_name(),
        "msg_check_client_status",
        new msg_check_client_status( ... ),
        so_query_name(),
        check_status_timeout() );
  }

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

Во-вторых, SObjectizer требует, чтобы обработчики событий не выпускали исключений наружу. В принципе, это обоснованное требование -- если уж сам агент не знает, что делать с его прикладным исключением (а это именно прикладное исключение, т.к. SObjectizer не порождает исключений), то откуда SObjectizer будет об этом знать?

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

Но даже если писать обработчики событий в параноидальном стиле:

void
a_some_agent_t::evt_some_handler()
  {
    try
      {
        ...
      }
    catch( const std::exception & x )
      {
        // Обработка всех исключений, производных от стандартного корня.
        ...
      }
  }

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

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

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

Возможность добавления в SObjectizer поддержки исключений

Добавление поддержки исключений в SObjectizer можно разбить на два этапа. На первом этапе, который можно реализовать в рамках SObjectizer-4, добавляется обработка выпущенных из обработчиков событий исключений. На втором этапе, который невозможен в рамках SObjectizer-4, происходит переход от использования so_4::ret_code_t в API SObjectizer к порождению исключений при возникновении ошибок.

Далее расматривается предполагаемый способ внедрения поддержки исключений в SObjectizer-4.

Реакция на вышедшие за пределы обработчиков событий исключения

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

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

При этом агент внутри SObjectizer-а помечается специальным образом, чтобы в него не попало больше ни одного события (возможно, агент будет переводится в специальное состояние, унаследованное из класса so_4::rt::agent_t).

Однако, дерегистрации проблемной кооперации недостаточно. Нужно дать возможность приложению восстановиться после подобного сбоя. Сейчас при дерегистрации кооперации SObjectizer рассылает сообщение msg_coop_deregistered. Это сообщение будет расширено информацией о причине дерегистрации кооперации.

Сейчас с обработкой msg_coop_deregistered связано серьезное неудобство: сообщение рассылается широковещательно, поэтому если кто-то заинтересован в получении уведомления о дерегистрации конкретной кооперации, то он вынужден будет получать все сообщения msg_coop_deregistered и выбирать из них нужное сравнивая имена коопераций. Для того, чтобы устранить это неудобство в SObjectizer добавится возможность связать с кооперацией имена агентов, которым будет отсылаться копия msg_coop_deregistered, но не широковещательно, а целенаправлено, под именем msg_coop_deregistration_notify.

Уведомления о дерегистрации коопераций можно будет использовать по аналогии с уведомлениями о завершении процессов в Erlang. В Erlang два процесса могут установить друг с другом связь. Обычно это связь между родительским (порождающим) и доченим (рабочим) процессом. Если дочерний процесс завершается, то родительский процесс получает уведомлений и может решить, что делать дальше -- перезапустить дочерний процесс еще раз или же завершится самому.

Предполагается, что аналогичным образом будут использоваться msg_coop_deregistration в SObjectizer: родительская кооперация порождает дочернюю кооперацию для выполнения каких-то действий. Если дочерняя кооперация дерегистрируется из-за ошибки (выпущенного наружу исключения), то родительская кооперация решает имеет ли смысл создать дочернюю кооперацию заново или дерегистрироваться самой.

Корневые кооперации

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

Для такой кооперации необходимо определить, что делать SObjectizer в случае поимки необработанного исключения. Если говорить точнее, то SObjectizer должен завершать свою работу, но вопрос в том, что сделать это можно несколькими способами: с помощью abort(), через сообщения msg_normal_shutdown или msg_alarm_shutdown, через механизм завершения SoSysConf или же каким-то другим способом. Поэтому при регистрации корневой кооперации можно будет указать SObjectizer-у объект-обработчик аварийной дерегистрации. Если такой обработчик не указан, то SObjectizer завершает свою работу через msg_alarm_shutdown.

Адаптация SoSysConf к сообщению msg_coop_deregistration_notify

В случае использования подсистемы SoSysConf приложение часто строится на основе утилит so_sysconf.process и so_sysconf.ntservice. В этих случаях корневыми и родительскими для всех коопераций будут кооперации подсистемы SoSysConf. Именно они будут принимать решения о перерегистрации аварийных коопераций или же об останове всего приложения.

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

Hosted by uCoz