SampleApp: Developing an asynchronous IBM Spectrum Symphony C++ client

The purpose of an asynchronous client is to get the output as soon as it is available. The client thread does not need to be blocked once the input data is sent and can perform other actions. In this tutorial, you learn how to convert a synchronous client into asynchronous.

Before you begin

Before you begin, ensure you have installed and started IBM® Spectrum Symphony Developer Edition.

About this task

You will complete the following tasks:
  1. Build the sample client and service
  2. Package the sample service
  3. Add the application
  4. Run the sample client and service
  5. Walk through the code

Procedure

  1. Build the sample client and service:
    1. Build the sample client and service on Windows. You can build client application and service samples at the same time.
      1. In the %SOAM_HOME%\version\samples\CPP\SampleApp directory, locate workspace file sampleCPP_vc6.dsw, or one of the Visual Studio solution files.
      2. Load the file into Visual Studio and build it.
    2. Build the sample client and service on Linux®. You can build client application and service samples at the same time.
      1. Change to the conf directory under the directory in which you installed IBM Spectrum Symphony Developer Edition.

        For example, if you installed IBM Spectrum Symphony Developer Edition in /opt/ibm/platformsymphonyde/de732, go to the /opt/ibm/platformsymphonyde/de732/conf directory.

      2. Source the environment:
        • (csh) source cshrc.soam
        • (bash) . profile.soam
      3. Compile using the Makefile located in $SOAM_HOME/version/samples/CPP/SampleApp:

        make

  2. Package the sample service.

    To deploy the service, you first need to package it.

    1. Package the sample service on Windows
      1. Go to the directory in which the compiled samples are located:

        cd %SOAM_HOME%\version\samples\CPP\SampleApp\Output\

      2. Create the service package by compressing the service executable. Here is an example, using gzip:
        gzip SampleServiceCPP.exe
        

        You have now created your service package SampleServiceCPP.exe.gz.

    2. Package the sample service on Linux:
      1. Change to the directory in which the compiled samples are located:

        cd $SOAM_HOME/version/samples/CPP/SampleApp/Output/

      2. Create the service package by compressing the service executable into a tar file:

        tar -cvf SampleServiceCPP.tar SampleServiceCPP

        gzip SampleServiceCPP.tar

        You have now created your service package SampleServiceCPP.tar.gz.

  3. Add the application.

    When you add an application through the IBM Spectrum Symphony Developer Edition cluster management console, you must use the Add Application wizard. This wizard defines a consumer location to associate with your application, deploys your service package, and registers your application. After completing the steps with the wizard, your application should be ready to use.

    1. In the IBM Spectrum Symphony Developer Edition cluster management console, click WorkloadSymphonyApplication Profiles.

      The Applications page displays.

    2. Select Global Actions > Add/Remove Applications.

      The Add/Remove Application page displays.

    3. Select Add an application, then click Continue.

      The Adding an Application page displays.

    4. Select Use existing profile and add application wizard. Click Browse and navigate to your application profile.
    5. Select your application profile xml file, then click Continue.

      For SampleApp, you can find your profile in the following location:

      • Windows: %SOAM_HOME%\version\samples\CPP\SampleApp\SampleApp.xml
      • Linux: $SOAM_HOME/version/samples/CPP/SampleApp/SampleApp.xml

      The Service Package location window displays.

    6. Browse to the service package you created in .gz or tar.gz format and select it, then, click Continue.

      The Confirmation window displays.

    7. Review your selections, then click Confirm.

      The window displays indicating progress. Your application is ready to use.

    8. Click Close.

      The window closes and you are now back in the Platform Management Console. Your new application is displayed as enabled.

  4. Run the sample client and service.

    To run the service, you run the client application. The service a client application uses is specified in the application profile.

    1. Run the sample client and service on Windows by running the client application:

      %SOAM_HOME%\version\samples\CPP\SampleApp\Output\AsyncClient.exe

      You should see output on the command line as work is submitted to the system.

      The client starts and the system starts the corresponding service. The client displays messages indicating that it is running.

    2. Run the sample client and service on Linux by running the client application:

      $SOAM_HOME/version/samples/CPP/SampleApp/Output/AsyncClient

      You should see output on the command line as work is submitted to the system.

      The client starts and the system starts the corresponding service. The client displays messages indicating that it is running.

  5. Walk through the code.

    You review the sample client application code to learn how you can understand the differences between a synchronous client and an asynchronous client.

    1. Locate the code samples:
      Table 1. Product code samples by operating system
      Operating system Files Location of code sample
      Windows Client %SOAM_HOME%\version\samples\CPP\SampleApp\AsyncClient
      Message object %SOAM_HOME%\version\samples\CPP\SampleApp\Common
      Service code %SOAM_HOME%\version\samples\CPP\SampleApp\Service
      Application profile The service required to compute the input data along with additional application parameters are defined in the application profile:

      %SOAM_HOME%\version\samples\CPP\SampleApp\SampleApp.xml

        Output directory %SOAM_HOME%\version\samples\CPP\SampleApp\Output\
      Linux Client $SOAM_HOME/version/samples/CPP/SampleApp/ AsyncClient
      Message object $SOAM_HOME/version/samples/CPP/SampleApp/Common
      Service code $SOAM_HOME/version/samples/CPP/SampleApp/Service
      Application profile The service required to compute the input data along with additional application parameters are defined in the application profile:

      $SOAM_HOME/version/samples/CPP/SampleApp/ SampleApp.xml

        Output directory $SOAM_HOME/version/samples/CPP/SampleApp/Output/
    2. Understand what the sample does.

      The client application sample sends 10 input messages with the data Hello Grid!! and retrieves the results.

      Results are returned asynchronously with a callback interface provided by the client to the API. Methods on this interface are called from threads within the API when certain events occur. In the sample, the events are as follows:
      • When there is an error at the session level
      • When results return from IBM Spectrum Symphony
    3. Note considerations for asynchronous clients:
      Synchronization

      Because results can come back at any time, it is probable that your callback code needs synchronization between the callback thread and the controlling thread. The controlling thread needs to know when work is complete.

      Order of results

      Results are not sent back in order. If order of results is important, the client application must sort the results.

    4. Note code differences between synchronous and asynchronous clients.

      An asynchronous client is very similar to a synchronous client. The only differences are:

      • You need to specify a callback when creating a session
      • You specify a different flag to indicate asynchronous when you create a session
      • Retrieval of replies
      Let us look at the steps to create synchronous and asynchronous clients and highlight the differences. Steps in bold indicate differences. Everything else is the same as the synchronous client.
      Differences in creating synchronous and asynchronous clients
    5. Declare the message object and implement.

      As in the synchronous client tutorial, declare the message object and implement your own message object.

      If you have not done so already, take a look at the synchronous client application tutorial Your First Synchronous IBM Spectrum Symphony C++ Client for details on the Message object, specifically:
      • Input and output: declare the message object
      • Implement the MyMessage object
    6. Declare and implement your callback object.

      Perform this step after declaring the Message object and implementing the MyMessage object.

      In MyCallback.h, we create our own callback class from the SessionCallback class, and we implemented onResponse() to retrieve the output for each input message that we send.

      Note:
      • onResponse() is called every time a task completes and output is returned to the client. The task output handle allows the client code to manipulate the output.
      • isSuccessful() checks whether there is output to retrieve.
      • If there is output to retrieve, populateTaskOutput() gets the output. Once results return, we print them to standard output and return.
      #include "soam.h"
      
      using namespace soam;
      using namespace std;
      
      #ifndef WIN32
      #include <pthread.h>
      #else
      #include <windows.h>
      #endif
      
      class MySessionCallback : 
          public SessionCallback
      {
          public:
              MySessionCallback()
                  :m_tasksReceived(0), m_exception(false) 
              {
      
      #ifndef WIN32
                  pthread_mutexattr_t attr;
                  pthread_mutexattr_init( &attr );
                  pthread_mutexattr_settype( &attr, PTHREAD_MUTEX_RECURSIVE ); 
      
                  pthread_mutex_init( &m_mutex, &attr );
      
                  pthread_mutexattr_destroy( &attr );
      #else
                  InitializeCriticalSection(&m_criticalSection); 
      #endif
      
                  cout << "Callback created ... " << endl;
              }
      virtual ~MySessionCallback() 
              {
      #ifndef WIN32
                  pthread_mutex_destroy( &m_mutex );
      #else
                  DeleteCriticalSection(&m_criticalSection);
      #endif
              }
      
      /////////////////////////////////////////////////////////
      // This handler is called once any exception occurs
      // within the scope of the session.
      // ======================================================
      virtual void onException(SoamException &exception) throw()
      {
          cout << "An exception occured on the callback.\nDetails: " << exception.what() 
          << endl;
      
      #ifndef WIN32
                      pthread_mutex_lock( &m_mutex);
      #else
                      EnterCriticalSection(&m_criticalSection); 
      #endif
                      m_exception = true;
      #ifndef WIN32
                      pthread_mutex_unlock( &m_mutex);
      #else
                      LeaveCriticalSection(&m_criticalSection);
      #endif
      
      }
      /////////////////////////////////////////////////////////
      // This handler is called once a message is returned 
      // from the system when a task completes.
      // ======================================================
      void onResponse(TaskOutputHandlePtr &output) throw()
      {
          try
          {
              // check for success of task
              if (true == output->isSuccessful())
              {                    // get the message returned from the service
                  MyMessage outMsg;
                  output->populateTaskOutput(&outMsg);
      
                  // display content of reply
                  cout << "Task Succeeded [" <<  output->getId() << "]" << endl;
                  cout << "Integer Value : " << outMsg.getInt() << endl;
                  cout << outMsg.getString() << endl;
              }
              else
              {
                  // get the exception associated with this task
                  SoamExceptionPtr ex = output->getException();
                  cout << "Task Failed : " << ex->what() << endl;
              }
      
          }
          catch(SoamException &exception)
          {
              cout << "Exception occured in OnResponse() : " << exception.what() << endl;
          }
      
      // Update counter used to synchronize the controlling thread 
      // with this callback object
      #ifndef WIN32
                      pthread_mutex_lock( &m_mutex);
      #else
                      EnterCriticalSection(&m_criticalSection); 
      #endif
                      ++m_tasksReceived;
      #ifndef WIN32
                      pthread_mutex_unlock( &m_mutex);
      #else
                      LeaveCriticalSection(&m_criticalSection);
      #endif
              }
              
              inline long getReceived() 
              { 
                  return m_tasksReceived; 
              }
      
              inline bool getDone()
              {
                  return m_exception;
              }
                         
      private:
      #ifndef WIN32
              pthread_mutex_t m_mutex;
      #else
              CRITICAL_SECTION m_criticalSection;
      #endif
      
              long m_tasksReceived;
              bool m_exception;
      };
      
      
    7. Create a session to group tasks.

      In AsyncClient.cpp, perform this step after you have connected to the application.

      When creating an asynchronous session, you need to specify the session attributes by using the SessionCreationAttributes object. In this sample, we create a SessionCreationAttributes object called attributes and set four parameters in the object.

      The first parameter is the session name. This is optional. The session name can be any descriptive name you want to assign to your session. It is for information purposes, such as in the command line interface.

      The second parameter is the session type. The session type is optional. You can leave this parameter blank or not make the API call at all. When you do this, system default values are used for your session.

      The third parameter is the session flag, which we specify as ReceiveAsync. You must specify it as shown. This indicates to IBM Spectrum Symphony that this is an asynchronous session.

      The fourth parameter is the callback object.

      We pass the attributes object to the createSession() method, which returns a pointer to the session.
      ...
      // Create session callback
      MySessionCallback  myCallback;
      
      // Set up session creation attributes
      SessionCreationAttributes attributes;
      attributes.setSessionName("mySession");
      attributes.setSessionType("ShortRunningTasks");
      attributes.setSessionFlags(Session::ReceiveAsync);
      attributes.setSessionCallback(&myCallback);
      
      // Create an asynchronous session
      SessionPtr sesPtr = conPtr->createSession(attributes);
      ...
      
    8. Synchronize the controlling and callback threads.

      Perform this step after sending the input data to be processed.

      Since our client is asynchronous, we need to synchronize the controlling thread and the callback thread. In this example, the controlling thread blocks until all replies have come back.
      ...
      // Now wait until all replies have been received asynchronously
      // by our callback ... for illustrative purposes we will poll 
      // until all replies are in.
      while ((myCallback.getReceived() < tasksToSend) && !myCallback.getDone()) 
      {
          ourSleep(2);
      }
      ...