Stiffstream / sobjectizer

An implementation of Actor, Publish-Subscribe, and CSP models in one rather small C++ framework. With performance, quality, and stability proved by years in the production.

Home Page:https://stiffstream.com/en/products/sobjectizer.html

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

User payload for event handler subscription

zumm opened this issue · comments

commented

SObjectizer allows to prioritize agents, but often it's not enough. There are two another prioritization levels. First one is level of messages. Dispatcher can define specific class (derived from message_t) to inherit prioritized messages from and resolve message priority by this class. This is great, but may be too expensive sometimes. Another one is level of event handlers. It is powerful enough and cheap, also it is best solution for my case. Unfortunately there is no way to use that level at this moment. I had to patch SObjectizer to pass priority through subscription_bind_t::event and store it in execution_hint_t, but i wish SObjectizer had native way to do it.

Optional argument of subscription_bind_t::event for user payload looks like suitable way that doesn't break backward compatibility. Also it may allow other possibilities for custom dispatchers.

I have to ask what priority of event handlers means.

I see two possible interpretations:

  1. The priority of the event handler means that there could be more than one subscription to a message in one agent's state. For example, evt_handler_low with low priority and evt_handler_high with high priority. Both of them subscribed to the same message type Msg. When a message of type Msg is extracted from agent's queue then evt_handler_high called first and only then evt_handler_low.
  2. The priority of the event handler means reordering of messages in the queue. For example, you have evt_handler_low for Msg_A, evt_handler_mid for Msg_B, and evt_handler_high for Msg_C. You send messages in that order: Msg_A, Msg_A, Msg_B, Msg_A, Msg_C. And event handlers are invoked in that order: evt_handler_high, evt_handler_mid, evt_handler_low, evt_handler_low, evt_handler_low.

I don't think that interpretation №1 has a sense for SObjectizer.

Interpretation №2 can be archived by using disp::prio_one_thread::strictly_ordered dispatcher. In that case, you have to split your agent with events of different priorities to a group of agents with different priorities. All agents from that group will use the common shared state (it's safe because all of them will work on the same dispatcher).

commented

I mean interpretation №2, of course. Yes, the splitting agent to group of agents and using prioritization at level of agents instead of level of event handlers is a possible solution. Unfortunately a changing some priorities during development becomes hard and produces a lot of work. Unnecessary entities make code more complicated. Also it may require additional synchronization depends on type of dispatcher.

Ok, I took the point.

I don't think that introduction of a custom field to some internal SObjectizer's structure is a good way to go. Because it isn't flexible enough, different users may want different types of that field.

It seems that the main question is the reordering of messages stored in the agent's queue. And that reordering can (or should) be orthogonal to a dispatcher type.

I need some time to think about it.

commented

That field may be pointer/union or type defined by dispatcher (not sure it's possible).

It seems that I have some time to work on this issue. There is some idea of how a prioritized queue can be added to an agent without modification of existing dispatchers. But this idea has to be checked, maybe I'm wrong.

Can you tell me some details about your implementation of the prioritized queue in your customized version of the SObjectizer? Do you use some custom dispatcher?

commented

Yes, i use custom dispatcher based on adv_thread_pool. Queue of demands is std::priority_queue. Priority of demand passing to execution_hint_t in same way as thread safety type. In methon push of dispatcher i use so_5::agent_t::so_create_execution_hint to get priority of demand from execution hint.

Thanks, your answer adds a lot to think about...

I think I would choose a different solution. I don't like the idea of extending subscription data by some additional info because it is impossible to predict which info will be required in different use cases. Sometimes it can be just short, sometimes it can be void*, sometimes it can be std::shared_ptr<SomeType> and so on.

For the prioritization of message handling I could imagine the following approach:

using priority_type = ...; // Some application specific type.
class prioritizator {
protected:
  ~prioritizator() = default;
public:
  prioritizator() = default;

  [[nodiscard]]
  virtual priority_type get_priority_for(
    const so_5::execution_demand_t & demand) const = 0;
};
...
void some_ticky_dispatcher::push(so_5::execution_demand_t demand)
{
  priority_type priority = lowest_priority;
  auto * priority_getter = dynamic_cast<prioritizator*>(demand.m_receiver);
  if(priority_getter) {
    priority = priority_getter->get_priority_for(demand);
  }
  ... // Storing of demand with detected priority.
}
...
class my_agent
  : public so_5::agent_t
  , protected prioritizator
{
protected:
  [[nodiscard]] priority_type get_priority_for(
    const so_5::execution_demand_t & demand) const override
  {
    if(demand.m_demand_handler == get_demand_handler_on_start_ptr())
      return highest_priority; // so_evt_start should have the highest priority.
    if(demand.m_demand_handler == get_demand_handler_on_finish_ptr())
      return lowest_priority; // so_evt_finish should have the lowest priority.
    if(std::type_index{typeid(some_message)} == demand.m_msg_type)
      return ...;
    if(std::type_index[typeid(another_message)} == demand.m_msg_type)
      return ...;
    ...
  }
};

You can have some reusable mixins like:

class first_concrete_prioritizator : public prioritizator {
public:
  [[nodiscard]] priority_type get_priority_for(
     const so_5::execution_demand_t & demand) const override
  {
    ...
  }
};

class second_concrete_prioritizator : public prioritizator {
public:
  [[nodiscard]] priority_type get_priority_for(
     const so_5::execution_demand_t & demand) const override
  {
    ...
  }
};

and use them as mixins for specific agent types:

class my_first_agent_type
  : public so_5::agent_t
  , protected first_concrete_prioritizator
{...};

class my_second_agent_type
  : public so_5::agent_t
  , protected second_concrete_prioritizator
{...};

Another way is to hold a mapper from a message type to a priority directly in a custom dispatcher.

For example, if the priority depends on an agent, mbox, and message type there can be a map with keys in the form std::tuple<agent_t *, mbox_id_t, std::type_index> and priority as values. The dispatcher will hold an instance of that map and will search priority in the map in push method.

If the priority depends only on message type and the priority is known at the compile time a mapper can be implemented via variadic template class.

The more I think about this topic the more ways I found without a need to modify SObjectizer's internals or SObjectizer's API. Another way is to use a pair of agent-collector and agent-performer. Agent-collector collects incoming messages and groups them by using various criteria (like message priorities). Agent-performer finishes the current task and asks the collector for a new one. The collector gets the next task from the internal queue and posts it to the performer the usual way.

Anyway, I described the problem in my blog in Russian here. I posted a reference to that post on LinkedIn and Twitter but do not get any feedback. So I have no enough information to start the addition of such a feature to SObjectizer. And have to way for more time until there will be more demands for it :(

Maybe with time, there will be more properly described real-world scenarios where the message priority handling is "the must-have" thing. But for now, I don't have such descriptions and can't check the applicability of various ways of extending SObjectizer.