HPC Pack SOA Tutorial III – Interactive mode
In tutorial II we discussed how to deal with a time-consuming service by employing a durable session. However, batch mode is not the only computation mode in the HPC world. Some computations can be finished within a few seconds to a few minutes. The end user may expect almost real-time response.
That mode brings different challenges when compared to batch mode. Response time is more critical. Therefore the session startup overhead cannot be ignored. In a typical HPC cluster, it takes a few seconds to start a new session. If there are other jobs running on the cluster ,the newly created session must wait until the resources are available, which makes the startup time much longer. Fortunately, HPC Pack provides a way to deal with this situation and cut down the unnecessary cost.
See the accompanying code sample to follow the steps in this article.
Implement the service
We use the same service as in tutorial II – the prime factorization service. To satisfy the real-time requirements, we'll just pass small numbers to the service.
Here's the service contract:
[ServiceContract]
public interface IPrimeFactorization
{
[OperationContract]
List<int> Factorize(int n);
}
And here's the service implementation:
public List<int> Factorize(int n)
{
List<int> factors = new List<int>();
for (int i = 2; n > 1;)
{
if (n % i == 0)
{
factors.Add(i);
n /= i;
}
else
{
i++;
}
}
return factors;
}
Implement the client
To save the time of starting new job, the client must reuse the existing session instead of creating a new one, because creating a new session means starting a new job. To reuse the existing session, we need to create the session as follows:
const string headnode = "head.contoso.com";
const string serviceName = "PrimeFactorizationService";
SessionStartInfo info = new SessionStartInfo(headnode, serviceName);
//Enable session pool
info.ShareSession = true;
info.UseSessionPool = true;
You may notice that there are two new properties of SessionStartInfo being assigned here.
Setting ShareSession to true means that any user can send requests to the broker, not just the one that creates the session.
Setting UseSessionPool to true ensures that the every new client uses the existing session instead of creating another one. The session pool is maintained on the server side - it guarantees that when a client connects to the same service with the flag set to true, it always returns the same session as long as it's still alive.
Now we can create the session. We don't want to use a durable session because it may affect the performance.
//create an interactive session
using (Session session = Session.CreateSession(info))
{
Console.WriteLine("Session {0} has been created", session.Id);
…
}
Create a broker client to send requests and get responses.
In the case of the previous code, we now have a situation where there can be many broker clients in a single session. In this case, we should assign a unique ID to the client.
//in one session, each broker client should have a unique id
string ClientId = Guid.NewGuid().ToString();
using (BrokerClient<IPrimeFactorization> client = new BrokerClient<IPrimeFactorization>(ClientId, session))
{
Console.WriteLine("BrokerClient {0} has been created", ClientId);
Random random = new Random();
int num = random.Next(1, Int32.MaxValue);
//Send request
FactorizeRequest request = new FactorizeRequest(num);
client.SendRequest<FactorizeRequest>(request, num);
client.EndRequests();
//Get response
foreach (BrokerResponse<FactorizeResponse> response in client.GetResponses<FactorizeResponse>())
{
int number = response.GetUserData<int>();
int[] factors = response.Result.FactorizeResult;
Console.WriteLine("{0} = {1}", number, string.Join<int>(" * ", factors));
}
}
Now run the client twice. You'll see that the clients share the same session ID, as a result of the enabled session pool. Also, the first client runs much longer than then second one, which indicates that the new client reuses the created session.
Since GetResponses is a synchronous function, the client will be blocked and kept waiting for the results. This is not a welcome situation in a real-time system, so let's try another way to get responses.
We can set an asynchronous callback for the client by using SetResponseHandler as follows:
//use this event sync main thread and callback
AutoResetEvent done = newAutoResetEvent(false);
//set callback function. this handler will be invoke before service replies.
client.SetResponseHandler<FactorizeResponse>((response) =>
{
int number = response.GetUserData<int>();
int[] factors = response.Result.FactorizeResult;
Console.WriteLine("{0} = {1}", number, string.Join<int>(" * ", factors));
//release the lock
done.Set();
});
Thus after normally sending requests, the client can continue with other work. When the responses are ready, the response handler will be called to display the results.
Deploy and test the service
You may follow this tutorial to deploy and test the case step by step.
You can run multiple clients. The output will be like this:
Notice that all the clients share the same session ID.
Cluster auto-grow and auto-shrink
A common situation of the interactive mode is to run a long-running service serving multiple clients. To respond to each request as quickly as possible, we should keep the session alive. But, on the other hand, having a SOA job occupy a large number of resources during off-peak times is wasteful.
HPC Pack has a feature to grow and shrink resources based on the number of requests. If there are no requests it will shrink the number of resources to the minimum number specified by the job. When receiving requests, it will auto-grow the resources to handle them.
Note: A session will time out if there's no client connecting to it for a period of time. To make a session a long running service, you can change the SessionIdleTimeout when starting the session.