Object Pooling

One last problem we should mention when discussing multiple clients accessing the same server was the issue of scalability. A scalable server application is simply one where an increase in the number of clients accessing the server can be offset by a linear increase in hardware capacity.

In a traditional DCOM application, the client object obtains and holds a reference to the server so long as both are alive. In practical terms, that means that the server object has spun off another thread for that client. The expense of obtaining the initial link to the server object means that client objects should be reluctant to release their reference.

However, thread creation versus resources consumed does not follow a linear relationship. So adding more clients to a server will have a progressively greater impact.

Now add to this the fact that many client-server applications do not require constant client communication with the server, just a connection on demand. Just think of email clients and servers.

So we see that clients often require a reference to a server application as long as they remain in use, but that they really only need the server occasionally. The answer is to pool the objects the server uses to work with clients.

There are three ways to improve a server application's scalability.

Thread Pooling

The simplest implementation of this is where the server application limits the number of threads it supports. This is common in web servers, where the simultaneous HTTP connections the server supports is set to match the hardware.

Just-In-Time Activation

But this doesn't answer the need to keep a client-server session open while the client is alive. To illustrate how to solve this problem, we'll use Microsoft Transaction Server (MTS). MTS intercepts client calls to the server objects running within. It gives the client a reference to itself and then deals with client requests — so the client has a long-lived reference as desired.

MTS creates a server object on demand, along with its associated context object, and the server does its work. When the server object is finished, it calls the 'transaction' methods

SetComplete()
or
SetAbort()
(even if the object isn't supporting transactions), which tells MTS that the object can be deactivated and memory reclaimed.

Note that there are times when the server object must maintain state, that is session information. In this situation there are a few solutions. Either we can use a direct client-server connection as we discussed above, we can use the context object or we can save the object's state to some datastore between each client call.

In designing such systems, the client gets a reference and hangs onto it, just as before. However, the server design needs some consideration. The server objects must implement the

IObjectContext
interface to tell MTS when they are finished, with the two methods above. Ideally, for good scalability, they should do this as quickly as possible — even to the point of the objects acting as 'function fulfillers' with little or no state.

Object Recycling

When a server object requires a lengthy initialization period, just-in-time activation won't be an adequate solution. It's possible to recycle an object preserving its initial state (for instance, with an ODBC connection to some datastore) and provide a pool of such objects to clients.

Object recycling has been tabled for inclusion in MTS for some time, using the

CanBePooled()
method of the
IObjectControl
interface. However it transpires that recycling an object instance, rather than creating a new object, may only be more efficient in a certain limited number of cases.

This has to do with MTS' use of resource dispensers. For example MTS can hold a number of ODBC connections open for MTS objects, which turns the programming model on its head, as the MTS objects should return the ODBC connection to the pool as quickly as possible.

© 1998 by Wrox Press. All rights reserved.