Programming Example: Create a Simple Service

To help you to better understand the development of an RPC service, this section contains the VTS code for a fully functional, simple service that is designed to maintain its synchronizable state correctly under all circumstances. We will build this code up from scratch, starting with the simplest specification. You can cut and paste this code into source files and use it.

The function of this simple service is to continually increment one numeric variable on the server and send the value of that variable to all clients. Each client who receives the new number verifies that it is one greater than the previous one. If it is not, the client increments an error counter. If the service functions correctly, each client will receive the value of the counter as the synchronizable state, followed by incremental updates to the count, regardless of how may clients there are or at what point in the server's life they are started.

The code for the main module of the service is quite straightforward. First, we need to register our service with the RPC Manager and then enter a state where, if we are the server, the counter is incremented and transmitted to all clients via the RPC Manager.

Below is the main module in its entirety, along with the RPC subroutine that it calls:

{======================== SampleService =======================}

{ This service maintains a simple synchronizable state and     }

{ properly supports all entry points required by RPC Manager   }

{ for correct operation. }

{==============================================================}

[

  SvcName = "Sample" { Deliberately different from the module name. Doesn't have to be different, but this allows clear distinction in the code between the two};

  RPCStatus { Current RPC connect status }; 

  CurrentServer { Current server }; 

  ServiceMode { Textual description of service mode }; 

{**** The synchronizable state of this service is represented by a simple, continually incrementing numeric. The last value generated by this service instance and the last value received by this service instance are needed to ensure continuity of count. ****} 

  HeartbeatCountIn = 0 { Heartbeat counter }; 

  HeartbeatCountOut = 0 { Heartbeat counter }; 

 

{***** Internal variables *****} 

  LastHeartbeatCount { Last Heartbeat count rxd }; 

  SequenceErrors = 0 { Number of periodic event sequence errors}; 

  ServerEnable { Server enable flag...prevents race condition}; 

{**** Regular service RPC modules which are service specific ****} 

  PeriodicRPC Module { Periodic event RPC call module }; 

{**** Required Synchronization modules, which RPC Manager will call ****} 

  GetServerChanges Module; 

  SetHeartbeat Module; 

 

  I; 

]

Init [

  If 1 Main; 

  [ 

{***** Register the sample service with RPC Manager. *****} 

    RPCStatus = \RPCManager\Register(SvcName, Invalid, Invalid, 

                                     Invalid, \RPC_SYNC_MODE); 

  ] 

]

Main [

{**** Maintain a couple of variables just to show the status of the service. These can be displayed on a VTS page ****} 

  ServiceMode = *RPCStatus == 2 { Server status == 2 } 

  ? "Server" 

  : *RPCStatus == 1 { Client status == 1 } 

  ? "Client" 

  : "Unknown" { Unknown yet == 0 }; 

  CurrentServer = \RPCManager\GetServer(SvcName); 

  If Watch(1, *RPCStatus); 

  [ 

    IfElse (*RPCStatus == 2, Execute( 

    {**** Just become server...set the output value to the last  received input value, plus 1 ****} 

    HeartbeatCountOut = HeartbeatCountIn + 1; 

    ServerEnable = 1; 

    ); 

  { else } Execute( 

    ServerEnable = 0; 

  )); 

{***** While I'm server, periodically increment the heart beat "out" counter 

and update all clients *****} 

If Timeout(PickValid(*RPCStatus == 2, 0) && PickValid(ServerEnable,

                     0), 1); 

  \RPCManager\Send(SvcName { Service sending the message }, 

                   \RemoteGUID { GUID defining app }, 

                   0 { Cut-off mode - NORMAL }, 

                   1 { Send to server flag - TRUE }, 

                   Invalid { Machine name or IP }, 

                   1 { Send to clients flag - TRUE }, 

                   0 { Execute local flag - FALSE }, 

                   1 { Recursive flag - TRUE }, 

                   "PeriodicRPC" { target RPC module to execute }, 

                   "SampleService" { context to execute in }, 

                   Invalid { object for update caching }, 

                   Invalid { InputSessionID }, 

                   HeartbeatCountOut++ { RPC module Parameters } 

     ); 

  ] 

]

{======================= PeriodicRPC ==========================}

{ Periodically RPC, called on the server and all clients, to   }

{ update the heart-beat (synchronized state).                  }

{==============================================================}

PeriodicRPC

(

  Sequence { Info regarding call }; 

)

PeriodicRPC [

  If watch(1); 

  [ 

    CriticalSection( 

      IfElse ((PickValid(HeartbeatCountIn, 0) != 1) && 

              (Sequence != HeartbeatCountIn + 1), 

              IfThen (PickValid(*RPCStatus != 2, 1), {No sequence  errors on server.} 

             SequenceErrors++; { It's already bumped the count. } 

             ); 

      { else } Execute( 

         HeartbeatCountIn = Sequence; 

       )); 

       LastHeartbeatCount = Sequence; 

     ); 

     Return(Invalid); 

  ] 

]

{ End of SampleService\PeriodicRPC }

Upon examining the code in more detail, it becomes apparent that the first task of the service code is to register itself as a service to RPC Manager:

RPCStatus = \RPCManager\Register(SvcName, Invalid, Invalid,

Invalid, \RPC_SYNC_MODE); 

The Register() call is passed the name of the service (SvcName). The name of the service must be unique within the application, but can be the same name as a service within another application. In our example, the service name is set to "Sample". The second, third, and fourth parameters to Register() are set to Invalid, as we do not require them at present. The fifth parameter to Register() is set to a special constant value (\RPC_SYNC_MODE), which tells the RPC Manager that our service has a synchronizable state. Omitting this parameter or setting it to zero results in a service that has no synchronizable state. In this example, there is no need to provide any of the synchronization support modules that we will see presently.

Register() returns a reference to a value that RPC Manager maintains on behalf of the service. The value that RPCStatus addresses (*RPCStatus) will be set by RPC Manager to one of three values:

2        If the service instance is the server.

1        If the service instance is a client.

0        If the state of the service instance has not yet been decided.

The remainder of the code in state Main uses *RPCStatus to determine what to do. ServiceMode and CurrentServer (see the "Sample" service module above) are set to values that can be put on a display page. They are not necessary for service operation.

When the service instance becomes the server and on the first evaluation of the service main module, the line:

If Watch(1, *RPCStatus);

evaluates to TRUE. This provides a point at which initialization can be performed when the service instance that is currently server changes.

Finally, a timeout statement trips once per second on the server, to broadcast the counter contents to all connected clients and then increment the counter. The ServerEnable variable is simply there to prevent a race condition that would arise with two statements watching the same data value (*RPCStatus).

The RPC issued by the Send() call invokes module PeriodicRPC() (see the PeriodicRPC() module example above) on the server and all clients, carrying the count as the sole parameter to this RPC subroutine. When PeriodicRPC() is running in the server instance, it does not check the sequence number for errors. Note that PeriodicRPC() performs its work in a CriticalSection, as RPC subroutines are run on the RPC Manager thread (whereas the remainder of the service code runs on the service thread). This prevents erroneous results caused by one thread modifying a value shared by the service and the RPC subroutine. An alternative technique is to have the RPC subroutine launch a module that interacts with the service's values. A module, so launched, runs on the application thread. Selection of the most appropriate method is usually determined by the complexity of the operations to be performed in response to an RPC subroutine invocation.

Note that the name for the service that is used for registration is different from the module name:

SvcName = "Sample" { Deliberately different from the module name }

(The service name used for registration is "Sample", while the module name is "SampleService"). The two names can be, and usually are, the same; however, they are two different entities, the service name being used to identify the service within the application, and the module name being used to identify a module within the scope of \Code. It is for clarity sake that two unique names are used to differentiate between the two.

The Send() call, in state Main, uses the module name to specify the context in which to find the RPC module, PeriodicRPC, but uses SvcName to identify the service within the application. The difference lies in the service name being used to determine to which workstations within the distributed system the RPC should be sent, while the module name identifies the module within \Code that contains the specified RPC module name.

Note: An example of a simple cross-application service based on the above "Sample" service's code can be found later in this manual (see Programming Example: Revised Code for Simple Service Example.

The section that follows addresses adding server-only synchronization.

Topics in this section:

Adding Server-only Synchronization

Configuring the Service

Adding More Servers

Server List Consistency

Client Revision Information

Client Changes