Adding Server-only Synchronization

The code in the previous section (see Programming Example: Create a Simple Service is sufficient to perform the work of the simple service, but requires the addition of a pair of modules to source and sink the synchronizable state when so requested by RPC Manager:

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

GetServerChanges Module;

SetHeartbeat Module;

GetServerChanges()is launched on the server by the RPC Manager, when the service must provide its synchronizable state for a client. RPC Manager is careful to launch this module on the same execution thread as the service so that, while executing a script in GetServerChanges(), nothing else can execute on the service's thread. This can greatly simplify the acquisition of the synchronizable state. It is still possible, however, to have an RPC subroutine run on the RPC Manager thread, within the service context. If any such subroutines can modify the synchronizable state of the service, it is essential to place certain statements within GetServerChanges() within a CriticalSection.

 

 GetServerChanges() should never be written as a subroutine, as it will run on the RPCManager thread, thereby suspending external machine communication during synchronization. If your GetServerChanges() takes any appreciable time, this will lead to a reduction in RPC throughput during RPC service synchronization, which may have undesirable effects. If GetServerChanges() is instead written as a launched module that slays itself when complete, the RPC thread will continue to service other RPC requests while you are building your synchronization data package. Further, if GetServerChanges()is written as a subroutine, the RPC thread can be suspended while running your GetServerChanges() script, allowing your service thread to get a time slice and modify the service data that you are attempting to sample. This may cause two PCs running the same service to end up with mismatching data. You must write GetServerChanges() as a launched module and call \RPCManager\SetDivert()as soon as you have finished sampling the synchronizable data for your service. If there is any possibility that another execution thread in your application can modify the data that GetServerChanges() is sampling during building of the synchronization package, you must protect the acquisition of the synchronization package with a CriticalSection().

 

GetServerChanges() passes the service's synchronizable state to the RPC Manager by returning a "packed stream" of RPC calls that will be made on the client. A packed stream allows the re-construction of the synchronizable state to be achieved. A sequence of RPC calls can be delivered to the client as one package, without the risk of communication interruptions causing a partial update of the synchronizable state on the client.

On the client, each component of the RPC package is executed in the strict sequence in which they were packed into the stream on the server. No other RPC call can interpose in this sequence on the client.

The second of the two modules is SetHeartbeat(). SetHeartbeat() is simply a service-specified RPC subroutine that, in our simple service case, receives the synchronizable state of the service as a parameter and stores it on the client. This RPC subroutine is the only RPC in the call package delivered from the server.

Let's look at the code for GetServerChanges():

{================ GetServerChanges =============================}

{ Called by RPC Manager during startup sync, on a server, to get the package of RPCs which create a synchronizable state on the client which is in step  with the server. }

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

GetServerChanges

(

  RevisionInfo { Revision info from GetClientRevision call 

made on synchronizing client - unused here }; 

  PackStreamRef { Pointer to var to receive changes }; 

  ClientName { Name of client }; 

)

[

  Stream { Stream of changes to go to client }; 

]

Sample [

  If 1 Wait; 

  [ 

{***** Sample the synchronizable state and build an RPC package to send to the synchronizing client. It is advisable to make the stream "building" code and SetDivert call share a CriticalSection, so that RPC subroutines cannot execute during this process. *****} 

 

    CriticalSection ( 

      Stream = \RPCManager\PackRPC(Stream, "SetHeartbeat" { module }, 

                      Invalid { scope }, 

      { Parameters: } Cast(LastHeartbeatCount, 4)); 

{***** Start diverting all RPCs for this client from here onwards.     RPC Manager will release the divert when synchronization done *****} 

      \RPCManager\SetDivert(SvcName, ClientName); 

    ); 

  ] 

]

Wait [

{***** This delay is not necessary. It is only here to prove that the service RPCs can continue once the RPC package has been built and SetDivert called. At the end of this delay, this module will pass the sampled state to RPC Manager which, in turn, passes it to the client ******} 

  If Timeout(1, 10) Done; 

]

Done [

  If watch(1); 

  [ 

    *PackStreamRef = Stream; 

    Slay(Self(), 0); 

  ] 

]

{ End of SampleService\GetServerChanges }

As stated above, RPC Manager launches this module when the server instance must provide the synchronized state of the service for a client. The second parameter (PackStreamRef) is a reference to a variable into which GetServerChanges() will store a packed stream of RPC calls that will be executed on the client. The third parameter (ClientName) is the workstation name of the client instance that is synchronizing with the server instance.

The script in state Sample does the important work of this code. While this script is running, nothing else on the service's execution thread can run. The first job is to pack together all the RPC calls that have to be made on the client to generate the same synchronizable state as is present on the server. In our simple case, this consists of a single call to SetHeartbeat() with the current count as its only parameter:

Stream = \RPCManager\PackRPC(Stream, "SetHeartbeat" { module },

Invalid { scope }, 

{ Parameters: } Cast(LastHeartbeatCount, 4)); 

After the RPC package has been built into a temporary stream, the RPC Manager module SetDivert()is called:

\RPCManager\SetDivert(SvcName, ClientName);

SetDivert()causes all remote procedure calls (RPCs) for the service that are destined for the specified client (the synchronizing client) to be held in a queue by the RPC Manager until synchronization is complete. The effect of this behaviour is to allow the server instance to continue to perform its normal work, including updating other clients, without the risk of updates arriving at the synchronizing client instance before the synchronizable state has been stored there.

Although the sample GetServerChanges shown is a launched module (specifically to incorporate the code in state Wait to demonstrate the function of SetDivert), you can also write it as a subroutine module. In this case, it must return Invalid when it is finished its work. If you return a valid value, RPC Manager will hang. There is no particular advantage in choosing a subroutine over a launched module. You can simply choose the form that best suits your needs. The same is true of two other service synchronization modules called by RPC Manager, named GetClientRevision and GetClientChanges. These modules are discussed later in this document.

The client stores the synchronizable state when RPC Manager unpacks the RPC package on the client. In our case, the only call in the package is to SetHeartBeat():

{====================== SetHeartbeat ==========================}

{ Called, on the client, by the RPC contained in the package   }

{ generated by the server, during GetServerChanges.            }

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

SetHeartbeat

(

Sequence { Info regarding call }; 

)

SetHeartbeat [

    If watch(1); 

    [ 

          CriticalSection( 

            HeartbeatCountIn = Sequence; 

            \RPCManager\SetSyncComplete(SvcName, Invalid, 1); 

          ); 

          Return(Invalid); 

    ] 

]

{ End of SampleService\SetHeartbeat }

{ End of SampleService }

SetHeartbeat() simply stores the synchronizable state in HeartbeatCountIn, and then informs RPC Manager that it is now synchronized. This is achieved by the call to SetSyncComplete(). It is vital that the client makes this call, as Service synchronization will hang if this call is not made. Note that SetHeartbeat() does not have to make this call. It can be a separate call within the RPC package, but it must be made. Once again, as SetHeartbeat() is an RPC subroutine and hence, runs on the RPC Manager's thread, a CriticalSection must protect modification of the values in the service.

Once SetSyncComplete()is called, RPC Manager releases the queue of service requests for this client on the server, and all subsequent service RPCs start flowing normally to the newly synchronized client.

The section that follows discusses the incorporation and configuration of the new service into a VTS application.