Inspirel banner

Programming Distributed Systems with YAMI4

6.2 Objects And Message Handlers

Objects in YAMI4 are high-level entities and are used as means to implement message routing.

Objects in YAMI4 are logical message destinations and can be treated as virtual ``mailboxes'', ``slots'', ``topics'' or ``devices'', which are the terms used by other messaging solutions. As such YAMI4 objects are not directly related to Ada, C++, Java, C# or Python objects, although obviously some language-level objects are needed to implement the actual state and behavior for them.

It is also not necessary to associate YAMI4 objects with object-oriented programming, although this kind of mental anchors can be useful to understand their meaning.

In their abstract meaning, objects are just destinations for remote messages. A single message is always sent to some named object that is managed by some agent. When the agent receives an incoming message, it extracts the destination object name from message header and uses that name to locate the message handler that conforms to a simple callback interface. The user-provided message handler can be treated as a physical implementation of the YAMI4 object and it is where the actual application-specific processing takes place in response to the incoming message.

The process of finding the appropriate message handler based on the object name extracted from message header is part of the more general concept called message routing, which is a high-level service provided by the general-purpose YAMI4 library.

Objects are identified by names and the name of the object has to be unique in the context of the given agent.

A special name "*" is reserved for the so-called any object, which is used when no object callback can be found for the given name. In other words, the any object wildcard name can be used to register general catch-all message handlers. If no message handler is found (and there is no any object registered), the message cannot be handled and is therefore rejected.

The callback handler is invoked in the context of the dispatcher thread, which is responsible for delivering incoming messages to their destination objects. This means that if a given agent has many dispatcher threads or if a single callback handler is registered with multiple agents, then the callback implementation needs to be thread-safe.

A single callback can be registered for many distinct logical objects - this is also, in a sense, the natural outcome of registering the any object. It is always possible to perform application-specific message routing or to take decisions based on the fine-grained destination object names, because the name of the destination object for the given message is available to the message handler.

Note:

Special care should be taken when the callback handler is removed (unregistered) from the agent. Since the whole process of message routing involves several background threads - the I/O thread that receives packets from physical channels and assembles them into complete messages and the dispatcher threads that deliver them to the user code - there is a possible race condition that arises when the object is unregistered by one of the user threads and at the same time background threads have successfully located the to-be-unregistered message handler callback and are just about to deliver the message to the user code. For performance reasons and to favor the most frequent path, no special protection against this race is put in place. This means that it is not guaranteed that after successful unregistration of the message handler (as perceived by the user code that did this unregistration) no more messages will be ever delivered to the just removed handler. This situation can be particularly challenging for those applications that rely on immediate deallocation of handlers, because this can lead to undefined behavior or erroneous execution. In practice this is rarely a problem, because typical servers do not unregister their objects and instead keep them alive and ready for service for the whole server lifetime. Still, some specific applications can rely on transient objects, where object removal is a regular part of the object life cycle - these applications can benefit from custom routing built with any objects, where message routing and object lifetime management can be integrated with a consistent synchronization policy.

The message handler has access to all necessary information concerning the message, in particular:

Further chapters provide information and examples related to the above data elements.

The following subsections present examples of callback definitions and registration in each programming language.

6.2.1 Ada

6.2.2 C++

6.2.3 Java

6.2.4 .NET

6.2.5 Python