Implementation Notes

Data Processing

The processing in the PPI is driven by events. Without events nothing will happen. When an event is generated, the called module will probably call one of it's active connectors.

Calling an active connector will directly call the handler registered at the connected passive connector. This way the call and data are handed across the connections until an I/O module will finally handle the request (by not calling any other connectors).

Throttling is handled in the same way: Throttling a passive connector will call a corresponding (internal) method of the connected active connector. This method will call registered handlers and will analyze the routing information of the module for other (passive) connectors to call and throttle. This will again create a call chain which terminates at the I/O modules. An event which is called to be throttled will disable the event temporarily. Unthrottling works in the same way.

This simple structure is complicated by the existence of the input queues. This affects both data forwarding and throttling:

  • A data request will only be forwarded, if no data is available in the queue
  • The connection will only be throttled when the queue is empty
  • Handlers of passive input connectors must be called repeatedly until either the queue is empty or the handler does not take any packets from the queue

Managing the Data Structures

The PPI itself is a singleton. This simplifies many of the interfaces (We do not need to pass the PPI instance). Should it be necessary to have several PPI systems working in parallel (either by registering all events with the same event handler or by utilizing multiple threads), we can still extend the API by adding an optional PPI instance argument.

Every module manages a collection of all it's connectors and every connector has a reference to it's containing module. In addition, every connector maintains a collection of all it's routing targets.

All this data is initialized via the routing statements. This is, why every connector must appear in at least one routing statement: These statements will as a side effect initialize the connector with it's containing module.

Since all access to the PPI via the module is via it's base class, unbound member function pointers can be provided as handler arguments: They will automatically be bound to the current instance. This simplifies the PPI usage considerably. The same is true for the connectors: Since they know the containing module, they can explicitly bind unbound member function pointers to the instance.

Random implementation notes

Generation of throttle notifications: Backward throttling notifications are automatically generated (if this is not disabled) whenever the input queue is non-empty after the event handler has finished processing. Forward throttling notifications are not generated automatically within the connector. However, the Passive-Passive adaptor will generate Forward-throttling notifications whenever the input queue is empty.

Class Diagram

senf::ppi::connector::PassiveOutput senf::ppi::connector::OutputConnector senf::ppi::EventDescriptor senf::ppi::connector::PassiveInput senf::ppi::EventImplementation senf::ppi::connector::PassiveConnector senf::ppi::connector::ActiveInput senf::ppi::Route senf::ppi::EventManager senf::ppi::connector::Connector senf::ppi::connector::InputConnector senf::ppi::module::Module senf::ppi::connector::ActiveConnector senf::ppi::connector::ActiveOutput classes