Контроль перегрузок

9 views
Skip to first unread message

Dmitriy V'jukov

unread,
Feb 10, 2009, 11:02:36 AM2/10/09
to SObjectizer
/*
Вот набросал семантику и интерфейс для контроля перегрузок. Постарался
их сделать
максимально гибкими (в разумных пределах), но в то же время
предоставить и максимально
простой интерфейс для общих случаев.
Интересны комментарии как по предоставляемой семантике - насколько
полная,
так и по конкретному интерфейсу. Особенно интересна проверка на
соответствие разнообразным
юз-кейсам.

Вот базовый, декларативный интерфейс для общих случаев.
*/

// basic (declarative) interface

enum overload_mode
{
overload_mode_disabled, // load is not tracked
overload_mode_suspend, // suspend agent that caused overload
on my queue
overload_mode_drop, // drop message that caused overload
on my queue
overload_mode_redirect_to, // redirect message that caused
overload on my queue
};

enum overload_option
{
overload_option_dont_suspend, // don't suspend me if I caused
overload
overload_option_dont_drop, // don't drop my messages if I
caused overload
};

enum prio
{
prio_low,
prio_normal,
prio_high,
};

struct msg
{
};

// set global defaults
void set_overload_mode(overload_mode m);
void set_queue_watermarks(size_t high, size_t low);

class agent
{
protected:
// overrides global settings for the particular agent
void set_option(overload_option op);
void set_overload_mode(overload_mode m, agent* redirect_target =
0);
void set_queue_watermarks(size_t high, size_t low);
public:
bool is_overloaded(); // must be thread-safe, can be called from
other agents
};

/*
Вот продвинутый императивный интерфейс для более сложных случаев.
Фактически,
базовый интерфейс - это не более, чем дефолтная реализация
продвинутого интерфейса.
*/

// advanced (imperative) interface

class agent2 : public agent
{
protected:
virtual bool is_overloaded(); // overridable by user
size_t get_queue_length(); // can be used in the implementation of
is_overloaded() function
virtual void on_send_hook(agent* sender, msg* m, prio p); //
overridable by user, called synchronously on send
// following functions can be called from on_send_hook()
void do_enqueue(agent* sender, msg* m, prio p); // enqueues msg
regardless of load
bool drop_msg(agent* sender, msg* m); // discards the msg, and
notifies the sender, returns 'false' if sender cancels drop
void notify_about_overload(agent* sender); // notifies the sender,
so that it can suspend, change state or whatever
//------------------------
virtual void on_overload_notification(bool overload, agent*
target); // overridable by user
// following functions can be called from on_overload_notification
()
void do_suspend(bool suspend);
//------------------------
virtual void on_drop_msg(agent* target, msg* m); // overridable by
user
};

/*
Я думаю, что из интерфейсов понятна и семантика. Реализация базового
интерфейса через
продвинутый достаточна тривиальна и прямолинейна (приведена ниже).
По поводу реализации самого продвинутого интерфейса надо отметить, что
при его реализации
не используется обычная передача сообщений - иначе идея сводится на
нет.
Ключевая функция on_send_hook() вызывается синхронно при отправке
сообщения.
Воздействие на получателя тут только одно - do_enqueue() - это обычное
добавление
сообщения в очередь. Воздействий на отправителя 2 - нотификация о
вызове перегрузки и нотификация
об отмене посылки сообщения - т.к. on_send_hook() вызывается на
контексте отправителя,
то мы просто откладываем обработку этих нотификаций до завершения
обработки текущего сообщения.
При завершении обработки сообщения, смотрим были ли какие-то
отложенные нотификации
и вызываем их. Никакой синхронизации и асинхронности тут нет (хотя с
т.з. отправителя
это выглядит асинхронно).
Ещё интересные реакции на перегрузку со стороны отправителя, которые
не видны в интерфейсе, -
это смена своего состояния или выбор другого агента в качестве
получателя последующих сообшений.
Что бы это реализовать надо перегрузить on_overload_notification() и
выполнить соотв. действие.
Опции overload_option_dont_suspend и overload_option_dont_drop могут
использоваться
для агентов с особыми требованиями, например - консоль управления.

Вот схематичная реализация базового интерфейса:
*/

class agent3 : public agent2
{
private:
// в конструкторе устанавливаются в глобальные значения по-
умолчанию
size_t high_watermark_;
size_t low_watermark_;
overload_option overload_options_;
overload_mode overload_mode_;
// необходимо для гистерезиса
bool overload_;
agent* redirect_target_;

protected:
virtual bool is_overloaded()
{
return overload_;
}

protected:
void on_queue_size_changed()
{
// обновляем состояние перегрузки
if (overload_ && get_queue_length() < low_watermark_)
{
overload_ = false;
notify_all_suspended_agents();
}
else if (overload_ == false && get_queue_length() >
high_watermark_)
{
overload_ = true;
}
}

virtual void on_send_hook(agent* sender, msg* m, prio p)
{
if (is_overloaded())
{
if (overload_mode_ == overload_mode_suspend)
{
notify_about_overload(sender);
do_enqueue(sender, m, p);
return;
}
else if (overload_mode_ == overload_mode_drop)
{
if (drop_msg(sender, m))
return;
}
else if (overload_mode_ == overload_mode_redirect_to)
{
redirect_target_->send(m, p);
return;
}
}
do_enqueue(sender, m, p);
}

virtual void on_overload_notification(bool overload, agent*
target)
{
if (overload_options_ & overload_option_dont_suspend)
do_suspend(overload);
}

virtual void on_drop_msg(agent* target, msg* m)
{
// nothing by default
}

bool drop_msg(agent* target, msg* m)
{
if (target->overload_options_ & overload_option_dont_drop)
return false;
target->on_drop_msg(this, m);
}

void notify_about_overload(agent* sender)
{
sender->on_overload_notification(true, this);
}
};

/*
Определение перегрузки происходит по принципу "кто последний, тот и
папа", т.е.
как только агент засекается за попыткой положить сообщение в
переполненную очередь,
он приостанавливается (ну либо какая-то другая реакция). Можно
попробовать улучшить
это какими-то эвристиками типа того, что приостанавливать агента после
того,
как он засекается за попыткой положить сообщение в переполненную
очередь
несколько раз за короткий промежуток времени (что бы отделить агентов,
которые реально создают
перегрузку, от агентов, которые посылают сообщения эпизодически).

Однако с таким автоматическим контролем самая серьёзная проблема - это
возможные
индуцированные дедлоки; когда несколько агентов соединены в "кольцо",
и мы приостановим их всех.
Фактически получится, что эти агенты будут выведены из работы системы,
хотя вся остальная часть
системы будет выглядеть работающей. Самое надёжное решение, которое
видится пока, -
периодически проверять граф приостановленных агентов (мы его можем
легко получить,
т.к. при приостановке мы знаем обоих агентов - отправителя и
получателя) на предмет наличия циклов.
Агентов в цикле необходимо разблокировать, и возможно помечать их, что
бы они не приостанавливать
в последствие (это всё равно будет бесполезно).

Последнее - глобальное умолчание устанавливается как
overload_mode_suspend, и high/low watermarks
в какие-то разумные значения, типа 2000 и 1000.
*/

Yauheni Akhotnikau

unread,
Feb 11, 2009, 1:47:36 AM2/11/09
to sobje...@googlegroups.com
Доброго дня!

Дима, а зачем иметь overload_mode и overload_option? И зачем позволять
агенту запрещать операции suspend и dont_drop?

--
Regards,
Yauheni Akhotnikau
Chief Developer
Intervale, http://www.intervale.ru
e-mail:eao...@intervale.ru <mailto:eao...@intervale.ru>

Dmitriy V'jukov

unread,
Feb 11, 2009, 3:12:05 AM2/11/09
to SObjectizer
On 11 фев, 09:47, "Yauheni Akhotnikau" <eao...@intervale.ru> wrote:

> Дима, а зачем иметь overload_mode и overload_option? И зачем позволять  
> агенту запрещать операции suspend и dont_drop?

overload_mode - определяет реакцию агента на его перегрузку. А
overload_option - позволяет агенту повлиять на то, что с ним сделают,
если он вызывает перегрузку.
С overload_option_dont_suspend/overload_option_dont_drop идея такая,
что допустим есть агент консоль-управления для администратора, она
иногда посылает единичные сообщения какому-то центральному агенту
бизнес-логики, который может быть в данный момент перегружен. Будет не
приятно, если команда администратора будет дропнута или консоль
подвиснет. Поэтому для агента-консоли логично установить эти опции. Ну
либо там... не знаю... допустим "исчезновение" сообщения агент
переживать умеет, но это "дорогостоящая" операция, поэтому не хотим
хотя бы сами их drop'апть.
В перегрузке присутствует 2 агент - получатель и отправитель, соотв.
идея, что в пределе они оба должны мочь влиять на действия,
предпринимаемые при перегрузке.


--
Дмитрий Вьюков

Yauheni Akhotnikau

unread,
Feb 11, 2009, 3:20:02 AM2/11/09
to sobje...@googlegroups.com
Понятно. Нужно будет еще на эту тему подумать. Ведь, в принципе, можно
безболезнено выбрасывать одни сообщения (например, какие-нибудь
периодические query_state), а другие выбрасывать ни в коем случае нельзя
(например, управляющее switch_mode или shutdown).

Еще одно соображение: думаю, было бы лучше и гибче, если бы управление
overload-ом выполнялось не с помощью элементов перечислений, а более
объектно-ориентированно. Например, есть какой-нибудь интерфейс
overload_controller_t. От него сделаны наследники: suspender_controller,
msg_drop_controller, another_receiver_controller. Тогда можно будет делать
собственные реализации overload_controller-а в соответствии с логикой
приложения.

On Wed, 11 Feb 2009 11:12:05 +0300, Dmitriy V'jukov <dvy...@gmail.com>
wrote:

--

Dmitriy V'jukov

unread,
Feb 11, 2009, 6:39:09 AM2/11/09
to SObjectizer
On 11 фев, 11:20, "Yauheni Akhotnikau" <eao...@intervale.ru> wrote:
> Понятно. Нужно будет еще на эту тему подумать. Ведь, в принципе, можно  
> безболезнено выбрасывать одни сообщения (например, какие-нибудь  
> периодические query_state), а другие выбрасывать ни в коем случае нельзя  
> (например, управляющее switch_mode или shutdown).

Такой юз-кейс я рассматривал. Если сам получатель знает об этих типах,
то это реализуется след. образом:

virtual void on_send_hook(agent* sender, msg* m, prio p)
{

if (is_overloaded() && (m->type != switch_mode::type && m->type !=
shutdown::type))
{
if (drop_msg(sender, m))
return;
}
do_enqueue(sender, m, p);
}

В противном случае - на стороне отправителя:

bool on_drop_msg(agent* target, msg* m)
{
return (m->type != switch_mode::type && m->type != shutdown::type);
}

Кстати, это говорит о том, что проверку overload_option надо перенести
из drop_msg() в on_drop_msg(), и соотв. что бы on_drop_msg()
возвращала bool, а не void. А drop_msg(), тогда наверное можно вообще
убрать.

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

srtuct shutdown_msg : lib::msg<shutdown_msg>
{
static lib::overload_option const overload_ctl =
lib::overload_option_dont_drop;
...
};

Yauheni Akhotnikau

unread,
Feb 11, 2009, 6:47:42 AM2/11/09
to sobje...@googlegroups.com
On Wed, 11 Feb 2009 14:39:09 +0300, Dmitriy V'jukov <dvy...@gmail.com>
wrote:

> On 11 фев, 11:20, "Yauheni Akhotnikau" <eao...@intervale.ru> wrote:


>> Понятно. Нужно будет еще на эту тему подумать. Ведь, в принципе, можно  
>> безболезнено выбрасывать одни сообщения (например, какие-нибудь  
>> периодические query_state), а другие выбрасывать ни в коем случае
>> нельзя  
>> (например, управляющее switch_mode или shutdown).
>
> Такой юз-кейс я рассматривал. Если сам получатель знает об этих типах,
> то это реализуется след. образом:
>
> virtual void on_send_hook(agent* sender, msg* m, prio p)
> {
> if (is_overloaded() && (m->type != switch_mode::type && m->type !=
> shutdown::type))
> {
> if (drop_msg(sender, m))
> return;
> }
> do_enqueue(sender, m, p);
> }

Как-то это сложно выглядит.

> И ещё возникла мысль, что можно добавить опции для контроля перегрузки
> в описание сообщения, например:
>
> srtuct shutdown_msg : lib::msg<shutdown_msg>
> {
> static lib::overload_option const overload_ctl =
> lib::overload_option_dont_drop;
> ...
> };

Вот это уже гораздо симпатичнее.

Yauheni Akhotnikau

unread,
Feb 11, 2009, 6:55:33 AM2/11/09
to sobje...@googlegroups.com
On Tue, 10 Feb 2009 19:02:36 +0300, Dmitriy V'jukov <dvy...@gmail.com>
wrote:

> Интересны комментарии как по предоставляемой семантике - насколько


> полная,
> так и по конкретному интерфейсу. Особенно интересна проверка на
> соответствие разнообразным
> юз-кейсам.

В качестве дополнительной информации. В рамках подготовки этих тезисов --
http://eao197.narod.ru/sobjectizer/first_look_at_so5.html -- мы внутри
компании провели небольшой семинар с тем, чтобы оценить актуальность
озвученных тезисов. Вот, что я зафиксировал на этом семинаре по поводу
контроля перегрузки приложения:

===
Из основных моментов обсуждения можно отметить:
- если уменьшение нагрузки на приложении построено на выбрасывании
"лишних" сообщений, то нужно уметь указывать те типы сообщений, которые
можно выбрасывать и те, выбрасывание которых недопустимо. Политика
выбрасывания должна определяться как подписчиком, так и отправителем
сообщения;
- Боря Сивко привел пример из одного нашего приложения, когда степень
загруженности определяется количеством агентов, а не сообщений;
- Боря Сивко привел пример из того же приложения, когда выбрасывание
определенных типов сообщений более предпочтительно, чем других (например,
сообщения check можно выбрасывать смело, т.к. транзакция еще не начата,
тогда как сообщения pay лучше доводить до обработки, чтобы не плодить
незавершенные транзакции, у которых стадия check была пройдена);
- SObjectizer должен предоставлять разработчикам возможности написания
собственных контроллеров загрузки приложенния с широкими возможностями
(например, с просмотром очереди сообщений конкретного агента и
выбрасывания произвольных сообщений из этой очереди).
===

Dmitriy V'jukov

unread,
Feb 11, 2009, 7:11:12 AM2/11/09
to SObjectizer
On 11 фев, 11:20, "Yauheni Akhotnikau" <eao...@intervale.ru> wrote:

> Еще одно соображение: думаю, было бы лучше и гибче, если бы управление  
> overload-ом выполнялось не с помощью элементов перечислений, а более  
> объектно-ориентированно. Например, есть какой-нибудь интерфейс  
> overload_controller_t. От него сделаны наследники: suspender_controller,  
> msg_drop_controller, another_receiver_controller. Тогда можно будет делать  
> собственные реализации overload_controller-а в соответствии с логикой  
> приложения.

Толи у меня отходняк от ООП, толи негативно сказывается поддержка
сервера на С :)
Плюсы от отдельного класса будут, если:
1. Будет существенное переиспользование этих классов
2. Получаем выгоду от разделения кода/логики

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

Например:

class my_agent
{
...


void on_overload_notification(bool overload, agent* target)

{
set_state(overload ? state_overload : state_normal);
}
...
};

Против:

template<typename state_t>
struct agent_callback
{
virtual void set_state(state_t s) = 0;
};

template<typename state_t>
class state_change_overload_controller : public overload_controller
{
public:
state_change_overload_controller(state_t norm, state_t overload,
agent_callback<state_t>* host)
: norm_(norm)
, overload_(overload)
, host_(host)
{}

private:
state_t norm_;
state_t overload_;
agent_callback<state_t>* host_;

virtual void on_overload(agent* target, bool overload)
{
host_->change_state(overload ? overload_ : norm_);
}
};

class my_agent : public agent, agent_callback<my_state_state>
{
pubic:
my_agent()
{
set_overload_controller(new state_change_overload_controller
(state_normal, state_overload, this));
}

private:
virtual void set_state(state_t s)
{
change_state(s);
}
};

Я честно говоря устал это писать, но для примера не поленился :)

Тем более, что при необходимости у пользователся остаётся возможность
вынести логику в отдельный объект и делегировать управление ему. А вот
наоборот уже не получится.
Плюс, если будет какой-то действительно часто используемый обработчик,
то можно сделать его поддержку прямо в библиотеке.
Плюс я всё ещё рассчитываю на шаблонные интерфейсы, которые позволяют
встраивать большинство "небольших" функций, а не делать виртуальные
вызовы (а "пустые" функции будут вообще удалены из кода). Что-то типа:

class my_agent : public lib::agent<my_agent>
{
// функция встраиваемая, соотв. оверхед на контроль перегрузки
устранен полностью


void on_send_hook(agent* sender, msg* m, prio p)

{
do_enqueue(sender, m, p);
}
};

В пределе можно даже:

class my_agent : public lib::agent<my_agent>
{
static bool const lib_disable_overload_ctl = true;
...
};

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


Yauheni Akhotnikau

unread,
Feb 11, 2009, 7:29:50 AM2/11/09
to sobje...@googlegroups.com
On Wed, 11 Feb 2009 15:11:12 +0300, Dmitriy V'jukov <dvy...@gmail.com>
wrote:

> On 11 фев, 11:20, "Yauheni Akhotnikau" <eao...@intervale.ru> wrote:


>
>> Еще одно соображение: думаю, было бы лучше и гибче, если бы управление  
>> overload-ом выполнялось не с помощью элементов перечислений, а более  
>> объектно-ориентированно. Например, есть какой-нибудь интерфейс  
>> overload_controller_t. От него сделаны наследники:
>> suspender_controller,  
>> msg_drop_controller, another_receiver_controller. Тогда можно будет
>> делать  
>> собственные реализации overload_controller-а в соответствии с логикой  
>> приложения.
>
> Толи у меня отходняк от ООП, толи негативно сказывается поддержка
> сервера на С :)
> Плюсы от отдельного класса будут, если:
> 1. Будет существенное переиспользование этих классов
> 2. Получаем выгоду от разделения кода/логики

И еще:

3. Заменять политику управления перегрузкой можно будет для агентов,
которых разрабатывал кто-то другой. Как сейчас выполняется привязка
агентов к различным типам диспетчеров (через свойства агента), так же
можно будет управлять их нагрузкой: в одном приложении агент a_channel
будет иметь одну политику, а во втором -- другу. И изменять исходный код
a_channel или наследоваться от него не потребуется.

> Я честно говоря устал это писать, но для примера не поленился :)

Я честно говоря, устал это читать и не понял, зачем такие сложности? ;)

Dmitriy V'jukov

unread,
Feb 11, 2009, 7:35:06 AM2/11/09
to SObjectizer
On 11 фев, 15:29, "Yauheni Akhotnikau" <eao...@intervale.ru> wrote:

> > Я честно говоря устал это писать, но для примера не поленился :)
>
> Я честно говоря, устал это читать и не понял, зачем такие сложности? ;)

А как ты себе это видишь? Учитывая, что если мы спускаемся до
императивного кода, значит у нас какой-то нетривиальный случай,
который скорее всего требует обратной связи от контроллера к агенту
как для получения каких-то параметров/данных из агента, так и для
обратного воздействия на агента (изменить состояние, изменить агента-
получателя сообщений).

Yauheni Akhotnikau

unread,
Feb 11, 2009, 10:00:43 AM2/11/09
to sobje...@googlegroups.com
On Wed, 11 Feb 2009 15:35:06 +0300, Dmitriy V'jukov <dvy...@gmail.com>
wrote:

> On 11 фев, 15:29, "Yauheni Akhotnikau" <eao...@intervale.ru> wrote:

Ну почему же нетривиальный? Обычный случай.
Пусть есть интерфейс:

class overload_controller_t
{
public :
virtual void
handle_overload(
const dispatching_context_t & ctx ) = 0;
};

Где через dispatching_context_t можно получить доступ к агенту-получателю
(или какому-то дескриптору этого агента, диспетчируемому сообщению,
очереди сообщений, контексту отправителя).

А дальше делается:

class drop_message_controller_t : public overload_controller_t
{
public :
virtual void
handle_overload(
const dispatching_context_t & ctx )
{
ctx.message_handler().drop_message();
}
};

class another_receiver_controller_t : public overload_controller_t
{
public :
virtual void
handle_overload(
const dispatcher_context_t & ctx )
{
ctx.change_receiver( some_another_agent );
}
};

И какой-то кастомный controller, который пытается выбросить из очереди
сообщений старые сообщения того же типа, что и новое (а если не
получается, то выбрасывает его):

class my_custom_controller_t : public drop_message_controller_t
{
public :
virtual void
handle_overload( ... )
{
... цикл по очереди сообщений...
if( <есть свободное место> )
ctx.try_again();
else
base_type_t::handle_overload( ctx );
}
};

Фозможно, при функциональном подходе, здесь можно было использовать
функции-обработчики. Но для этого язык реализации нужно будет сменить на
C#/Scala/F#/OCaml или Haskell :)))

Dmitriy V'jukov

unread,
Feb 12, 2009, 12:52:17 PM2/12/09
to SObjectizer
On 11 фев, 18:00, "Yauheni Akhotnikau" <eao...@intervale.ru> wrote:

> Ну почему же нетривиальный? Обычный случай.
>

> class my_custom_controller_t : public drop_message_controller_t
> {
> public :
>    virtual void
>    handle_overload( ... )
>    {
>      ... цикл по очереди сообщений...
>      if( <есть свободное место> )
>        ctx.try_again();
>      else
>        base_type_t::handle_overload( ctx );
>    }
>
> };


Как я себе вижу картину, и почему я склоняюсь к функциям внутри
агента, а не к объектам overload_controller_t.
1 Большинству агентов, допустим 80%, вообще не нужна
специализированная обработка перегрузок - им достаточно глобальной
политики по-умолчанию (блокирование отправителя на время перегрузки).
2. 80% из оставшихся агентов достаточно простой декларативной
подстройки обработки - изменить high/low watermarks, указать, что
сообщения можно выбрасывать и т.д.
3. 80% из оставшися агентов нужна специализированная обработка, при
которой не обойтись без написания своего кода. При этом эти агнеты не
используются в нескольких проектах.
4. И лишь 20% из пункта 3 используются в нескольких проектах и им
нужна разная обработка перегрузок.

И использование отдельно задаваемых overload_controller_t приносит
выгоду только для тех 0.4% агентов. В остальных же случаях - это
существенное раздувание кода и неудобство. А во многих случаях это не
принесёт выгодны и тем 0.4% агентов, т.к. (мой тезис - требует
проверки) агент и его обработчик перегрузки на самом деле не 2
независимых и хорошо разделяемых сущности, а одна тесно-связанная
сущность. И попытка разорвать её на 2 части будет либо очень кривая,
либо будет невозможна. Твой пример с my_custom_controller_t сильно
упрощён, т.к. обработчик не "спрашивает" у агента никаких
специфических для него данных и не производит на него никакого
специфического воздействия. Допустим, обработчику требуется спросить у
агента его состояние или какие-то флаги, или ещё какие-то данные;
допустим обработчику надо изменить состояние агента или установить у
него какой-то флаг или менить в агенте получателя отправляемых
сообшений. В лучшем случае это потребует кривизны, как я приводил в
"длинном" примере; в худшем случае это будет невозможно - как ты
можешь "просто задать" агенту при создании контроллер, если этому
контроллеру надо меня флаг в агенте, а этом флаг приватный для агента;
или ещё хуже - в агенте ещё нет этого флага.
В общем случае, без поддержки со стороны агента нельзя задать ему
произвольный контроллер. А уж коли нам всё равно менять агента, мы
проще прям в нём пару строчек и допишем. А если агент используется в
нескольких проектах и там нужны различные стратегии, то мы можем
добавить в конструктор агенту параметр для параметризации поведения. И
только если агент используется во множестве проектов и там нужны
сильно разные стратегии (имхо - очень мало вероятно), то мы можем
*руками* написать интерфейс overload_controller_t и делегировать ему
обработку; но при этом (важный момент) мы будем передавать в этот
интерфейс всю необходимую специфическую информацию и давать средства
для специфического обратного воздействия на агента (чего не будет в
библиотечном overload_controller_t).
Итого - оба варианта функционально полны. Вопрос сводится только к
"сделать просто для простых частых случаев и сложно для сложных" или
"сделать сложно для простых частых случаев и просто для сложных". Ну
впрочем для сложных случаев просто всё равно не будет, поэтому -
"сделать просто для простых частых случаев и сложно для сложных" или
"сделать сложно всегда".
Вот так мне это видится. Хотя у тебя в этом опыта больше, но я просто
хочу быть уверен, что ты учитываешь моменты, о которых я говорю.

Вот пример тесной связи между агентом и контролем перегрузки:

class transport_channel_agent : public so::agent
{
bool active_overload_propagation_;
bool am_I_overloaded_;
socket sock_;

void on_overload_notification(bool overload)
{
if (active_overload_propagation_)
{
sock_.send(msg_remote_overload::create(overload));
am_I_overloaded_ = overload;
}
else
suspend_me(overload);
}
};

Представь как это будет реализовываться с помощью отдельного
контроллера. Контролёру надо будет как-то получать данные из агента и
потом получать доступ к его сокету и флагу. И вообще насколько
разделение этого агента на 2 сущности целесообразно? Насколько можно
будет переиспользовать этого контроллера для других агентов? Насколько
имеет смысл для этого агента иметь другие политики обработки? Смогут
ли эти политики быть "прозрачно прицеплены" к этому агенту без какой-
либо поддержки с его стороны?

Какой вариант, имхо, стоит рассмотреть - так это установка стандартных
вариантов обработки перегрузки из вне. Допустим:
my_agent::ptr a = my_agent::create(generic_parameters(agent_name
("foo"), overload_control(drop_messages, 10, 20)),
здесь_идут_параметры_которые_форвардятся_конструктору_пользовательского_класса);
Мы знаем, что стандартные варианты обработки можно прицепить к любому
агенту, с его стороны не требуется никакой поддержки. А если требуется
нестандартная обработка перегрузки, то придётся руками писать
поддержку в агенте.

Dmitriy V'jukov

unread,
Feb 12, 2009, 2:38:37 PM2/12/09
to SObjectizer
On 11 фев, 14:47, "Yauheni Akhotnikau" <eao...@intervale.ru> wrote:
> On Wed, 11 Feb 2009 14:39:09 +0300, Dmitriy V'jukov <dvyu...@gmail.com>  

> > Такой юз-кейс я рассматривал. Если сам получатель знает об этих типах,
> > то это реализуется след. образом:
>
> > virtual void on_send_hook(agent* sender, msg* m, prio p)
> > {
> >   if (is_overloaded() && (m->type != switch_mode::type && m->type !=
> > shutdown::type))
> >   {
> >     if (drop_msg(sender, m))
> >       return;
> >   }
> >   do_enqueue(sender, m, p);
> > }
>
> Как-то это сложно выглядит.
>
> > И ещё возникла мысль, что можно добавить опции для контроля перегрузки
> > в описание сообщения, например:
>
> > srtuct shutdown_msg : lib::msg<shutdown_msg>
> > {
> >   static lib::overload_option const overload_ctl =
> > lib::overload_option_dont_drop;
> >   ...
> > };
>
> Вот это уже гораздо симпатичнее.


Да, наверное для сообщения имеет смысл декларативно задавать dont_drop
и drop_me_at_the_first_opportunity.
Хотя тут встаёт другая проблема. Если обработку определяет и сообщение
и получатель и отправитель, то надо как-то разруливать между ними
приоритеты. Понятно, что наверное надо отдавать приоритет dont_drop.
Но что если сообщение говорит drop_me, агент-отправитель говорит
drop_my_messages, а получатель вообще перекрывает функцию обработки
перегрузки и вообще ничего этого не учитывает...

Dmitriy V'jukov

unread,
Feb 12, 2009, 2:54:12 PM2/12/09
to SObjectizer
On 11 фев, 14:55, "Yauheni Akhotnikau" <eao...@intervale.ru> wrote:

> ===
> Из основных моментов обсуждения можно отметить:
> - если уменьшение нагрузки на приложении построено на выбрасывании  
> "лишних" сообщений, то нужно уметь указывать те типы сообщений, которые  
> можно выбрасывать и те, выбрасывание которых недопустимо. Политика  
> выбрасывания должна определяться как подписчиком, так и отправителем  
> сообщения;

... и сообщением

> - Боря Сивко привел пример из одного нашего приложения, когда степень  
> загруженности определяется количеством агентов, а не сообщений;

Логично. Когда я проектировал интерфейс, я рассматривал так же такой
паттерн - считаем сумму длин очередей некоторых агентов.

> - Боря Сивко привел пример из того же приложения, когда выбрасывание  
> определенных типов сообщений более предпочтительно, чем других (например,  
> сообщения check можно выбрасывать смело, т.к. транзакция еще не начата,  
> тогда как сообщения pay лучше доводить до обработки, чтобы не плодить  
> незавершенные транзакции, у которых стадия check была пройдена);
> - SObjectizer должен предоставлять разработчикам возможности написания  
> собственных контроллеров загрузки приложенния с широкими возможностями  
> (например, с просмотром очереди сообщений конкретного агента и  
> выбрасывания произвольных сообщений из этой очереди).

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

class my_agent
{
atomic<int> msg1_produced_;
atomic<int> msg2_produced_;
int msg1_consumed_;
int msg2_consumed_;

void on_send_hook(agent* sender, msg* m)
{
if (m->type == msg1::type)
msg1_produced_.fetch_add(1);
else if (m->type == msg2::type)
msg2_produced_.fetch_add(1);
do_enqueue(sender, m);
}

void on_msg(msg1* m)
{
msg1_consumed_ += 1;
...
}

void on_msg(msg2* m)
{
msg2_consumed_ += 1;
...
}

int get_msg1_count()
{
return msg1_produced.load() - msg1_consumed_;
}

int get_msg2_count()
{
return msg2_produced.load() - msg2_consumed_;
}
};

И уже использовать get_msg1_count() и get_msg2_count() при отправке,
что бы решать, надо ли добавлять в очередь. Или при потреблении, что
бы решать, надо ли отбрасывать сообщение:

void on_send_hook(agent* sender, msg* m)
{
if (m->type == msg1::type)
{
if (get_msg1_count())
return; // в очереди по-прежнему лежит такое сообщение
msg1_produced_.fetch_add(1);
}

или:

void on_msg(msg2* m)
{
msg2_consumed_ += 1;
if (get_msg1_count() > 10)
return; // в очереди лежит достаточно более свежих сообщений
...


Возможность произвольно просматривать очередь я бы отмёл как анти-
паттерн, так же как у std::list<> нет operator[].

Yauheni Akhotnikau

unread,
Feb 13, 2009, 3:42:57 AM2/13/09
to sobje...@googlegroups.com
On Thu, 12 Feb 2009 20:52:17 +0300, Dmitriy V'jukov <dvy...@gmail.com>
wrote:

> Допустим, обработчику требуется спросить у


> агента его состояние или какие-то флаги, или ещё какие-то данные;
> допустим обработчику надо изменить состояние агента или установить у
> него какой-то флаг или менить в агенте получателя отправляемых
> сообшений. В лучшем случае это потребует кривизны, как я приводил в
> "длинном" примере; в худшем случае это будет невозможно - как ты
> можешь "просто задать" агенту при создании контроллер, если этому
> контроллеру надо меня флаг в агенте, а этом флаг приватный для агента;
> или ещё хуже - в агенте ещё нет этого флага.

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

На счет "кривизны и невозможности" я не вполне согласен. Но это будет
продемонстрировано ниже.

> Итого - оба варианта функционально полны. Вопрос сводится только к
> "сделать просто для простых частых случаев и сложно для сложных" или
> "сделать сложно для простых частых случаев и просто для сложных". Ну
> впрочем для сложных случаев просто всё равно не будет, поэтому -
> "сделать просто для простых частых случаев и сложно для сложных" или
> "сделать сложно всегда".

Не, мне хочется достичь варианта "очень просто для простых случаев и чуть
более сложно в остальных" :)

> Вот пример тесной связи между агентом и контролем перегрузки:
>
> class transport_channel_agent : public so::agent
> {
> bool active_overload_propagation_;
> bool am_I_overloaded_;
> socket sock_;
>
> void on_overload_notification(bool overload)
> {
> if (active_overload_propagation_)
> {
> sock_.send(msg_remote_overload::create(overload));
> am_I_overloaded_ = overload;
> }
> else
> suspend_me(overload);
> }
> };
>
> Представь как это будет реализовываться с помощью отдельного
> контроллера

Не вижу сложностей:

class transport_channel_agent : public so::agent
{

<... все как у тебя ...>
friend class transport_channel_agent::overload_controller_t;
class overload_controller_t : public so::overload_controller_t
{
public :
virtual void handle_overload(... ctx) {
// Либо используем static_cast/dynamic_cast...
auto r = dynamic_cast< transport_channel_agent * >( ctx.receiver() );
r->on_overload_notification();

// Либо же ссылка на агента передается объекту в конструкторе и
// ему не нужно делать никаких cast-ов.
target_.on_overload_notification();
}
};
};

Yauheni Akhotnikau

unread,
Feb 13, 2009, 3:42:58 AM2/13/09
to sobje...@googlegroups.com
On Thu, 12 Feb 2009 22:54:12 +0300, Dmitriy V'jukov <dvy...@gmail.com>
wrote:

> On 11 фев, 14:55, "Yauheni Akhotnikau" <eao...@intervale.ru> wrote:


>
>> ===
>> Из основных моментов обсуждения можно отметить:
>> - если уменьшение нагрузки на приложении построено на выбрасывании  
>> "лишних" сообщений, то нужно уметь указывать те типы сообщений, которые
>>  
>> можно выбрасывать и те, выбрасывание которых недопустимо. Политика  
>> выбрасывания должна определяться как подписчиком, так и отправителем  
>> сообщения;
>
> ... и сообщением

А у сообщения-то зачем спрашивать?

Yauheni Akhotnikau

unread,
Feb 13, 2009, 4:05:03 AM2/13/09
to sobje...@googlegroups.com
On Thu, 12 Feb 2009 22:54:12 +0300, Dmitriy V'jukov <dvy...@gmail.com>
wrote:

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


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

Я попробую ответить в виде двух пунктов.

Пункт первый. Когда нужен просмотр очереди сообщений?

Представь себе взаимодействие клиента и некоторого сервера (платежного,
sms-шлюза, бронирования билетов и пр.). Клиент отсылает запрос (допустим,
pay). Сервер должен ответить ему pay_result. После чего клиент должен
подтвердить серверу, что он получил pay_result посредством pay_confirmed.
Т.е. трехфазная схема, в которой каждое сообщение помечается уникальным
идентификатором. Такая схема позволяет клиенту повторять pay несколько
раз, если он по каким-то причинам (тайм-ауты, разрывы связи и пр.) не
получает pay_result.

Итак, что-то произошло с агентом на сервере, который обрабатывает
сообщения pay -- его скорость упала в несколько раз (допустим, не по его
вине, а по виде платежной системы, с которой ему приходится работать). В
очередь к этому агенту может попать пара тысяч сообщений pay/pay_confirm.
Из которых изрядная часть сообщений будет повторыми. Т.е. отослал клиент
pay(N), не дождался pay_result (поскольку сервер сам еще не получил ответа
от платежной системы), повторил pay(N), затем еще раз повторил pay(N) и
т.д.

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

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

Итак, наступает какой-то затык в платежной системе, скорость обработки
запросов падает с 10 секунд до 20. У нас скапливается очередь. Мы
сканируем ее и выбрасываем из нее те сообщения, которые прождали своей
очереди больше 20 секунд (т.к. у них практически нет шансов попасть в
отведенный 30 секундный диапазон).

В любом из этих сценариев требуется сканирование очереди.

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


Пункт второй. Программист сам себе злобный Буратино :)

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

В конце-концов, во всем C++ именно такая идеология :)

Yauheni Akhotnikau

unread,
Feb 13, 2009, 6:17:51 AM2/13/09
to sobje...@googlegroups.com
On Fri, 13 Feb 2009 11:42:57 +0300, Yauheni Akhotnikau
<eao...@intervale.ru> wrote:

> Не вижу сложностей:
>
> class transport_channel_agent : public so::agent
> {
> <... все как у тебя ...>
> friend class transport_channel_agent::overload_controller_t;
> class overload_controller_t : public so::overload_controller_t
> {
> public :
> virtual void handle_overload(... ctx) {
> // Либо используем static_cast/dynamic_cast...
> auto r = dynamic_cast< transport_channel_agent * >(
> ctx.receiver() );
> r->on_overload_notification();
>
> // Либо же ссылка на агента передается объекту в конструкторе и
> // ему не нужно делать никаких cast-ов.
> target_.on_overload_notification();
> }
> };
> };

Кстати, что-то я совсем затормозил. Можно же использовать множественное
наследование:

class transport_channel_agent
: public so::agent

, public so::overload_controller_t
{
... все детали прежние ...
public :
virtual void handle_overload(ctx) {
... здесь код метода on_overload_notification...

Dmitriy V'jukov

unread,
Feb 13, 2009, 9:18:53 AM2/13/09
to SObjectizer
On 13 фев, 11:42, "Yauheni Akhotnikau" <eao...@intervale.ru> wrote:
> On Thu, 12 Feb 2009 22:54:12 +0300, Dmitriy V'jukov <dvyu...@gmail.com>  

> wrote:
>
> > On 11 фев, 14:55, "Yauheni Akhotnikau" <eao...@intervale.ru> wrote:
>
> >> ===
> >> Из основных моментов обсуждения можно отметить:
> >> - если уменьшение нагрузки на приложении построено на выбрасывании  
> >> "лишних" сообщений, то нужно уметь указывать те типы сообщений, которые  
> >>  
> >> можно выбрасывать и те, выбрасывание которых недопустимо. Политика  
> >> выбрасывания должна определяться как подписчиком, так и отправителем  
> >> сообщения;
>
> > ... и сообщением
>
> А у сообщения-то зачем спрашивать?


\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/

> srtuct shutdown_msg : lib::msg<shutdown_msg>
> {
> static lib::overload_option const overload_ctl =
> lib::overload_option_dont_drop;
> ...
> };

eao197: Вот это уже гораздо симпатичнее.


В пределе у сообщения тоже может перегрузаться функция:

enum msg_drop_t {dont_drop, drop_me, dont_care};
struct msg
{
virtual msg_drop_t();
};

Ну либо через интерфейс msg_overload_controller_t;

Dmitriy V'jukov

unread,
Feb 13, 2009, 9:21:15 AM2/13/09
to SObjectizer
On 13 фев, 14:17, "Yauheni Akhotnikau" <eao...@intervale.ru> wrote:
> Кстати, что-то я совсем затормозил. Можно же использовать множественное  
> наследование:
>
> class transport_channel_agent
>    : public so::agent
>    , public so::overload_controller_t
>    {
>    ... все детали прежние ...
>    public :
>      virtual void handle_overload(ctx) {
>        ... здесь код метода on_overload_notification...
>      }
>    };

Это уже симпатичнее. Фактически ты свёл к моему варианту. Ну хорошо, а
есть какие-нибудь примеры, где нам именно нужно разделение агента и
контроллера? Где мы хотим переиспользовать контроллера или прозрачно
устанавливать для агента кастомного контроллера без его ведома или
использовать одного агента с разными контроллерами?

Dmitriy V'jukov

unread,
Feb 13, 2009, 9:36:36 AM2/13/09
to SObjectizer


Ну это существенно отличается от того, что было раньше... ну или
точнее от того, как я это понял в первый раз :)

Во-первых, просмотр нужен не всегда а только периодически. Кстати, как
определять эту периодичность? На первый взгляд кажется, что просмотр
нужен когда очередь полностю обновляется, т.е. все старые сообщения с
предыдущего просмотра уже обработаны. Но если критерий удаления из
очереди основан на временных параметрах, то он может быть нужен чаще.
Хотя, возможно, можно остановиться на критерии "полного обновления
очереди" как на неком компромиссе.
Во-вторых, просмотр делает не отправитель, а получатель. А это легче,
т.к. сообщения лежащие в очереди уже "принадлежат" получателю - это
можно сделать без мьютекса. К тому же, что *получатель* закэширует
сообщения не так страшно, т.к. они ему всё равно в скором будущем
понадобятся (чего нельзя сказать об отправителях).

Кстати, это наталкивает на мысль, что в агент можно добавить
переопределяемые функции
void on_overload_begin();
void on_overload_end();
void scan_queue();

Dmitriy V'jukov

unread,
Feb 13, 2009, 10:02:05 AM2/13/09
to SObjectizer
On 13 фев, 11:42, "Yauheni Akhotnikau" <eao...@intervale.ru> wrote:

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

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

Если собственная политика *требует* наличия какого-то флага (и
ассоциированных с ним различий в действиях), то как ты с помощью
overload_controller_t сможешь это сделать для агентов, "для которых мы
не можем реализовать собственные методы"?
Я тоже не смогу. В этом и есть мой поинт - для агентов, "для которых
мы не можем реализовать собственные методы" мы во многих случаях
*никак* не сможем со стороны прилепить контроль перегрузки. Это не
недостаток моего метода, это следствие тесной интимной связи между
агентом и контролем перегрузки.

Yauheni Akhotnikau

unread,
Feb 13, 2009, 10:07:23 AM2/13/09
to sobje...@googlegroups.com
On Fri, 13 Feb 2009 17:18:53 +0300, Dmitriy V'jukov <dvy...@gmail.com>
wrote:

>> А у сообщения-то зачем спрашивать?

> В пределе у сообщения тоже может перегрузаться функция:


>
> enum msg_drop_t {dont_drop, drop_me, dont_care};
> struct msg
> {
> virtual msg_drop_t();
> };
>
> Ну либо через интерфейс msg_overload_controller_t;

Понял, в чем дело. Изначально я писал: "Политика  выбрасывания должна
определяться как _подписчиком_". Дело в том, что подписчиком в большинстве
случаев является владелец сообщения. А поскольку сообщение с флагами
dont_drop, drop_me, dont_care описывается у владельца, то я считал, что
все это как раз является вотчиной владельца (получателя) сообщения. И у
сообщения уже спрашивать не нужно.

В общем, недопонимание. Считаю, что вопрос прояснен.

Yauheni Akhotnikau

unread,
Feb 13, 2009, 10:11:59 AM2/13/09
to sobje...@googlegroups.com
On Fri, 13 Feb 2009 18:02:05 +0300, Dmitriy V'jukov <dvy...@gmail.com>
wrote:

> Если собственная политика *требует* наличия какого-то флага (и


> ассоциированных с ним различий в действиях), то как ты с помощью
> overload_controller_t сможешь это сделать для агентов, "для которых мы
> не можем реализовать собственные методы"?
> Я тоже не смогу. В этом и есть мой поинт - для агентов, "для которых
> мы не можем реализовать собственные методы" мы во многих случаях
> *никак* не сможем со стороны прилепить контроль перегрузки. Это не
> недостаток моего метода, это следствие тесной интимной связи между
> агентом и контролем перегрузки.

Ну, тут ситуация такая:

1. Агент сам реализует политику реакции на перегрузку. Вмешиваться мы не
можем.

2. Агент не реализует политики реакции на перегрузку. Мы можем вмешаться,
даже если не имеем доступа к исходникам агента. Например, какому-то агенту
мы назначим политику drop_messages, другому -- suspend_sender, третьему --
redirect_message.

А ситуация с тесной интимной связью на то и интимная, чтобы посторонние в
нее не лезли. И тут, я с тобой согласен, никакие overload_controller_t не
помогут.

Yauheni Akhotnikau

unread,
Feb 13, 2009, 10:39:53 AM2/13/09
to sobje...@googlegroups.com
On Fri, 13 Feb 2009 17:21:15 +0300, Dmitriy V'jukov <dvy...@gmail.com>
wrote:

> Это уже симпатичнее. Фактически ты свёл к моему варианту.

C++ слишком богатый язык, уж очень много вариантов. А вот в Eiffel у нас
бы и выбора не было, за что мне Eiffel и нравился ;)

> Ну хорошо, а
> есть какие-нибудь примеры, где нам именно нужно разделение агента и
> контроллера? Где мы хотим переиспользовать контроллера или прозрачно
> устанавливать для агента кастомного контроллера без его ведома или
> использовать одного агента с разными контроллерами?

Ну вот такие политики, как drop_message (выбрасывание сообщений),
suspend_sender (приостановка отправителя), redirect_message (переадресация
сообщения) -- они довольно универсальны для того, чтобы не переписывать их
каждый раз. А использование -- легко.

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

Или, допустим, пишем мы для параллельных вычислений/обработки чего-нибудь.
Есть у нас N агентов (скажем, по числу доступных ядер), и мы хотим сделать
балансировку нагрузки. Очень простой вариант: первым (N-1) агентам
назначаем политику redirect_message (каждый i-й агент переадресует свои
"лишние" сообщения на i+1 агента), а N-ому агенту -- политику
suspend_sender. И такой сценарий должен хорошо работать, если нагрузка
возникает спонтанно и сразу генерируется массовая рассылка сообщений,
после чего наступает некоторое затишье.

Если такой сценарий нас не устроит, мы можем сделать другую политику:
redirect_message по очереди. Т.е. первое сообщение отправляется первому
агенту, второе -- второму и т.д. Здесь будет использоваться очередь с
маленьким high_watermark (чтобы часто дергался overload_controller) и один
overload_controller на всех агентов (с сохранением общего состояния).

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

Dmitriy V'jukov

unread,
Feb 13, 2009, 10:40:33 AM2/13/09
to SObjectizer
On 13 фев, 18:11, "Yauheni Akhotnikau" <eao...@intervale.ru> wrote:

> 2. Агент не реализует политики реакции на перегрузку. Мы можем вмешаться,  
> даже если не имеем доступа к исходникам агента. Например, какому-то агенту  
> мы назначим политику drop_messages, другому -- suspend_sender, третьему --  
> redirect_message.

Это и в моём варианте решается:

Yauheni Akhotnikau

unread,
Feb 13, 2009, 10:51:43 AM2/13/09
to sobje...@googlegroups.com
On Fri, 13 Feb 2009 18:40:33 +0300, Dmitriy V'jukov <dvy...@gmail.com>
wrote:

>> 2. Агент не реализует политики реакции на перегрузку. Мы можем
>> вмешаться,  
>> даже если не имеем доступа к исходникам агента. Например, какому-то
>> агенту  
>> мы назначим политику drop_messages, другому -- suspend_sender, третьему
>> --  
>> redirect_message.
>
> Это и в моём варианте решается:
>
> my_agent::ptr a = my_agent::create(generic_parameters(agent_name
> ("foo"), overload_control(drop_messages, 10, 20)),
> здесь_идут_параметры_которые_форвардятся_конструктору_пользовательского_класса);

Экземпляр какого типа будет создаваться функцией my_agent::create? Что
будет создаваться функцией overload_control и куда ее результат будет
сохраняться?

Yauheni Akhotnikau

unread,
Feb 13, 2009, 11:01:38 AM2/13/09
to sobje...@googlegroups.com
On Fri, 13 Feb 2009 17:36:36 +0300, Dmitriy V'jukov <dvy...@gmail.com>
wrote:

> Ну это существенно отличается от того, что было раньше... ну или


> точнее от того, как я это понял в первый раз :)

Ну, вот и здорово, что что взаимопонимание улучшается.

> Во-первых, просмотр нужен не всегда а только периодически. Кстати, как
> определять эту периодичность? На первый взгляд кажется, что просмотр
> нужен когда очередь полностю обновляется, т.е. все старые сообщения с
> предыдущего просмотра уже обработаны. Но если критерий удаления из
> очереди основан на временных параметрах, то он может быть нужен чаще.
> Хотя, возможно, можно остановиться на критерии "полного обновления
> очереди" как на неком компромиссе.

Я думал так: у агента есть признак того, что очередь может быть
пересканирована. Когда случается перегрузка агент (или контроллер)
проверяет этот признак и, если признак положителен, выполняет перебор
очереди. Если в результате перебора оказывается, что очередь освободилась
достаточно (скажем на 1/3 и больше), то признак остается положительным. В
противном случае признак становится отрицательным и при следующей
перегрузке очередь уже не будет просматриваться.

Правда я не додумал одной важной вещи: когда признак опять сделать
положительным? По идее, это нужно делать автоматом, как только размер
очереди опускается ниже low_watermark.

> Во-вторых, просмотр делает не отправитель, а получатель. А это легче,
> т.к. сообщения лежащие в очереди уже "принадлежат" получателю - это
> можно сделать без мьютекса. К тому же, что *получатель* закэширует
> сообщения не так страшно, т.к. они ему всё равно в скором будущем
> понадобятся (чего нельзя сказать об отправителях).

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

Нужно будет еще подумать.

> Кстати, это наталкивает на мысль, что в агент можно добавить
> переопределяемые функции
> void on_overload_begin();
> void on_overload_end();
> void scan_queue();

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

Dmitriy V'jukov

unread,
Feb 13, 2009, 11:02:05 AM2/13/09
to SObjectizer
On 13 фев, 18:51, "Yauheni Akhotnikau" <eao...@intervale.ru> wrote:
> > Это и в моём варианте решается:
>
> > my_agent::ptr a = my_agent::create(generic_parameters(agent_name
> > ("foo"), overload_control(drop_messages, 10, 20)),
> > здесь_идут_параметры_которые_форвардятся_конструктору_пользовательского_класса);
>
> Экземпляр какого типа будет создаваться функцией my_agent::create? Что  
> будет создаваться функцией overload_control и куда ее результат будет  
> сохраняться?

my_agent::create() создаёт что-то типа smart_handle<my_agent>.
куда сохраняет результат функция overload_control - смотри самый
первый пост, реализация класса agent3

Yauheni Akhotnikau

unread,
Feb 13, 2009, 11:05:54 AM2/13/09
to sobje...@googlegroups.com
On Fri, 13 Feb 2009 19:02:05 +0300, Dmitriy V'jukov <dvy...@gmail.com>
wrote:

> On 13 фев, 18:51, "Yauheni Akhotnikau" <eao...@intervale.ru> wrote:

Понятно, т.е. overload_controll расчитывает на то, что у my_agent есть все
необходимые атрибуты для сохранения параметров контроля перегрузки.

Dmitriy V'jukov

unread,
Feb 16, 2009, 3:23:57 PM2/16/09
to SObjectizer
On Feb 13, 6:39 pm, "Yauheni Akhotnikau" <eao...@intervale.ru> wrote:
> On Fri, 13 Feb 2009 17:21:15 +0300, Dmitriy V'jukov <dvyu...@gmail.com>  

Это всё решается и в моём варианте:


my_agent::ptr a = my_agent::create(generic_parameters(agent_name
("foo"), overload_control(drop_messages, 10, 20)),
здесь_идут_параметры_которые_форвардятся_конструктору_пользовательского_класса);

Именно поэтому я и прошу нетривиальных/кастомных контроллеров/
примеров.
В этом и есть мой поинт - типовые варианты (приостановка агента,
выкидывание сообщений, перенаправление), на то и типовые, что они
используются широко, и для них НЕ НУЖНА никакая поддержка со стороны
агента (контроллер и агент взаимодействуют через некий стандартный
интерфейс, который есть у всех агентов). Для нетиповых - либо они
очень узко применяются и вещи типа переиспользования для них не
актуальны, либо они вообще требует специальной поддержки со стороны
агента - соотв. идея независимой компоновки агента и контроллера не
имеет смысла.

Dmitriy V'jukov

unread,
Feb 16, 2009, 3:30:13 PM2/16/09
to SObjectizer
On Feb 13, 7:05 pm, "Yauheni Akhotnikau" <eao...@intervale.ru> wrote:
> >> > Это и в моём варианте решается:
>
> >> > my_agent::ptr a = my_agent::create(generic_parameters(agent_name
> >> > ("foo"), overload_control(drop_messages, 10, 20)),
>
> >> здесь_идут_параметры_которые_форвардятся_конструктору_пользовательского_класса);
>
> >> Экземпляр какого типа будет создаваться функцией my_agent::create? Что  
> >> будет создаваться функцией overload_control и куда ее результат будет  
> >> сохраняться?
>
> > my_agent::create() создаёт что-то типа smart_handle<my_agent>.
> > куда сохраняет результат функция overload_control - смотри самый
> > первый пост, реализация класса agent3
>
> Понятно, т.е. overload_controll расчитывает на то, что у my_agent есть все  
> необходимые атрибуты для сохранения параметров контроля перегрузки.


То, что идёт в функцию создания агента через "generic_parameters
(...)", для всего этого безусловно есть поддержка со стороны
библиотеки. В этом их суть - грубо говоря, параметры функции создания
"расщепляются" на 2 группы: первая идёт непосредственно в конструктор
пользовательского класса - она нам не интересна, да и сделать с ней мы
ничего не можем. Вторая - идёт к нам в библиотеку, о каждом параметре
мы знаем, и знаем как его применить.
Цель двоякая. Во-первых - освободить пользователя от обязательного
определения конструктора агента и форвардить через себя библиотечные
параметры. Во-вторых, перекрывать некоторые параметры, которые агент
сам для себя установил в конструкторе (параметры, которые идут через
generic_parameters(...) должны применяться после отработки
конструктора).


Dmitriy V'jukov

unread,
Feb 16, 2009, 3:37:01 PM2/16/09
to SObjectizer
On Feb 13, 7:01 pm, "Yauheni Akhotnikau" <eao...@intervale.ru> wrote:

> Я думал так: у агента есть признак того, что очередь может быть  
> пересканирована. Когда случается перегрузка агент (или контроллер)  
> проверяет этот признак и, если признак положителен, выполняет перебор  
> очереди. Если в результате перебора оказывается, что очередь освободилась  
> достаточно (скажем на 1/3 и больше), то признак остается положительным.

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

> В  
> противном случае признак становится отрицательным и при следующей  
> перегрузке очередь уже не будет просматриваться.

А вот тут не понял. Допустим первый раз просканировали - ничего не
очистили. Перегрузка сохраняется. Проходит время. Уже возможно
появились сообщения, которые можно отбросить. А мы всё равно уже
ничего не пересканируем?

Мне кажется, что очередь должна пересканироваться периодически пока
есть перегрузка, но не при добавлении каждого сообщения. Периодичность
можно определять эвристиками типа - полное обновление очереди + по
реальному времени (например каждую секунду).


>
> Правда я не додумал одной важной вещи: когда признак опять сделать  
> положительным? По идее, это нужно делать автоматом, как только размер  
> очереди опускается ниже low_watermark.

Так если он сброшен, то очередь может уже и не упасть ниже
low_watermark.

Dmitriy V'jukov

unread,
Feb 16, 2009, 3:38:29 PM2/16/09
to SObjectizer
On Feb 13, 7:01 pm, "Yauheni Akhotnikau" <eao...@intervale.ru> wrote:

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

А чего пользователь может там хорошего предоставить?

Dmitriy V'jukov

unread,
Feb 16, 2009, 4:03:11 PM2/16/09
to SObjectizer
On Feb 13, 7:01 pm, "Yauheni Akhotnikau" <eao...@intervale.ru> wrote:

> > Во-вторых, просмотр делает не отправитель, а получатель. А это легче,
> > т.к. сообщения лежащие в очереди уже "принадлежат" получателю - это
> > можно сделать без мьютекса. К тому же, что *получатель* закэширует
> > сообщения не так страшно, т.к. они ему всё равно в скором будущем
> > понадобятся (чего нельзя сказать об отправителях).


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

С другой стороны - если бы сканирование делалось бы только один раз,
при возникновении перегрузки - было бы ещё ничего. Хотя уже это будет
требовать очереди обязательно с мьютексом. Грубо говоря меняем некую
фичу связанную с перегрузками на удар по основному пути - имхо не
очень хорошо.
Но твой пример с устареванием заявок будет требовать периодического
пересканирования очереди. И тут смысл всего для меня полностью
теряется. Смысл контроля загрузки - не допустить деградации
производительности при перегрузке. Тут НЕ достаточно просто
реализовать какую-то логику - эта логика должна быть очень аккуратно
спроектирована и не бить по системе во время загрузки ещё больше, а
разгружать её.
Если у нас нет каких-то очень специфических требований, то мы просто
полагаемся на механизм по-умолчанию - приостанавливание агентов.
Делать что-то другое имеет смысл ТОЛЬКО если это что-то будет
заставлять работать систему быстрее. Если они будет делать систему
медленнее - то смысл это делать? Воспользуйся механизмом по-умолчанию.
Ты говоришь, что "программист - сам себе злобный буратино". Злобный он
или не злобный, а выбора у него всё равно 2 - либо использовать
сканирование, либо не использовать; при чём если он использует, то это
неминуемо очень тормозно на большой очереди. Как он может быть тут не
злобным?

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

Например можно периодически сканировать на стороне получателя, но ещё
ввести некий middle watermark, после которого мы начинаем принимать
превентивные меры по разгрузке очереди (сканирование), но ещё не
выкидываем все подряд сообщения и не приостанавливаем агентов.
Допустим, если мы считаем, что в нормальном режиме очередь должна быть
не больше 100 сообщений, то можно установить:
low watermark - 100
middle watermark - 1000 (10-ти кратное превышение, начинаем выкидывать
неприоритетные сообщения)
high watermark - 2000 (20-ти кратное превышение, начинаем дропать всё
подряд и тормозить агентов)

Либо, например, явно приписывать сообщениям флаг drop_me_on_overload
при добавлении в очередь, т.е. сообщение добавляется, но если будет
перегрузка можем его удалить из очереди. Вообще логично, т.к. в
текущей схеме получается, что удаляются только вновь добавляемые
сообщения, но вполне логично, что их уже и в очереди достаточно.
Плюс можно задавать рил-тайм свойства в виде дедлайна. Т.е. явно
говорим - это сообщение можно выкидывать через 30 секунд.

Т.е. моя идея - более тонко подобрать средства. Возможно, что это и
для пользователя будет лучше, т.к. drop_me_on_overload и дедлайн
достаточно распространенные случаи.

Yauheni Akhotnikau

unread,
Feb 17, 2009, 2:43:58 AM2/17/09
to sobje...@googlegroups.com
On Mon, 16 Feb 2009 23:23:57 +0300, Dmitriy V'jukov <dvy...@gmail.com>
wrote:

>> Есть у нас N агентов (скажем, по числу доступных ядер), и мы хотим

>> сделать  
>> балансировку нагрузки. Очень простой вариант: первым (N-1) агентам  
>> назначаем политику redirect_message (каждый i-й агент переадресует свои
>> "лишние" сообщения на i+1 агента), а N-ому агенту -- политику  
>> suspend_sender. И такой сценарий должен хорошо работать, если нагрузка  
>> возникает спонтанно и сразу генерируется массовая рассылка сообщений,  
>> после чего наступает некоторое затишье.
>>
>> Если такой сценарий нас не устроит, мы можем сделать другую политику:  
>> redirect_message по очереди. Т.е. первое сообщение отправляется первому
>> агенту, второе -- второму и т.д. Здесь будет использоваться очередь с  
>> маленьким high_watermark (чтобы часто дергался overload_controller) и
>> один  
>> overload_controller на всех агентов (с сохранением общего состояния).
>>

> Это всё решается и в моём варианте:
> my_agent::ptr a = my_agent::create(generic_parameters(agent_name
> ("foo"), overload_control(drop_messages, 10, 20)),
> здесь_идут_параметры_которые_форвардятся_конструктору_пользовательского_класса);

Не все. Я как раз оставил два варианта, когда стандартные политики не
сработают. Особенно в последнем варианте, когда одна копия
overload_controller-а должна совместно использоваться несколькими агентами.

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

Как я понимаю, сейчас ситуация такая: ты предлагаешь зашить в агента
встроенную поддержку типовых ситуаций, а не стандартные ситуации
пользователь должен решать сам, переопределяя виртуальные методы агента. Я
предлагаю выделить логику управления перегрузкой в отдельный
объект-controller и предоставить готовые controller-ы для типовых
ситуаций. В нестандартной ситуации пользователь может определить
собственный controller.

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

В моем варианте такая возможность есть.

При этом создание агента посредством конструкции:

my_agent::ptr a = my_agent::create(
generic_parameters(

agent_name("foo"),
overload_control(drop_messages, 10, 20)),
здесь_идут_параметры_которые_форвардятся_конструктору_пользовательского_класса);

будет успешно работать как в одном варианте, так и в другом.

Поэтому я предлагаю пока данную тему заморозить. Может через пару недель
нам что-нибудь еще в голову придет.

Yauheni Akhotnikau

unread,
Feb 17, 2009, 2:44:00 AM2/17/09
to sobje...@googlegroups.com
On Mon, 16 Feb 2009 23:30:13 +0300, Dmitriy V'jukov <dvy...@gmail.com>
wrote:

> Цель двоякая. Во-первых - освободить пользователя от обязательного


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

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

my_agent_t::ptr_t
my_agent_t::create( const generic_params_t & params, ...other_params... )
{
// Фаза первая: создание объекта.
my_agent_t::ptr_t result( new my_agent_t( ...other_params... ) );
// Фаза вторая установка общих параметров.
result->set_name( params.name() );
result->set_overload_controll( params.overload_controll() );
...
return result;
}

приведет к лишнему геморрою в реализации конструктора my_agent_t.

Yauheni Akhotnikau

unread,
Feb 17, 2009, 2:55:05 AM2/17/09
to sobje...@googlegroups.com
On Mon, 16 Feb 2009 23:37:01 +0300, Dmitriy V'jukov <dvy...@gmail.com>
wrote:

> On Feb 13, 7:01 pm, "Yauheni Akhotnikau" <eao...@intervale.ru> wrote:


>
>> Я думал так: у агента есть признак того, что очередь может быть  
>> пересканирована. Когда случается перегрузка агент (или контроллер)  
>> проверяет этот признак и, если признак положителен, выполняет перебор  
>> очереди. Если в результате перебора оказывается, что очередь
>> освободилась  
>> достаточно (скажем на 1/3 и больше), то признак остается положительным.
>
> Т.е. очередь будет сканироваться при каждом добавлении сообщения? Ну
> хотя, если она каждый раз будет так сильно уменьшаться, то перегрузка
> должна рассосаться за несколько таких проходов. Так что это не должно
> быть проблемой.

> А вот тут не понял. Допустим первый раз просканировали - ничего не
> очистили. Перегрузка сохраняется. Проходит время. Уже возможно
> появились сообщения, которые можно отбросить. А мы всё равно уже
> ничего не пересканируем?
>
> Мне кажется, что очередь должна пересканироваться периодически пока
> есть перегрузка, но не при добавлении каждого сообщения. Периодичность
> можно определять эвристиками типа - полное обновление очереди + по
> реальному времени (например каждую секунду).

> Так если он сброшен, то очередь может уже и не упасть ниже
> low_watermark.

Смотри какой алгоритм. Допустим, у нас есть метод handle_overload, который
вызывается каждый раз, когда размер очередит превышает high_watermark.

class my_agent_t : public so5::agent_t, public so5::overload_controller_t
{
private :
// По умолчанию очередь можно пересканировать.
bool queue_rescan_enabled_;
const size_t low_watermark;
const size_t high_watermark;

public :
my_agent_t( ... )
: queue_rescan_enabled_( true )
, low_watermark( 500 )
, high_watermark( 750 )
{}

so5::overload_action_t
handle_overload(...ctx)
{
// Пересканируем очередь только, если это разрешено.
if( queue_rescan_enabled_ )
{
rescan_queue(ctx);
// Если нагрузка упала ниже low_watermark, значит при следующей
// перегрузке можно будет еще раз пересканировать очередь.
const size_t result_queue_size = ctx.queue().size();
queue_rescan_enabled_ = result_queue_size < low_watermark;
// Если освободили место в очереди, то очередное сообщение
// можно помещать в очередь. В противном случае сообщение
// должно быть выброшено.
return result_queue_size < high_watermark ?
so5::push_message_to_queue : so5::drop_message;
}
// Поскольку пересканирование запрещено, а места в очереди нет,
// то указываем сразу выбрасывать сообщения.
return so5::drop_message;
}
};

Таким образом, handle_overload будет вызываться только тогда, когда
количество сообщений в очереди достигает high_watermark. При этом возможны
две ситуации: разрешено пересканирование или нет. Если запрещено, то все
тривиально -- новое сообщение выбрасывается. А вот если разрешено, то
здесь интереснее.

Допустим, при пересканировании мы выбросили много сообщений
(result_queue_size<low_watermark). Это значит, что мы спокойно можем
получить в свою очередь еще, как минимум, 250 сообщений и handle_overload
вызывать не будут. Поэтому, если в следующий раз возникнет перегрузка, мы
вполне можем выполнить еще одно пересканирование.

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

Теперь самое важное: пусть queue_rescan_enabled_ оказался равным false.
Кто и когда выставить его в true? Вот этот момент я не додумал. Можно
пойти по пути создания в overload_controller-е двух методов:

so5::overload_action_t on_high_watermark(...ctx) = 0;
void on_low_watermark(...ctx) = 0;

Первый вызывается когда нагрузка превышает high_watermark, а второй --
когда нагрузка падает ниже low_watermark. Тогда приведенный мной пример
дополнился бы так:

class my_agent_t : public so5::agent_t, public so5::overload_controller_t
{
...все как и раньше...
void on_low_watermark(...ctx) { queue_rescan_enabled_ = true; }
};

Yauheni Akhotnikau

unread,
Feb 17, 2009, 2:55:07 AM2/17/09
to sobje...@googlegroups.com
On Mon, 16 Feb 2009 23:38:29 +0300, Dmitriy V'jukov <dvy...@gmail.com>
wrote:

> On Feb 13, 7:01 pm, "Yauheni Akhotnikau" <eao...@intervale.ru> wrote:

См. здесь:
http://groups.google.com/group/sobjectizer/browse_thread/thread/dcd8d078f3433a1a

В разделе "Остапа понесло" :)

Yauheni Akhotnikau

unread,
Feb 17, 2009, 3:02:24 AM2/17/09
to sobje...@googlegroups.com
On Tue, 17 Feb 2009 00:03:11 +0300, Dmitriy V'jukov <dvy...@gmail.com>
wrote:

> Но твой пример с устареванием заявок будет требовать периодического


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

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

Вообще, боюсь, что разговорами мы вряд ли сможем приблизиться к хорошему
решению. Тут уже нужны какие-то эксперименты и прототипирование. :(

Dmitriy V'jukov

unread,
Feb 17, 2009, 5:16:25 AM2/17/09
to SObjectizer
On 17 фев, 10:43, "Yauheni Akhotnikau" <eao...@intervale.ru> wrote:

> Поэтому я предлагаю пока данную тему заморозить. Может через пару недель  
> нам что-нибудь еще в голову придет.

Да, я тоже пока предлагаю притормозить :)

Dmitriy V'jukov

unread,
Feb 17, 2009, 5:17:22 AM2/17/09
to SObjectizer
On 17 фев, 10:44, "Yauheni Akhotnikau" <eao...@intervale.ru> wrote:
> On Mon, 16 Feb 2009 23:30:13 +0300, Dmitriy V'jukov <dvyu...@gmail.com>  

> wrote:
>
> > Цель двоякая. Во-первых - освободить пользователя от обязательного
> > определения конструктора агента и форвардить через себя библиотечные
> > параметры. Во-вторых, перекрывать некоторые параметры, которые агент
> > сам для себя установил в конструкторе (параметры, которые идут через
> > generic_parameters(...) должны применяться после отработки
> > конструктора).
>
> Вообще-то на практике встречаются ситуации, когда имя агента должно быть  
> известно в конструкторе пользовательского класса. В таких случаях  
> ращепление инициализации пользовательского агента на две фазы:
>
> my_agent_t::ptr_t
> my_agent_t::create( const generic_params_t & params, ...other_params... )
>    {
>      // Фаза первая: создание объекта.
>      my_agent_t::ptr_t result( new my_agent_t( ...other_params... ) );
>      // Фаза вторая установка общих параметров.
>      result->set_name( params.name() );
>      result->set_overload_controll( params.overload_controll() );
>      ...
>      return result;
>    }
>
> приведет к лишнему геморрою в реализации конструктора my_agent_t.

Ну так нет проблем - клади имя агента в ...other_params..., и всё
будет по старому.

Yauheni Akhotnikau

unread,
Feb 17, 2009, 5:23:19 AM2/17/09
to sobje...@googlegroups.com
On Tue, 17 Feb 2009 13:17:22 +0300, Dmitriy V'jukov <dvy...@gmail.com>
wrote:

>> my_agent_t::ptr_t


>> my_agent_t::create( const generic_params_t & params, ...other_params...
>> )
>>    {
>>      // Фаза первая: создание объекта.
>>      my_agent_t::ptr_t result( new my_agent_t( ...other_params... ) );
>>      // Фаза вторая установка общих параметров.
>>      result->set_name( params.name() );
>>      result->set_overload_controll( params.overload_controll() );
>>      ...
>>      return result;
>>    }
>

> Ну так нет проблем - клади имя агента в ...other_params..., и всё
> будет по старому.

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

Dmitriy V'jukov

unread,
Feb 17, 2009, 5:35:32 AM2/17/09
to SObjectizer
On 17 фев, 13:23, "Yauheni Akhotnikau" <eao...@intervale.ru> wrote:
>
> >> my_agent_t::ptr_t
> >> my_agent_t::create( const generic_params_t & params, ...other_params...  
> >> )
> >>    {
> >>      // Фаза первая: создание объекта.
> >>      my_agent_t::ptr_t result( new my_agent_t( ...other_params... ) );
> >>      // Фаза вторая установка общих параметров.
> >>      result->set_name( params.name() );
> >>      result->set_overload_controll( params.overload_controll() );
> >>      ...
> >>      return result;
> >>    }
>
> > Ну так нет проблем - клади имя агента в ...other_params..., и всё
> > будет по старому.
>
> Я надеялся, что подобная функция create будет шаблонной и пользователю не  
> придется ее переписывать под каждый собственный тип агентов.

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

Yauheni Akhotnikau

unread,
Feb 17, 2009, 5:50:49 AM2/17/09
to sobje...@googlegroups.com
On Tue, 17 Feb 2009 13:35:32 +0300, Dmitriy V'jukov <dvy...@gmail.com>
wrote:

> Так и есть - шаблонная. И это - единственный способ создания агента.

А это не сильно сурово? Единственный способ создания агента -- это
функция-фабрика?

Dmitriy V'jukov

unread,
Feb 17, 2009, 6:22:48 AM2/17/09
to SObjectizer
On 17 фев, 13:50, "Yauheni Akhotnikau" <eao...@intervale.ru> wrote:

> > Так и есть - шаблонная. И это - единственный способ создания агента.
>
> А это не сильно сурово? Единственный способ создания агента -- это  
> функция-фабрика?

По-моему самое оно.

Yauheni Akhotnikau

unread,
Feb 17, 2009, 6:31:09 AM2/17/09
to sobje...@googlegroups.com
On Tue, 17 Feb 2009 14:22:48 +0300, Dmitriy V'jukov <dvy...@gmail.com>
wrote:

> On 17 фев, 13:50, "Yauheni Akhotnikau" <eao...@intervale.ru> wrote:

А обосновать? ;)

Dmitriy V'jukov

unread,
Feb 17, 2009, 7:10:13 AM2/17/09
to SObjectizer
On 17 фев, 14:31, "Yauheni Akhotnikau" <eao...@intervale.ru> wrote:

> >> > Так и есть - шаблонная. И это - единственный способ создания агента.
>
> >> А это не сильно сурово? Единственный способ создания агента -- это  
> >> функция-фабрика?
>
> > По-моему самое оно.
>
> А обосновать? ;)

Агент достаточно высокоуровневая вещь, а чем "выше мы поднимаемся",
тем больше утрачивает смысл возможность делать мини-оптимизации типа
экономии одного выделения памяти (такие оптимизации актуальны на
уровне "манипулирования битами в буфере"); и вместе с тем больше
начинает напрягать излишнаяя свобода - поди разбирись в проекте, где
18 поколений разработчиков и каждый счёл своим долгом
продемонстрировать как ещё можно разместить и создать агента.
С другой стороны предельная простота и единообразие на высоком уровне
дают выгоду и разработчику библиотеки и конечному пользователю.

Аналогия с хэндлами файлов ОС. Допустим для определенности, что это
Win API. Файл ты можешь открыть только одной функцией, встроить объект
файла, что бы избежать динамического выделения памяти ты не можешь, с
файлом ты можешь работать только через непрозрачный хендл. И никто не
жалуется.

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

Yauheni Akhotnikau

unread,
Feb 17, 2009, 7:25:15 AM2/17/09
to sobje...@googlegroups.com
On Tue, 17 Feb 2009 15:10:13 +0300, Dmitriy V'jukov <dvy...@gmail.com>
wrote:

> Аналогия с хэндлами файлов ОС. Допустим для определенности, что это


> Win API. Файл ты можешь открыть только одной функцией, встроить объект
> файла, что бы избежать динамического выделения памяти ты не можешь, с
> файлом ты можешь работать только через непрозрачный хендл. И никто не
> жалуется.
>
> К тому же для агентов встраивание всё равно бессмысленно, т.к. никак
> не вяжется с асинхронностью: встраивание - общее время жизни,
> асинхронность - никаких зависимостей по времени жизни.

Все равно не понятно, почему нельзя давать пользователю возможность
сделать new my_agent_t()?

Dmitriy V'jukov

unread,
Feb 17, 2009, 2:34:50 PM2/17/09
to SObjectizer
On 17 фев, 15:25, "Yauheni Akhotnikau" <eao...@intervale.ru> wrote:
>
> > Аналогия с хэндлами файлов ОС. Допустим для определенности, что это
> > Win API. Файл ты можешь открыть только одной функцией, встроить объект
> > файла, что бы избежать динамического выделения памяти ты не можешь, с
> > файлом ты можешь работать только через непрозрачный хендл. И никто не
> > жалуется.
>
> > К тому же для агентов встраивание всё равно бессмысленно, т.к. никак
> > не вяжется с асинхронностью: встраивание - общее время жизни,
> > асинхронность - никаких зависимостей по времени жизни.
>
> Все равно не понятно, почему нельзя давать пользователю возможность  
> сделать new my_agent_t()?

А как его потом удалять? А если он массив из агентов сделает?
Заставлять пользователя писать конструкторы у всех агентов и
форвардить параметры? Давать пользователю указатель на "голый" объект
агента - а если он начнёт у него методы напрямую дёргать, или вообще
удалит? А если помимо имени агента в конструктор базового класса
агента надо будет добавить ещё один параметр - заставлять пользователя
переписывать всех агентов для форвардинга ещё одного параметра? А
какие плюсы?
Дать-то можно ему всё, только иногда можно дать больше разрешая
меньше.

Dmitriy V'jukov

unread,
Feb 17, 2009, 2:36:09 PM2/17/09
to SObjectizer
On 17 фев, 11:02, "Yauheni Akhotnikau" <eao...@intervale.ru> wrote:
>
> Ну, я исходил из того, что контроль загрузки должен ликвидировать само  
> понятие "перегрузка". Т.е. ситуация, когда у многих агентов начинают  
> вызывать handle_overload, должна показывать нижний предел  
> производительности системы. Т.к. если уж систему загрузили так, что она  
> постоянно дергает handle_overload, то ее производительность не должна  
> падать ниже этого уровня.

Производительность ступенчато упадёт после входа системы в состояние
перегрузки.

Dmitriy V'jukov

unread,
Feb 17, 2009, 2:42:15 PM2/17/09
to SObjectizer
А что ты думаешь по поводу сути метода - т.е. автоматическое
определение перегрузок исходя из длин очередей, при этом "виновником"
считается агент, который отсылал сообщение и само сообщение? Твоё
предложение заключалось в введении ochannels и явном задании кто куда
и что отправляет.

Dmitriy V'jukov

unread,
Feb 17, 2009, 2:52:23 PM2/17/09
to SObjectizer
Ещё тут остался открытый вопрос - если действие при перегрузке
определяет и отправитель и получатель и сообщение, то надо как-то
приоритезировать их воздействие. Особенно в ситуации, когда
пользователь переопределяет какие-то функции и, допустим, игнорирует,
что сообщение нельзя удалять.

Yauheni Akhotnikau

unread,
Feb 18, 2009, 2:43:58 AM2/18/09
to sobje...@googlegroups.com
On Tue, 17 Feb 2009 22:34:50 +0300, Dmitriy V'jukov <dvy...@gmail.com>
wrote:

>> Все равно не понятно, почему нельзя давать пользователю возможность  


>> сделать new my_agent_t()?
>
> А как его потом удалять?

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

// Класс родителя.
class a_parent_t : public so_4::rt::agent_t {
...
// Дочерний агент является атрибутом родителя.
a_child_t m_child;
};

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

> А если он массив из агентов сделает?

Ну сделает и что в этом плохого?

> Заставлять пользователя писать конструкторы у всех агентов и
> форвардить параметры?

Я вообще за все время работы с SObjectizer не помню более-менее серьезных
агентов, которым в конструктор не нужно было передавать ничего, кроме
имени агента. Может быть в каких-то тестах есть агенты типа a_hello_world,
но в серьезных проектах таких нужно еще поискать. Так что конструкторы
своим агентам пользователям придется писать. И если базовый тип, скажем,
so_5::agent_t будет требовать какой-то параметр
so_5::basic_agent_params_t, значит конструкторы пользовательских типов
будут об этом заботится. Это нормальная практика в ООП, имхо. Скажем,
многие агенты в SObjectizer-проектах сами определяют свое имя, которое
передается в базовый тип. Т.е. имя агента в этих случаях не
"протягивается" через конструкторы производных классов.

> Давать пользователю указатель на "голый" объект
> агента - а если он начнёт у него методы напрямую дёргать, или вообще
> удалит? А если помимо имени агента в конструктор базового класса
> агента надо будет добавить ещё один параметр - заставлять пользователя
> переписывать всех агентов для форвардинга ещё одного параметра? А
> какие плюсы?

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

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

> Дать-то можно ему всё, только иногда можно дать больше разрешая
> меньше.

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

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

Yauheni Akhotnikau

unread,
Feb 18, 2009, 3:13:10 AM2/18/09
to sobje...@googlegroups.com
On Tue, 17 Feb 2009 22:52:23 +0300, Dmitriy V'jukov <dvy...@gmail.com>
wrote:

> Ещё тут остался открытый вопрос - если действие при перегрузке

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

Вопросов здесь много. Думаю, я возьму тайм-аут, чтобы проанализировать и
переосмыслить все, что мы здесь понаписывали.

Dmitriy V'jukov

unread,
Feb 18, 2009, 5:45:53 AM2/18/09
to SObjectizer
On 18 фев, 11:13, "Yauheni Akhotnikau" <eao...@intervale.ru> wrote:

> Меня еще и другой вопрос занимает: как вообще приостанавливать отправителя  
> сообщения. Например, в ситуации, когда отправитель работает на контексте  
> той же рабочей нити, что и получатель.


С агентами всё просто - просто не помещаем его в очередь готовых к
выполнению агентов, пока у него счётчик suspension_counter больше
нуля. Когда он опускается до нуля и в очереди сообщений есть сообщения
- кладём его опять в очередь готовых для выполнения агентов.


> Или в ситуации, когда сообщение  
> отсылается вообще не из нити SObjectizer.


Тут, конечно, сложнее. Единственное, что приходит на ум - сделать
следующую полу-меру:
определяем степень перегрузки Х по шкале 1-10, дальше при Х=1..3
вызываем SwitchToThread/pthread_yield, при Х=4..10 вызываем Sleep(X).
Т.е. при максимальной перегрузке внешний поток сможет отсылать не
более 1 сообщения в 10 мс.

Dmitriy V'jukov

unread,
Feb 18, 2009, 6:03:51 AM2/18/09
to SObjectizer
On 18 фев, 10:43, "Yauheni Akhotnikau" <eao...@intervale.ru> wrote:

> > А как его потом удалять?
>
> Сейчас в SObjectizer агенты, если они были созданы динамически, удаляются  
> после дерегистрации. А если не были созданы статически, но вообще не  
> удаляются -- ответственность за это лежит на пользователе. Иногда я  
> использовал даже такой фокус:
>
> // Класс родителя.
> class a_parent_t : public so_4::rt::agent_t {
>    ...
>    // Дочерний агент является атрибутом родителя.
>    a_child_t m_child;
>
> };
>
> Агент-родитель регистрировался как динамический. После своего старта он  
> регистрировал своего дочернего агента в качестве своей дочерней  
> кооперации. И не нужно было ему заботится о создании экземпляра m_child --  
> он создавался автоматически.


Это не совместимо с использованием указателей для идентификации
агентов...

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


> > А если он массив из агентов сделает?
>
> Ну сделает и что в этом плохого?


Тогда только ручное управление временем жизни. Грубо говоря, malloc/
free.

> > Заставлять пользователя писать конструкторы у всех агентов и
> > форвардить параметры?
>
> Я вообще за все время работы с SObjectizer не помню более-менее серьезных  
> агентов, которым в конструктор не нужно было передавать ничего, кроме  
> имени агента. Может быть в каких-то тестах есть агенты типа a_hello_world,  
> но в серьезных проектах таких нужно еще поискать. Так что конструкторы  
> своим агентам пользователям придется писать. И если базовый тип, скажем,  
> so_5::agent_t будет требовать какой-то параметр  
> so_5::basic_agent_params_t, значит конструкторы пользовательских типов  
> будут об этом заботится. Это нормальная практика в ООП, имхо. Скажем,  
> многие агенты в SObjectizer-проектах сами определяют свое имя, которое  
> передается в базовый тип. Т.е. имя агента в этих случаях не  
> "протягивается" через конструкторы производных классов.


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


>
> > Давать пользователю указатель на "голый" объект
> > агента - а если он начнёт у него методы напрямую дёргать, или вообще
> > удалит? А если помимо имени агента в конструктор базового класса
> > агента надо будет добавить ещё один параметр - заставлять пользователя
> > переписывать всех агентов для форвардинга ещё одного параметра? А
> > какие плюсы?
>
> Ну вот, после адаптации SObjectizer к Qt у нас есть возможность создавать  
> Qt-виджеты, которые одновременно являются и агентами. Такие виджеты могут  
> как обрабатывать обычные Qt-шные события, так и события агента. Временами  
> очень удобно -- окно-агент, которое на контексте главной нити получает  
> сообщения из недр SObjectizer.
>
> В твоем подходе, как я понимаю, мы все эти возможности у пользователя  
> отнимаем. Не говоря почему.


Почему нельзя будет создавать агентов Qt-виджетов?

Yauheni Akhotnikau

unread,
Feb 18, 2009, 6:22:47 AM2/18/09
to sobje...@googlegroups.com
On Wed, 18 Feb 2009 13:45:53 +0300, Dmitriy V'jukov <dvy...@gmail.com>
wrote:

> On 18 фев, 11:13, "Yauheni Akhotnikau" <eao...@intervale.ru> wrote:


>
>> Меня еще и другой вопрос занимает: как вообще приостанавливать
>> отправителя  
>> сообщения. Например, в ситуации, когда отправитель работает на
>> контексте  
>> той же рабочей нити, что и получатель.
>
>
> С агентами всё просто - просто не помещаем его в очередь готовых к
> выполнению агентов, пока у него счётчик suspension_counter больше
> нуля. Когда он опускается до нуля и в очереди сообщений есть сообщения
> - кладём его опять в очередь готовых для выполнения агентов.

Это не будет работать в случае, когда в своем событии агент в цикле
генерирует сообщения.

>> Или в ситуации, когда сообщение  
>> отсылается вообще не из нити SObjectizer.
>
>
> Тут, конечно, сложнее. Единственное, что приходит на ум - сделать
> следующую полу-меру:
> определяем степень перегрузки Х по шкале 1-10, дальше при Х=1..3
> вызываем SwitchToThread/pthread_yield, при Х=4..10 вызываем Sleep(X).
> Т.е. при максимальной перегрузке внешний поток сможет отсылать не
> более 1 сообщения в 10 мс.

По-моему, нужно просто засыпать в send-е до срабатывания какого-то события.

Yauheni Akhotnikau

unread,
Feb 18, 2009, 6:37:23 AM2/18/09
to sobje...@googlegroups.com
On Wed, 18 Feb 2009 14:03:51 +0300, Dmitriy V'jukov <dvy...@gmail.com>
wrote:

>> // Класс родителя.


>> class a_parent_t : public so_4::rt::agent_t {
>>    ...
>>    // Дочерний агент является атрибутом родителя.
>>    a_child_t m_child;
>>
>> };
>>
>> Агент-родитель регистрировался как динамический. После своего старта он
>>  
>> регистрировал своего дочернего агента в качестве своей дочерней  
>> кооперации. И не нужно было ему заботится о создании экземпляра m_child
>> --  
>> он создавался автоматически.
>
>
> Это не совместимо с использованием указателей для идентификации
> агентов...

Почему?

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

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

>> > А если он массив из агентов сделает?
>>
>> Ну сделает и что в этом плохого?
>
> Тогда только ручное управление временем жизни. Грубо говоря, malloc/
> free.

Даже сейчас в SObjectizer нет проблем с массивами агентов. Зачем нам в
более продвинутой версии лишаться того, что уже есть сейчас?

> Практика это, имхо, совсем не нормальная. Просто альтернатив обычно нет.

Ну а какая альтернатива? Сейчас человек пишет, например:

class my_agent : public so_4::rt::agent_t {
public :
my_agent(
// Вот единственный параметр, который нужно передать в базовый класс.
const std::string & agent_name,
// А дальше набор прикладных параметров.
... )
: base_type_t( agent_name ) ...
};

После чего может создавать объекты my_agent так, как ему захочется.

Сейчас мы говорим, пишите так:

class my_agent : public so_4::rt::agent_t {
public :
my_agent(
// Набор прикладных параметров.
... ) ...
};

но создавать вы просто так my_agent не можете. Нужно обязательно через
create_agent. И если вы в конструкторе класса определяете какие-то
свойства агента (например, параметры перегрузки), то при вызове
create_agent этот факт нужно учитывать.

> Почему нельзя будет создавать агентов Qt-виджетов?

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

Dmitriy V'jukov

unread,
Feb 18, 2009, 6:38:47 AM2/18/09
to SObjectizer
On 18 фев, 14:22, "Yauheni Akhotnikau" <eao...@intervale.ru> wrote:
>
> >> Меня еще и другой вопрос занимает: как вообще приостанавливать  
> >> отправителя  
> >> сообщения. Например, в ситуации, когда отправитель работает на  
> >> контексте  
> >> той же рабочей нити, что и получатель.
>
> > С агентами всё просто - просто не помещаем его в очередь готовых к
> > выполнению агентов, пока у него счётчик suspension_counter больше
> > нуля. Когда он опускается до нуля и в очереди сообщений есть сообщения
> > - кладём его опять в очередь готовых для выполнения агентов.
>
> Это не будет работать в случае, когда в своем событии агент в цикле  
> генерирует сообщения.


Если он генерирует какое-то ограниченное кол-во, то нормально, т.к.
жестко ограничивать размер очереди мы и не стараемся. А если он
генерирует ОЧЕНЬ много сообщений, ну что ж это его проблемы. Точно так
же как если агент работает на разделяемом потоке, а в своём
обработчике события входит в практически бесконечный цикл. Оба эти
варианта должны быть просто описаны в документации.
Есть вариант с fiber'ами. Но он имхо ещё более тупиковый - на него
тоже элементарно придумать контр-пример вида "а если пользователь...".

> >> Или в ситуации, когда сообщение  
> >> отсылается вообще не из нити SObjectizer.
>
> > Тут, конечно, сложнее. Единственное, что приходит на ум - сделать
> > следующую полу-меру:
> > определяем степень перегрузки Х по шкале 1-10, дальше при Х=1..3
> > вызываем SwitchToThread/pthread_yield, при Х=4..10 вызываем Sleep(X).
> > Т.е. при максимальной перегрузке внешний поток сможет отсылать не
> > более 1 сообщения в 10 мс.
>
> По-моему, нужно просто засыпать в send-е до срабатывания какого-то события.


А, ну в принципе, можно и так. Засыпать до спада перегрузки. Наверное
так даже лучше.

Yauheni Akhotnikau

unread,
Feb 18, 2009, 6:50:45 AM2/18/09
to sobje...@googlegroups.com
On Wed, 18 Feb 2009 14:38:47 +0300, Dmitriy V'jukov <dvy...@gmail.com>
wrote:

>> Это не будет работать в случае, когда в своем событии агент в цикле  


>> генерирует сообщения.
>
>
> Если он генерирует какое-то ограниченное кол-во, то нормально, т.к.
> жестко ограничивать размер очереди мы и не стараемся. А если он
> генерирует ОЧЕНЬ много сообщений, ну что ж это его проблемы. Точно так
> же как если агент работает на разделяемом потоке, а в своём
> обработчике события входит в практически бесконечный цикл. Оба эти
> варианта должны быть просто описаны в документации.
> Есть вариант с fiber'ами. Но он имхо ещё более тупиковый - на него
> тоже элементарно придумать контр-пример вида "а если пользователь...".

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

Dmitriy V'jukov

unread,
Feb 18, 2009, 7:06:35 AM2/18/09
to SObjectizer
On 18 фев, 14:37, "Yauheni Akhotnikau" <eao...@intervale.ru> wrote:

> > Это не совместимо с использованием указателей для идентификации
> > агентов...
>
> Почему?


Повисшие указатели будут. Что с ними делать? И даже никакого средства
нет проверить повисший указатель или нет.


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


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


> Пользователю не нужно будет лишний раз делать new  
> (ну или вызывать самому create_agent) -- это же хорошо.


В своих рассуждениях ты исходишь из того, что new/delete - это дорого.
Я считаю, что это неправильный подход, это - полумера. Проблему с new/
delete мы не решаем, зато пытаемся в каких-то *частных* случаях
экономить единичные new/delete. Суть в том, что множество new/delete
всё равно останется.
Я считаю, что надо решить саму проблему - сделать new/delete быстрыми.
Соотв. тогда то, что не нужно делать лишний раз new/delete - это уже
не хорошо, это - ничто, аргумент ни за ни против.


> >> > А если он массив из агентов сделает?
>
> >> Ну сделает и что в этом плохого?
>
> > Тогда только ручное управление временем жизни. Грубо говоря, malloc/
> > free.
>
> Даже сейчас в SObjectizer нет проблем с массивами агентов. Зачем нам в  
> более продвинутой версии лишаться того, что уже есть сейчас?


А чего мы лишаемся-то? Если new/delete - это ничто.
Наоборот делаем систему более консистентной, более единообразной,
более понятной, более простой. Вот ты сам говорил это про отправку
сообщений - что надо предоставить пользователю только один вариант
отправки (ну хотя там как раз я не согласен, т.к. это будет иметь
последствия для производительности, в отличии от создания агентов).


> > Практика это, имхо, совсем не нормальная. Просто альтернатив обычно нет.
>
> Ну а какая альтернатива? Сейчас человек пишет, например:
>
> class my_agent : public so_4::rt::agent_t {
>    public :
>      my_agent(
>        // Вот единственный параметр, который нужно передать в базовый класс.
>        const std::string & agent_name,
>        // А дальше набор прикладных параметров.
>        ... )
>        : base_type_t( agent_name ) ...
>
> };
>
> После чего может создавать объекты my_agent так, как ему захочется.


Какая в создании агентов как захочется внутренняя ценность?


> Сейчас мы говорим, пишите так:
>
> class my_agent : public so_4::rt::agent_t {
>    public :
>      my_agent(
>        // Набор прикладных параметров.
>        ... ) ...
>
> };
>
> но создавать вы просто так my_agent не можете. Нужно обязательно через  
> create_agent. И если вы в конструкторе класса определяете какие-то  
> свойства агента (например, параметры перегрузки), то при вызове  
> create_agent этот факт нужно учитывать.


В create_agent() этого не нужно учитывать. В create_agent() можно
*опционально* переопределить некоторые параметры.


> > Почему нельзя будет создавать агентов Qt-виджетов?
>
> Потому что create_agent будет выдавать какой-то умный указатель, который  
> прячет в себе реальный указатель. А в Qt временами нужно отдавать  
> указатель на виджета кому-нибудь под управление. Например, время жизни  
> агента-виджета будет определяться его родительским виджетом.


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

Что касается управления временем жизни - тут сложнее. Если QT (или что-
то другое) принимает функцию для удаления (хороший дизайн), то мы
подсовываем ей нашу функцию управления; если не принимает, а дёргает
delete, то перегружаем delete у класса.

Dmitriy V'jukov

unread,
Feb 18, 2009, 7:09:22 AM2/18/09
to SObjectizer
On 18 фев, 14:50, "Yauheni Akhotnikau" <eao...@intervale.ru> wrote:
>
> >> Это не будет работать в случае, когда в своем событии агент в цикле  
> >> генерирует сообщения.
>
> > Если он генерирует какое-то ограниченное кол-во, то нормально, т.к.
> > жестко ограничивать размер очереди мы и не стараемся. А если он
> > генерирует ОЧЕНЬ много сообщений, ну что ж это его проблемы. Точно так
> > же как если агент работает на разделяемом потоке, а в своём
> > обработчике события входит в практически бесконечный цикл. Оба эти
> > варианта должны быть просто описаны в документации.
> > Есть вариант с fiber'ами. Но он имхо ещё более тупиковый - на него
> > тоже элементарно придумать контр-пример вида "а если пользователь...".
>
> Так вот в связи с этим у меня есть подозрение, что мы не сможем обеспечить  
> реализацию политики suspend_sender всегда. Т.е. suspend_sender -- это  
> пожелание. Если выясняется, что отправитель сообщения работает на другой  
> нити, то мы можем выполнить приостановку. Если же контексты отправителя и  
> получателя совпадают, то вместо suspend_sender мы должны либо выбрасывать  
> сообщения, либо порождать исключения. Может быть, исключения даже > предпочтительнее.


С агентами на одной нити нет проблем. Думай о планировании выполнения
агентов, не потоков.

Yauheni Akhotnikau

unread,
Feb 18, 2009, 7:48:35 AM2/18/09
to sobje...@googlegroups.com
On Wed, 18 Feb 2009 15:06:35 +0300, Dmitriy V'jukov <dvy...@gmail.com>
wrote:

>> > Это не совместимо с использованием указателей для идентификации
>> > агентов...
>>
>> Почему?
>
> Повисшие указатели будут. Что с ними делать? И даже никакого средства
> нет проверить повисший указатель или нет.

У меня сложилось впечатление, что после регистрации агента в SObjectizer
мы сможем получать его дескриптор через операцию поиска по имени. Т.е.
что-то вроде:

so_5::agent_handle_t channel = kernel->lockup_agent( "my_channel" );
so_5::msg_send_package_domain.send( channel, ... );

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

Более того, мы когда-то разговаривали на счет того, чтобы иметь
возможность сменить агента за дескриптором. Вроде того, что после
получения agent_handle_t агент был дерегистрирован (скажем из-за сбоя) и
зарегистрирован заново. Но agent_handle позволит с ним работать дальше.

Но вот зачем изначально создавать агентов только через create_agent я так
и не понял.

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

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

>> Пользователю не нужно будет лишний раз делать new  
>> (ну или вызывать самому create_agent) -- это же хорошо.
>
> В своих рассуждениях ты исходишь из того, что new/delete - это дорого.

Нет, я исхожу из других соображений.
По мне, вариант:

class a_child_t : public so_4::rt::agent_t {
public :
a_child_t( <Набор обязательных параметров> ) { ... }
};

class a_parent_t : public so_4::rt::agent_t {

private :
a_child_t m_child;

public :
a_parent_t(...)
: m_child( <Набор обязательных параметров> ) { ... }
};

более симпатичен, чем:

class a_parent_t : public so_5::agent_t {
private :
so_5::agent_handle_t< a_child_t > m_child;
public :
a_parent_t( ... )
: m_child( so_5::create_agent< a_child_t >( <Набор обязательных
параметров> )
{}
};

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

> Какая в создании агентов как захочется внутренняя ценность?

Свобода выбора, в конце-концов. Я не верю в правильность подхода "there is
only one right way".


> Что касается управления временем жизни - тут сложнее. Если QT (или что-
> то другое) принимает функцию для удаления (хороший дизайн), то мы
> подсовываем ей нашу функцию управления; если не принимает, а дёргает
> delete, то перегружаем delete у класса.

Блин, как-то все становится совсем запутано.

Yauheni Akhotnikau

unread,
Feb 18, 2009, 7:49:51 AM2/18/09
to sobje...@googlegroups.com
On Wed, 18 Feb 2009 15:09:22 +0300, Dmitriy V'jukov <dvy...@gmail.com>
wrote:

> С агентами на одной нити нет проблем. Думай о планировании выполнения
> агентов, не потоков.

Ну да, кооперативная многозадачность. Как в Windows 3.0.

Reply all
Reply to author
Forward
0 new messages