Preface
I will be covering the COM+ Object Pooling service
in a two part article series. This first installment will introduce the topic
as well as describe how it works. Additionally, it will specify the
requirements for pooled objects and describe when it makes sense to use this
service.
Part II will focus on the development of a pooled
object using Visual C++ and ATL. It will also cover how to deploy the solution
and monitor statistics for pooled objects.
Terminology
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.
Introduction
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
-
Minimum pool size: This specifies the Low water
mark for the pool and defaults to ‘0’ (zero).
-
Maximum pool size: This specifies the High
water mark for the pool and defaults to ‘1048576’.
-
Creation timeout (ms): This specifies the time
to wait in order to satisfy a client’s request to create a object in the pool
and defaults to ‘60000’ ms. Once the number of objects in a pool reaches the
high water mark, additional client requests will have to wait until an object
in the pool becomes available. Since this wait period is uncertain, specifying
this characteristic allows better control over how long a client will wait to
acquire an object in the pool.
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
- Improving performance and scalability of applications
- Governing resources
Given the right circumstances, performance and
scalability of a COM+ application can be significantly improved by using Object
Pooling. This is possible because
- The minimum number of objects are pre-allocated and initialized when the
application starts but before any client requests come in. This number is
specified in the ‘Minimum pool size’ characteristic.
- Any time consuming initialization and resource acquisition is separated
from the actual work that the object performs for clients.
- The cost of acquiring expensive and precious resources is amortized across
multiple clients since objects are ideally reused.
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 is 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. Refer
to the platform SDK documentation for more details about this.
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 the ‘State
management for pooled objects’ section.
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 ‘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
initialization. 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
- Objects in the pool are homogeneous (enforced by COM+)
- Initialization of the object is expensive and uniform for all clients
- The object acquires and holds onto precious resource(s) like socket or
database connections during initialization.
- Clients use the pooled objects briefly and in rapid succession.
The benefits resulting from using Object Pooling
must be thoroughly evaluated by testing the client application to use the
object with and without pooling. 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.
Some more details…
The next two sections describe topics that may not
be of interest to those looking for an overview of COM+ Object Pooling. Hence I
have separated them out to be at the end of the article.
State management for pooled objects
A
pooled object's state typically comprises of
- Precious resources that it acquires during its creation and holds onto
throughout its lifetime and
- State that it acquires when activated in the context of a specific
client.
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 aggregrate 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 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 initialization.
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 parameterized 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 initialized.
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 some other
techniques to achieve the same behaviour.
Summary
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.
Part II will attempt to cover in-depth the
development of a COM object that leverages this service as well as a test
client application to put it through its paces.