COM+ Object Pooling
Author: Jeremiah Talkar
This article introduces COM+ Object Pooling as well as describes how it works. Additionally, it will specify the requirements for pooled objects and describe when it makes sense to use this service.
It is important to understand the concept of a COM+ Application. The formal definition from the Platform SDK documentation states:
"A COM+ application is the primary unit of administration and security for Component Services. The application is a group of COM components that, generally, perform related functions."
For those familiar with MTS, this is similar to the MTS Package. Unless otherwise stated, the term Application is used throughout this article to mean a COM+ Application.
Object Pooling is a service that was documented in MTS but is fully supported only in COM+. The MTS Glossary defines Pooling as "A performance optimization based on using collections of pre-allocated resources, such as objects or database connections." It is also mentioned that pooling results in more efficient resource allocation.
While the MTS definition above is valid for COM+ Object Pooling, the latter has some really great innovations on this front. These will be highlighted throughout this article.
A component can be declaratively configured to specify that it be pooled as well as to specify various pool characteristics. The characteristics that are available in the Windows 2000 implementation of COM+ are:
Additionally, a constructor string can be declaratively specified in order to customize the pooled object for a given task. For example, a database connection string or a log file name can be specified as the constructor string. Refer to the section 'Using constructor strings with pooled objects' later in this article for additional information about this feature.
The following figure displays the Activation tab for the IIS Web Application Manager component. It should be noted that all of these features are new in COM+ and were not available in MTS.
The one thing that a reader should take from this article is that COM+ Object Pooling can be used for either or both of the following
Given the right circumstances, performance and scalability of a COM+ application can be significantly improved by using Object Pooling. This is possible because:
Resource governing is primarily achieved by administratively configuring the 'Maximum pool size' characteristic. This is a deceptively simple, yet extremely powerful feature of Object Pooling. The simplest example of this is to keep open only as many database connections as you have licenses available for. This in stark contrast to MTS which did not limit the number of objects created, leading to situations wherein objects would start failing once the available licenses for a precious resource (e.g. database connection) were all used up.
How Object Pooling works
An application is started up either explicitly by selecting "Start' from it's context-sensitive menu within the COM+ Explorer or implicitly as a result of a creation request for one of the components within that application. For each component (within the application) that has been configured for pooling, COM+ will maintain and manage instances of it in a separate pool.
Pools are homogeneous which implies that all objects within a pool have the same CLSID. Any pooled object that is not in use is good to return to any client except in the case of transactional objects wherein COM+ tries to first scan the pool for an object that is already associated with that transaction.
Each pool within the application is populated with the number of instances specified in the 'Minimum pool size' characteristic. So ideally, each pool will have the configured number of objects ready and waiting before the first client request is satisfied. Note that I use the term 'ideally' because it is possible that some or all of the objects could not be created as a result of some error(s).
As object creation requests start flowing in, they are satisfied on a first-come-first-served basis from the pool. If no pooled objects are available, and the pool has not yet reached the 'Maximum pool size' high-water mark, COM+ will create a new object for the requesting client. Before handing out an object reference to the requesting client, the object is activated such that it can perform any per-client initialization.
Once the high-water mark is reached, all subsequent object creation requests are queued up, waiting for one of the objects within the pool to become available. The number of activated and deactivated objects within the pool never exceeds the 'Maximum pool size' value.
If any request has not been satisfied within the time specified in the 'Creation timeout (ms)' characteristic, an error (E_TIMEOUT) is returned to the client, indicating that the request timed out. How this condition is handled is entirely up to the client application.
When a client releases its reference to an object, COM+ will deactivate the object and try to return it to the general pool. Details of the activation and deactivation process are covered in detail in the 'State management for pooled objects' section.
State management for pooled objects
A pooled object's state typically comprises of:
In order to have better control over state that can be classified as 2) above, the pooled object should implement the IObjectControl interface. This interface will be familiar to MTS programmers and in fact has the same IID as the MTS version (I verified this by looking up MTX.h that is part of VC++ 6.0 and the newer COMSvcs.h that is part of the Platform SDK).
It is not mandatory for a COM object to implement this interface in order to be pooled. The exception to this rule is if the pooled object also happens to be transactional, in which case the CanBePooled method of the interface is used to determine the state of the resources it holds. If a COM object does not implement IObjectControl, instances of it will be created until the maximum pool size is reached, as each instance is assumed to be poolable.
When a pooled object is created, COM+ will aggregate it into a COM+ object that then manages the former by invoking the IObjectControl methods at various instances during its lifetime. It is important to understand that only COM+ can QI for and invoke methods on this interface. If a client tries to QI for this interface, the COM+ object that aggregates the actual pooled object will return E_NOINTERFACE.
The IObjectControl interface is derived from IUnknown and defined with the [local] IDL attribute (i.e. it is not remotable). This interface defines three methods as described in the following paragraphs.
Activate: This method is invoked when an object in the general pool is about to be returned to a client, activated in a specific COM+ context. This happens before any of the other methods are invoked. This is a good time to implement any client and context specific initialisation.
Deactivate: This method is invoked whenever the client releases an object. If the object is also JIT activated, this will happen when it is deactivated. Any client and context specific cleanup is typically performed in the implementation of this method.
This method returns a 'void' (or nothing) and hence cannot be used to determine whether the deactivation failed. The CanBePooled method (described next) is the only way for COM+ to find out if the deactivation was successful and whether the object can be returned to the general pool of available objects.
CanBePooled: This method is invoked after the Deactivate method and it allows the object to notify COM+ whether it can be returned to the general pool. The object should monitor its internal state to determine whether it can be reused and return TRUE from this method. It is important to ensure that the object is returned to the pool in a generic state without holding onto any client specific information.
Using constructor strings with pooled objects
COM+ allows parameterised object construction wherein a text string can be passed in to an object at creation time. This allows the developer to write a general-purpose component that can then be administratively configured for each installation by specifying a construction string for it via the COM+ Explorer. The classic example of this is to specify the Data Source Name (DSN) that the object uses when it is initialised.
The Activation tab in the properties for the COM object has the 'Enable object construction' checkbox that is enabled only if the object implements the IObjectConstruct interface. When checked, a text string can be entered in the 'Constructor string' edit box.
COM+ provides the constructor string to an instance of the component via the IObjectConstruct interface. When an instance of the component is created, COM+ will QI for this interface and invoke its Construct method, passing in an IObjectConstructString interface. The instance of the component can then access the constructor string via IObjectConstructString::get_ConstructString and use it appropriately.
It should be noted that this feature is not restricted to pooled objects but can be effectively used within pooled objects to achieve a greater degree of granularity in how you pool and reuse resources. The Platform SDK documentation outlines creating several distinct components that are identical except for constructor strings and CLSIDs, in order to maintain distinct pools of objects holding connections usable by distinct groups of clients. I personally am not sure if I like this approach and would like to experiment with using the 'TreatAs' key to achieve the same behaviour.
Requirements for pooling COM objects
A COM object must meet certain requirements in order for COM+ to provide the Object pooling service for that object. These requirements are described in the next few paragraphs.
The object must be configured for object pooling
This is done by checking the 'Enable object pooling' checkbox on the Activation tab of the properties for that object from within the COM+ Explorer.
The object must have no thread affinity
Since a pooled object could be invoked on a different thread every time it is activated, it should not exhibit any kind of thread affinity. This means that the object's ThreadingModel must be marked as Both, Free or Neutral. This also implies that the object should not use any thread local storage (TLS) to hold state.
The object must be aggregatable
As mentioned in the earlier 'State management for pooled objects' section, when COM+ activates a pooled object, it creates an aggregate to manage the lifetime of that object and invoke methods on IObjectControl. Hence the object must be aggregatable.
The object should not aggregrate the Free Threaded Marshaller (FTM)
COM+ uses interception to provide services like Object Pooling and the FTM essentially bypasses the COM+ proxy stub mechanism. Hence a pooled object cannot aggregrate the FTM.
The object must be (almost) stateless
In order for a pooled object to be reused to service different clients, it must not hold onto any client specific state when it is returned to the general pool. It will typically have some state that it reuses across clients, like any precious resources that are acquired during initialisation. As mentioned in the earlier 'State management for pooled objects' section, per client state can be managed by implementing the IObjectControl interface.
The object can optionally implement IObjectControl
Object Pooling does not require the pooled object to implement IObjectControl. Hence Legacy COM objects that satisfy all of the above requirements can still be pooled. In this case the legacy COM object will be created until the maximum pool size is reached, as each instance is assumed to be poolable.
This is a good way to govern resources using legacy COM objects. If performance and scalability is the real reason to use this service, then it makes sense to implement IObjectControl to enable lifetime management.
Transactional objects that are also pooled MUST implement IObjectControl.
Which development environments can be used?
Microsoft Visual C++ 6.0
This environment allows creating COM objects that satisfy the requirements mentioned in the previous section. This is applicable to COM objects created using the Active Template Library or using raw C++ (a la Kraig Brockshmidt in 'Inside OLE').
To the best of my knowledge, it is not possible to use MFC for this purpose because the MFC classes are not thread safe, a primary requirement if the threading model is to be Both, Free or Neutral. If any reader is aware of how to achieve this within acceptable limits of programming complexity, I will be happy to revise this paragraph to reflect the same.
Microsoft Visual Basic 6.0
Since Visual Basic 6.0 can only create Single or Apartment threaded COM objects, these objects are automatically ruled out from being able to leverage the COM+ Object Pooling service.
Future versions of Visual Basic are likely to support developing COM objects that are Free Threaded, as indicated during Steve Ballmer's February 15th keynote at VBITS San Francisco. Whether these objects will satisfy all the requirements for Object Pooling is a question that will have to wait until a preview version is available.
Microsoft Visual J++ 6.0
This is an environment that I have never used for COM development and hence cannot verify if it can be used to develop pooled objects. The Platform SDK for Windows 2000 RC2 does have a Java sample, which leads me to believe that this environment can be used to develop COM objects that can be pooled.
Object Pooling is not for everyone
Object Pooling is often misunderstood as being a PANACEA for all performance and scalability ills. The title of this section serves to alert the reader to this very fact and provides some practical guidelines on when to use this service.
Specifically, Object Pooling is effective only if:
The benefits resulting from using Object Pooling must be thoroughly evaluated by testing the client application to use the object being pooled with and without this service. Another important consideration is to determine meaningful values for the various pool characteristics. Values for the 'Minimum pool size', 'Maximum pool size' and 'Creation timeout' will vary from application to application. Some factors that affect these values are hardware configuration, available precious resources, average number of clients, average client access time, maximum number of clients and expected response time.
This article has attempted to provide an overview of the COM+ Object Pooling service. It has clearly outlined the requirements for COM objects that can be pooled and also cautioned the reader to the fact that Object Pooling is not the silver bullet for achieving improvements in performance and scalability.
What do you think of this article?
You can also write a review. We will publish the best ones here on this article. Send your review to [email protected].
Mail a question to the author!!
As part of the IDevResource commitment to Open Publishing, all of our authors are available to answer all of your trickiest questions at Author Central. For information about the authors, or to mail a question, visit them at Author Central.
Contribute to IDR:
To contribute an article to IDR, a click here.