Example of TRoServerLocator/TRoServerLocatorCollection creation at runtime

Hi

Using Delphi and RO 9.x in order to create a fail over solution.
After testing a simple server (hooking all components at runtime), I am now trying to add a failover procedure using TRoServerLocator/TRoServerLocatorCollection in TRoChannel.
I studied the FailOver sample but all the TRoServerLocation objects are already created at design time there.

The problem I have is that the Create function of TRoServerLocator is requiring a Collection that I don’t know where to find. Is it the TRoServerLocationCollection property of TRoChannel? Could you send me an example of how to create it at runtime?

why you can’t use function TROServerLocatorCollection.Add: TROServerLocator; ?

So, TRoServerLocatorCollection.Add returns a new TROServerLocator and already hooks it into the collection?

yep

The relevant client code I am executing is:

FCanal := TROIndyHTTPChannel.Create(nil);
FCanal.DispatchOptions := FCanal.DispatchOptions + [doFaultTolerant];
FCanal.ProbeServers := true;
// 
//  TServerInfo = record
//     Ip: string;
//     Porta: integer;
//     Nome: string
//   end;
for serverInfo in servidorInfoColection do begin
   locator := FCanal.ServerLocators.Add;
   with locator do begin
     DisableOnFailure := true;
     Enabled := true;
     Host := serverInfo.Ip;
     Port := serverInfo.Porta;
     ProbingOptions := ProbingOptions + [poProbeWhenEnabled];
     ProbingOptions := ProbingOptions + [poProbeWhenDisabled];
     ProbingOptions := ProbingOptions + [poEnableIfProbeSucceeded];
     ProbingOptions := ProbingOptions + [poDisableIfProbeFailed];
     Name := serverInfo.Nome;
     LoadBalancingServer := true;
     TargetUrl := 'http://' + Host + ':' + IntToStr(Port) + '/' + path + '/';
   end;
   FCanal.TargetUrl := locator.TargetUrl;
end;
...

I start 2 servers (127.0.0.1:8090 and 127.0.0.1:8091).
I setup client to read IP/Port from an INI and create a list which I read and create each TROServerLocator above.
The Problem:
The client always connects in the last server and if I shutdown the last server, it cannot go to the first server as in the sample.
I don’t know which other properties I should have to change in order to have it automatically connects to the first available server in the list.

have you set channel.DispatchOptions := [doFaultTolerant, doLoadBalanced]; ?

Yes, At first (in the code above), I’ve just set channel.DispatchOptions := [doFaultTolerant]; but changed now to
channel.DispatchOptions := [doFaultTolerant, doLoadBalanced]; with the same result.

can you create a simple project that illustrates your problem, pls?
you could miss something obvious …

I’ve created one.
Based on the LoadBalance sample.

  1. First, as I used originally HTTPServer, I changed the server and client to HTTP. With the same components as the original sample but Http. It worked.
  2. Then, I started creating the components in code (at runtime). Created TRoIndyHttpChannel, TRoBinMessage, TRoRemoteService, hooked them all and it worked.
  3. Then, I created TRoDynamicRequest and it doesn’t work

The problem is with the method: RefreshParameters. It seems to require a working server in order to work. As the server is disabled, it should try to go to next server and execute RefreshParameters on it (as it would if you don’t have TRoDynamicRequest and executes server method directly from the interface) but it doesn’t do that and all the remaining code doesn’t get executed.

Attached are the projects (client and server).LoadBalancing.zip (64.5 KB)

why you can’t create parameters manually like

    c := request.Params.Add;
    c.Name := 'Result';
    c.DataType := rtInteger;
    c.Flag := fResult;

    c := request.Params.Add;
    c.Name := 'a';
    c.DataType := rtInteger;
    c.Flag := fin;

    c := request.Params.Add;
    c.Name := 'b';
    c.DataType := rtInteger;
    c.Flag := fin;

instead of

request.RefreshParams();

?

by other hand, you can call ProbeAll and detect what servers work …

Hi

OK, I could do that but it’s annoying to always do that.

as you can see in TRODynamicRequest.RefreshParams, it requires TRODLLibrary that is filled with RODL from server-side.

if your client-side had server RODL you could call another overload method:

procedure TRODynamicRequest.RefreshParams(aOperation: TRODLOperation; aPersistValues: boolean);

for filling parameters automatically.

Could you give me an example? I have both server and client units and RODL file access from both projects.

see implementation of procedure TRODynamicRequest.RefreshParams(aPersistValues: boolean); but instead of RemoteService.GetRODLLibrary(); use your local TRODLLibrary where .RODL was loaded

OK, before I do that, I simplified my application and are using hard coded interfaces (not using TRoDynamicRequest) in order to avoid the refreshParams problem and the FaultTolerant option is working now: if I don’t have an active server, it tries to execute code in the next server, etc.

But, I designed the server to accept two TRoBinMessages (one for the server’s working interface and the other for controlling the working interface).

So, to shutdown the interface (in order to do some maintenance, etc), I disable the working interface using the code below:

procedure TsrvControl.DisableWorkingInterface;
var
nomeDispatcher: string; 
begin
  nomeDispatcher := 'ROMessage';
  ROServer.Dispatchers.DispatcherByName(nomeDispatcher).Enabled := false;
end;

When I do that (enabling and disabling the working interface this way), the fail-over doesn’t work, eg, it should see that the first server interface is disabled and try the next server but it throws and error: Invalid binary header Either incompatible or not a binary message
(you could simulate that using the load balancing sample).

I could simplify my server using two TRoServer components (one for working operations and the other to control the first server) but this would require one aditional open port at server station.

the fail-over feature will work when server isn’t accessible.
in your case, server is accessible but isn’t properly configured, i.e. dispatcher is disabled
I can suggest to handle admin and usual services on different ports, in this case you can stop usual service and restart it after maintenance

OK, I will go that way. Just to make it clear, when you say “handle admin and usual services on different ports”, you are saying to drop two TRoServers in the main unit and configure it accordingly, it is correct?

yes. you even can drop two different components like tcp server for admin purposes and http server for usual access.
Note: each server should use personal message components