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

[idea] Passing of some message_sink_t to subscribe_event_handler instead of agent_t

eao197 opened this issue · comments

NOTE. This idea is for the next big release of SObjectizer (probably breaking compatibility with 5.7-branch). It's unknown at the moment when work on that big release could be started.

In the current version of SObjectizer, only agents can make subscriptions to messages passed to message boxes. It's reflected in the format of abstract_message_box_t::subscribe_event_handler method:

//! Add the message handler.
virtual void
subscribe_event_handler(
	//! Message type.
	const std::type_index & type_index,
	//! Optional message limit for that message type.
	const message_limit::control_block_t * limit,
	//! Agent-subscriber.
	agent_t & subscriber ) = 0;

This prevents some interesting tricks like the chaining of mboxes. For example, there could be a standard MPMC mbox to that some messages are sent. And an instance of so_5::extra::mboxes::round_robin mbox can be subscribed to that MPMC mbox as a subscriber. And one of subscribers to round_robin mbox can be a so_5::extra::mboxes::retained_mbox. And so on.

It seems that there could be an abstract interface named message_sink_t that can be used as a destination for messages. Class agent_t will implement that interface. Message mboxes and message chains can implement that interface too.

It would make possible to subscribe a mbox to another mbox. And that opens a possibility to create chains of mboxes like the one described above.

I think it can make SObjectizer yet more flexible. But this idea is at the very early stage and needs additional thinking and experimenting.

I would like to drop here just a reference to a real use case we have discussed already that would benefit from this feature:
#30 (comment)

The idea I have at the moment is:

A new interface has to be introduced. Something like that:

class message_sink_t
{
public:
  static void
  call_push_event(
    message_sink_t & sink,
    const message_limit::control_block_t * limit,
    mbox_id_t mbox_id,
    std::type_index msg_type,
    const message_ref_t & message )
  {
    sink.push_event(limit, mbox_id, msg_type, message);
  }

protected:
  ~message_sink_t() noexcept; // Will be empty.

  virtual void
  push_event(
    const message_limit::control_block_t * limit,
    mbox_id_t mbox_id,
    std::type_index msg_type,
    const message_ref_t & message ) = 0;
};

The class agent_t will implement this interface.

A new helper class mbox_binder_t will be introduced. It will manage subscriptions between mboxes. And will destroy all remaining subscriptions in the destructor (or in a special method clear()). So a user has to create an instance of mbox_builder_t, make all subscriptions and then ensure that this mbox_builder_t lives as long as required.

Something like:

const so_5::mbox_t source_mbox = ...;
const so_5::mbox_t dest_one = ...;
const so_5::mbox_t dest_two = ...;
const so_5::mbox_t dest_three = ...;

auto binder{ std::make_unique< so_5::mbox_binder_t<> >() };

binder->from(source_mbox)
  .subscribe<MSG1>(dest_one)
  .subscribe<MSG1>(dest_two)
  .subscribe<MSG1>(dest_three);

binder->from(source_mbox)
  .subscribe<MSG2>(dest_one)
  .subscribe<MSG2>(dest_three);

binder->from(source_mbox)
  .subscribe<MSG3>(dest_one)
  .unsubscribe<MSG1>(dest_two)
  .unsubscribe_all(dest_three);

... // binder should be stored somewhere.

Where mbox_binder_t can be something like (just a sketch):

template< typename Lock_Type = std::mutex >
class mbox_binder_t {
  ... // Some internals.
public:
  class performer_t {
    mbox_binder_t & m_binder;
    mbox_t m_source;
    performer_t(mbox_binder_t & binder, mbox_t source) : m_binder{binder}, m_source{std::move(source)} {}
  public:
    template<typename Msg_Type>
    performer_t &
    subscribe(const mbox_t & dest) {
      m_binder.make_subscription<Msg_Type>(m_source, dest);
      return *this;
    }

    template<typename Msg_Type>
    performer_t &
    unsubscribe(const mbox_t & dest) {
      m_binder.drop_subscription<Msg_Type>(m_source, dest);
      return *this;
    }

    performer_t &
    unsubscribe_all(const mbox_t & dest) {
      m_binder.drop_all_subscriptions(m_source, dest);
      return *this;
    }
  };

  [[nodiscard]] performer_t
  from(const mbox_t & source) { return { *this, source }; }

private:
  ... // Implementation of make_subscription, drop_subscription, drop_all_subscriptions.
};

The implementation of mbox_binder_t will hide an actual implementation of message_sink_t interface from a user. But it seems that this implementation of message_sink_t will be very simple. The mbox_binder_t itself it expected to be more complex.

This will cover all cases when we want to subscribe a destination mbox to a source mbox (and have to possibility to use something from so5extra as the source mbox).

If a user wants to cover other use cases where a custom message sink is necessary he/she has to deal will all that stuff (like handling of subscriptions) to him/herself.

commented

Implemented in v.5.8.0