Example Walk Through
Starting out with OpenMAMA can be a bit daunting for a beginner, particularly if you aren't familiar with the pub sub messaging model it uses. And if you're coming from an object-oriented background, the mechanisms behind the C API can be quite difficult to wrap your head around.
For the following walk through, we're going to look purely at the C++ version of the API, outlining the key components of an OpenMAMA application, and following the process of setting up our interface, subscribing to messages and outputing the data we recieve to the screen. We're going to use the "mamalistencpp.cpp" example application for this discussion. You can find the full source within the OpenMAMA source directory, under mama/c_cpp/src/examples/cpp/
The first step for any code review is to find our entry point into the application. In this case we are dealing with the very short main() function (compare with the mamalistenc application to see the difference in size).
The code begins by creating an instance of the MamaListen class, which sets up a number default values for member variables in the constructor, before parsing the command line and setting further values as required:
The command line parsing code isn't particularly interesting, though there are a number of parameters you may wish to be aware of:
-m - Sets the middleware which the application will use
-v - Can be passed multiple times, and sets the level of logging from OpenMAMA itself.
-s - Symbol list to create subscriptions for.
-tport - Transport on which to subscribe to the data.
-S - Data source.
The code proceeds to determine if a list of symbols has been provided on the command line, and if not it will attempt to get them either from a file (passed to the command line), or from a prompt:
The following code is enclosed within a try-catch block to handle any exceptions which may be thrown during the rest of the application lifecycle.
Entering into the try statement, the first call we make is to initialise the OpenMAMA libraries:
This is one of the more important functions in an OpenMAMA application, particularly regarding the order in which the components of an OpenMAMA session are setup.
To begin, a call is made to Mama::loadBridge() - this API calls accepts a string middleware name, and returns a constructed bridge object, which can then be used for subsequent methods.
This is followed by a call to Mama::open(), which sets up the internal processes used by OpenMAMA. open() doesn't return anything, but it is essential, and must be called after the bridges have been loaded.
The code proceeds by making a call to Mama::getDefaultEventQueue ( mBridgeImpl ), passing the bridge object created previously. This call returns the default event queue which OpenMAMA has created. If you aren't familiar with an event queue, you should take a look at the in depth documentation within the developer guide. At a basic level, event queues are FIFO structures onto which OpenMAMA lines up messages and other events on for processing by the client application. Each bridge will have a default queue, which is used in part by OpenMAMA itself. It's possible to create multiple other queues for a bridge to allow processing across each.
Skipping over the low/high watermark code, the next point of interest is the transport management code. First, we create a new MamaTransport object, which will be used to encapsulate the transport properties we set.
We then set the callback object for the transport. In our example, the TransportCallback object is a subclass of MamaTransportCallback, and implements the methods associated with the interface. It is these methods which handle various events associated with connecting to the underlying transport. This allows you to manage your applications behaviour should a transport disconnect, or the receipt of a data quality event.
Example from the MamaTransport.h header file, which defines the MamaTransportCallback object being subclassed:
We then have a call to create the transport. This takes the transport name passed in the command line - mTport - and the bridge created earlier, and creates the underlying transport.
Skipping over some futher setup code, the next step of the initialisation code is to setup the details for accessing the data dictionary. Some basic applications won't need this step, but when dealing with complex data sets the dictionary is invaluable (as it relates the fields of a message to their content). The transport initialisation code should be familiar (though we skip setting a transport event callback). The primary difference here is the use of a MamaSource object, which encapsulates details of how to access data in a single object.
At this stage, control falls back to the main() function again, and we proceed with the next step of inialisation. OpenMAMA has been setup, and transports have been defined. We now move on to a few minor issues: pulling down the data dictionary mentioned above, dumping it to a output if we want to (configured via a command line option), and setting up a symbol map (again, only if we've passed a parameter via the command line).
The next integral call is to the MamaListen::subscribeToSymbols() method:
Here we get down to the important part - figuring out what data we actually want to pull into our client.
The method begins by creating a MamaQueueGroup object, which creates a predefined number of queues equal to the number of threads we've assigned to our process.
If we want to include high or low watermark checks, we do it at this point, setting them for each event queue in the QueueGroup.
After which we iterate through the list of symbols, calling MamaListen::subscribeToSymbol () for each one.
The first step here is to create the DisplayCallback object, which is a subclass of the MamaSubscriptionCallback class. The methods of this class handle events relating to subscriptions - they are tiggered when a message is recieved, when gaps are detected, etc.
The next chunk of code builds our subscription object, and sets a number of properties associated with it. Check out the developer guide and API documentation for more information on what each does, or try experimenting with different settings (most can be set to different values via command line parameters).
The subscription itself is then "created" by a call to the create function. This accepts a transport, a queue upon which to track events for that subscription, the callback object, the "source" of the data, the symbol and a final "closure" value - essentially a small piece of data that floats around with subscriptions allowing you to identify them later. In this case, our closure is left as NULL.
The remaining code of this function deals with some general tidy up, noting the number of subscriptions created, setting the debug level of each, and populating a list of pointers to Subscription objects:
From here we drop back out to our main() function, and continue with out processing.
This is a traditional line within the example applications (and some commercial apps written on OpenMAMA) which alerts an aware user that the bridges have successfully loaded, the transports have been created, the dictionary has been requested, and the subscriptions setup. See this, and your application has made it past the hard parts ;-)
Having printed a nice little notification, the application finalises it's setup, preparing a file for logging output if required. It then triggers the MamaListen::start() method, which in turn makes a call to:
This kicks off all the underlying components, connecting transports, firing subscription requests, and pulling data off the queues. This call will block the application until something interrupts it, so all procesing from this point onwards continues within our callbacks.
For example, when a message is recieved for a specific message, it is placed onto the appropriate queue. When it reaches the top of the queue, OpenMAMA grabs the message, and fires the onMsg callback associated with that subscription:
In our application, this call triggers the display of the message contents, dumping the message to standard output.
NOTE: There is a fairly large switch statement before the display calls, which is designed to handle message types which do not contain interesting data to display, such as expiries, deletions etc.
If the application has been set to use the old iterator approach, and instance of the MsgIteratorCallback object is created (which is in turn a subclass of the MamaMsgFieldIterator), which is then passed to the messages iterator function. This triggers the onField call for each field in the message, dumping the data to screen.
If the new iterator approach is used, an iterator is created per message, and then stepped through, displaying the field at each stage.
Processing in OpenMAMA will continue until it determines the application has performed all the required processing - for example in the onMsg callback, when all the existing subscriptions have timed out, the following is triggered:
Alternatively, it will end when a signal is sent to the application which causes an interrupt - such as sending a Ctrl-C to a running application. Both these approaches will kill the application without any cleanup, so aren't fantastic examples in this case.
The final method for ending the processing, which actually performs appropriate cleanup, is via the setting of a shutdown timer via the command line. This is actually created during the MamaListen::start call, creating the timer and setting the callback to be fired when the timer is triggered:
The callback in this case is actually the MamaListen object itself, which subclasses MamaTimerCallback
Once the OpenMAMA stop call is made, the bridge begins to shut down, and the application will drop out of the start method entered earlier, back into main(). It then calls the cleanup MamaListen::shutdownListener () which tackles the tidy up steps:
- Stopping dispatch from various queue groups.
- Deleting the dictionary object.
- Destroying each existing subscription.
- Destroying the queue group itself.
- Destroying the underlying transports.
And finally making a call to Mama::close(). The shutdown should always be performed in this order to avoid unsafe interactions (such as queues attempting to use destroyed subscriptions).
At this point, your OpenMAMA application will have successfully started, connected, received data, used that data to output to stdout, then shut itself down and cleaned up after itself. All that remains it to tell the OS that the application has successfully completed it's work, and that it's ended as expected (and with it this code review manages the same):