SObjectizer  5.1
so_5: Обработка исключений в событиях агентов.

Обработка исключений в событиях агентов. so_5: Версия 5.1.0

Введение

В предыдущих версиях SObjectizer-а исключения
в обработчиках событий не обрабатывались.
Это мог делать сам пользователь внутри
метода-обработчика события:
void
A::evt( const event_data_t & data )
{
try
{
// обработка события.
}
catch( const std::exception & ex )
{
// Обработка исключения
}
}
Недостатками при таком подходе является:
- Накладные расходы на try-catch при выполнении обработчика.
- Увеличение повторяющегося кода по обработке исключений.
- При отсутствии блока try-catch в теле обработчика события, если
выбрасывалось исключение, то это приводило к некорректному
завершению работы программы.

В v.5.0.0 обработчики событий могут выбрасывать исключения,
с одним лишь требованием - выбрасываемое исключение должно быть
наследником std::exception. В SObjectizer предусмотрен механизм
позволяющий отлавливать исключения, в который можно встроить
собственный обработчик и журнализатор исключения.

Для этого вводится следующего понятия:
- \ref so_5__5_1_0__event_exception__logger - объект,
который выполняет действия необходимые для логирования
возникшего исключения;
- \ref so_5__5_1_0__event_exception__handler - объект,
который определяет, какой должна быть реакция
на возникшее исключение.

Журнализатор исключений

По умолчанию, стандартный журнализатор исключений печатает
сообщение с описанием ошибки, которое возвращает
\c std::exception::what() в \c std::cout.

Для определения своего журнализатора необходимо реализовать
интерфейс std_event_exception_logger_t.
См. также
so_5/rt/h/event_exception_logger.hpp
Метод std_event_exception_logger_t::log_exception() служит для выполнения
логирования и в него передается информация об исключении и кооперации агента,
событие которого выбросило исключение.
Метод so_5::rt::std_event_exception_logger_t::on_install() необходим для того,
чтобы в ситуации, когда ставится новый журнализатор,
объект, который станет новым журнализатором,
сам решил, что делать со старым журнализатором, умный указатель
на который передается ему при его установке.

Достаточно реализовать метод только
std_event_exception_logger_t::log_exception(),
метод std_event_exception_logger_t::on_install(), по умолчанию, удаляет
журнализатор-предшественник. Альтернативой может быть
выстраивание журнализаторов в цепочку, когда, например,
новый журнализатор забирает себе владение предшественником,
и в методе логирования информации об исключении,
вместе с выполнением собственного логирования, вызывает
и логирование предшественником.

Например, если необходимо добавить собственный слой
логирования исключений и при этом не затрагивать
правила логирования, которые были изначально,
то можно написать следующим образом:
class my_event_exception_logger_layer_t
:
{
public:
my_event_exception_logger_layer_t(
const std::string & log_file_name )
{
m_log_file_stream.open(
log_file_name.c_str(),
std::ios_base::trunc | std::ios_base::binary );
}
virtual ~my_event_exception_logger_layer_t()
{
m_log_file_stream.close();
}
virtual void
log_exception(
const std::exception & event_exception,
const std::string & coop_name )
{
m_log_file_stream << get_current_time()
<< "\n\t" "coop: " << coop_name
<< "\n\t" "exception: " << event_exception.what()
<< std::endl;
// Пусть исходный журнализатор также зафиксирует ошибку.
m_previous_logger->log_exception(
event_exception,
coop_name );
}
virtual void
on_install(
{
m_previous_logger = std::move( previous_logger );
}
private:
// Файл в который ведется логирование.
std::ofstream m_log_file_stream;
// Журнализатор предшественник.
m_previous_logger;
};
Рассмотрим другой пример. Пусть мы хотим отдельно фиксировать
возникновение исключений для разных коопераций. Тогда код
журнализатора может выглядеть так:
class specific_coop_event_exception_logger_t
:
{
public:
specific_coop_event_exception_logger_t(
const std::string & coop_name,
const std::string & log_file_name )
: m_target_coop_name( coop_name )
{
m_log_file_stream.open(
log_file_name.c_str(),
std::ios_base::trunc | std::ios_base::binary );
}
virtual ~specific_coop_event_exception_logger_t()
{
m_log_file_stream.close();
}
virtual void
log_exception(
const std::exception & event_exception,
const std::string & coop_name )
{
// Если это исключение в агенте интересующей нас кооперации,
// то логируем информацию об агенте.
if( coop_name == m_target_coop_name )
{
m_log_file_stream << get_current_time()
<< "\n\t" "exception: " << event_exception.what()
<< std::endl;
}
else
{
m_previous_logger->log_exception(
event_exception,
agent_name,
coop_name );
}
}
virtual void
on_install(
{
m_previous_logger = std::move( previous_logger );
}
private:
// Имя кооперации, исключения в агентах которой составляют интерес.
const std::string m_target_coop_name;
// Файл в который ведется логирование.
std::ofstream m_log_file_stream;
// Журнализатор предшественник.
m_previous_logger;
};
Для установки в SObjectizer нового журнализатора
исключений служит функция so_environment_t::install_exception_logger().
Если впоследствии необходимо поставить стандартный журнализатор исключений,
то его можно создать с помощью функции create_std_event_exception_logger().

Пример использования собственного журнализатора исключений
\ref so_5/exception_logger

Обработчик исключений

Для определения своего обработчика исключений необходимо
реализовать интерфейс event_exception_handler_t.
См. также
so_5/rt/h/event_exception_handler.hpp
При возникновении исключений может понадобиться
дерегистрировать кооперацию агента, который вызвал исключение,
инициировать завершение работы SObjectizer, отправить
каким-либо агентам сообщение и т.д. и т.п.
Получается, что при возникновении исключения может
понадобиться выполнить большое количество операций,
либо эти операции выполняются продолжительное время.
А т.к. обращения к обработчику исключений происходят
синхронно, то, чтобы не держать другие рабочие нити, на
которых тоже могут возникнуть исключения, на общем мутексе,
решено передать ответственность за выполнение реакции на
возникшее исключение отдельному объекту с интерфейсом
event_exception_response_action_t. Задача обработчика заключается в
создании подходящего объекта ответного действия,
которое инициализируется методом
event_exception_response_action_t::respond_to_exception().
Этот метод определяет, как надо
реагировать на возникшее исключение. Т.о.
event_exception_handler_t является фабрикой для
объектов-реакций на возникшее исключение, которая
в зависимости от типа возникшего исключения (через
dynamic_cast можно уточнять, не является ли исключение
исключением заданного класса) или/и от сообщения об ошибке,
агента конкретной кооперации, решает, какой должна быть реакция, после чего создает
объект соответствующий заданной реакции и возвращает
умный указатель на созданный объект.

Метод event_exception_handler_t::on_install(), по умолчанию удаляет
обработчик-предшественник. Альтернативный подход - выстраивание
обработчиков в цепочку, в которой обработчик обращается к
предшественнику в случае, если сам не может определить,
какой должна быть реакция на возникшее исключение при
имеющейся информации об исключении.

Рассмотрим пример. Пусть у нас есть несколько типов исключений,
и на каждое из них предусмотрена отдельная реакция. Будем
выстраивать их в цепочку. Каждый обработчик будет проверять
не является ли исключение исключением заданного типа,
и если является, то обработчик возвращает определенный для
данного исключения объект-ответчик:
class first_event_exception_handler_t
:
public event_exception_handler_t
{
public:
event_exception_handler_t();
virtual ~event_exception_handler_t();
handle_exception(
so_environment_t & so_environment,
const std::exception & event_exception,
const std::string & coop_name )
{
// Выясняем наше ли это исключение.
const my_first_exception * ex =
dynamic_cast< const my_first_exception * >(
&event_exception );
if( nullptr != my_first_exception )
{
new my_first_event_exception_response_t );
}
// Раз это исключение нам не знакомо, то
// пусть его обрабатывает предшественник.
return m_previous_handler->handle_exception(
so_environment,
event_exception,
coop_name );
}
virtual void
on_install(
// Предыдущий обработчик ошибок.
{
// Берем владение над обработчиком-предшественником.
m_previous_handler = previous_handler;
}
private:
// Обработчик предшественник.
m_previous_handler;
};

Аналогично поступаем и с обработчиками для других исключений:

second_event_exception_handler_t::handle_exception(
so_environment_t & so_environment,
const std::exception & event_exception,
const std::string & coop_name )
{
// Выясняем наше ли это исключение.
const my_second_exception * ex =
dynamic_cast< const my_second_exception * >(
&event_exception );
if( nullptr != my_second_exception )
{
new my_second_event_exception_response_t );
}
// Раз это исключение нам не знакомо, то
// пусть его обрабатывает предшественник.
return m_previous_handler->handle_exception(
so_environment,
event_exception,
coop_name );
}
// ...
n_th_event_exception_handler_t::handle_exception(
so_environment_t & so_environment,
const std::exception & event_exception,
const std::string & coop_name )
{
// Выясняем наше ли это исключение.
const my_n_th_exception * ex =
dynamic_cast< const my_n_th_exception * >(
&event_exception );
if( nullptr != my_n_th_exception )
{
new my_n_th_event_exception_response_t );
}
// Раз это исключение нам не знакомо, то
// пусть его обрабатывает предшественник.
return m_previous_handler->handle_exception(
so_environment,
event_exception,
coop_name );
}
Заметки
Необходимо, конечно, учитывать, как связаны типы исключений в их иерархии и порядок установки обработчиков исключений. Чтобы, например, обработчик, который проверяет, не является ли исключение типом base_exception_t, не проверял бы его раньше, чем обработчик, который проверяет, не является ли исключение типом derived_exception_t, который унаследован от base_exception_t. Т.е. действуют привила для последовательности типов в конструкциях catch.

Для установки в SObjectizer нового обработчика исключений служит функция so_environment_t::install_exception_handler(). Если впоследствии необходимо поставить стандартный обработчик исключений, то его можно создать с помощью функции create_std_event_exception_handler().

Пример использования собственного обработчика исключений sample/so_5/exception_handler/main.cpp

Внимание
Сами реализации журнализатора и обработчика событий не должны выбрасывать исключений, в противном случае программа некорректно завершит свою работу.
Обработка исключений и смена журнализатора или обработчика происходят синхронно. И в один момент времени может выполняться либо только обработка только одного исключения, либо только смена журнализатора, либо только смена обработчика. Поэтому при реализации интерфейса можно рассчитывать, что, например, метод event_exception_logger_t::log_exception() не будет вызываться параллельно с другим вызовом этого же метода. В тоже время методы event_exception_response_action_t::respond_to_exception() объектов-реакций могут работать параллельно на разных рабочих нитях.

Документация по SObjectizer v.5.1 'Джимара'. Последние изменения: Ср 15 Май 2013 12:56:21. Создано системой  doxygen1.8.3.1 Intervale SourceForge.net Logo