Object Pooling

The idea behind object pooling is just as the name implies: COM+ can maintain a pool of objects that are already created and ready to serve clients. The pool is created per object type; different objects types have separate pools. You can configure each component type pool by setting the pool parameters on the component’s properties Activation tab (as shown in Figure 3-3). With object pooling, for each object in the pool, you pay the cost of creating the object only once and reuse it with many clients. The same object instance is recycled repeatedly for as long as the containing application runs. The object’s constructor and destructor are each called only once. Object pooling is an instance management technique designed to deal with the interaction pattern of Internet clients—numerous clients creating objects for every request, not holding references on the objects, but releasing their object references as soon as the request processing is done. Object pooling is useful when instantiating the object is costly or when you need to pool access to scant resources. Object pooling is most appropriate when the object initialization is generic enough to not require client-specific parameters. When using object pooling, you should always strive to perform in the object’s constructor as much as possible of the time-consuming work that is the same for all clients, such as acquiring connections (OLEDB, ADO, ODBC), running initialization scripts, initializing external devices, creating file handles, and fetching initialization data from files or across a network. Avoid using object pooling if constructing a new object is not a time-consuming operation because the use of a pool requires a fixed overhead for pool management every time the client creates or releases an object.

Any COM+ application, whether a server or a library application, can host object pools. In the case of a server application, the scope of the pool is the machine. If you install proxies to that application on other machines, the scope of the pool can be the local network. In contrast, if the application is a library application, then a pool of objects is created for each client process that loads the library application. As a result, two clients in different processes will end up using two distinct pools. If you would like to have just one pool of objects, configure your application to be a server application.

Pooled Object Life Cycle

When a client issues a request to create a component instance and that component is configured to use object pooling, instead of creating the object, COM+ first checks to see if an available object is in the pool. If an object is available, COM+ returns that object to client. If there is no available object in the pool and the pool has not yet reached its maximum configured size, COM+ creates a new object and hands it back to the creating client. In any case, once a client gets a reference to the object, COM+ stays out of the way. In every respect except one, the client’s interaction with the object is the same as if it were a nonpooled object. The exception occurs when the client calls the final release on the object (when the reference count goes down to zero). Instead of releasing the object, COM+ returns it to the pool. Figure 3-4 describes this life cycle graphically in a UML activity diagram.[1]

A pooled object life cycle

Figure 3-4.  A pooled object life cycle

If the client chooses to hold onto the pooled object for a long time, it is allowed to do so. Object pooling is designed to minimize the cost of creating an object, not the cost of using it.

Configuring Pool Parameters

To use object pooling for a given component, you should first enable it by selecting the “Enable object pooling” checkbox on component’s Activation tab. The checkbox allows you to enable or disable object pooling. The two other parameters let you control the pool size and the object creation timeout. The minimum pool size determines how many objects COM+ should keep in the pool, even when no clients want an object. When an application that is configured to contain pools of objects is first launched, COM+ creates a number of objects for each pool equal to the specified minimum pool size for the application. If the minimum pool size is zero, COM+ doesn’t create any objects until the first client request comes in. Minimum pool size is used to mitigate sudden spikes in demand by having a cache of ready-to-use, initialized objects. The minimum pool size must be less than the maximum pool size, and the Component Services Explorer enforces this condition.

The maximum pool size configuration is used to control the total number of objects that can be created, not just how many objects the pool can contain. For example, suppose you configure the pool to have a minimum size of zero and a maximum of four. When the first creation request comes in, COM+ simply creates an object and hands it over to the client. If a second request comes in and the first object is still tied up by the first client, COM+ creates a new object and hands it over to the second client. The same is true for the third and fourth clients. However, when a fifth request comes along, four objects are already created and the pool has reached its maximum potential size, even though it is empty. Once you reach that limit and all objects are in use, further clients requests for objects are blocked until an object is returned to the pool. At that time, COM+ hands it over to the waiting client. If, on the other hand, the client waited for the duration specified in the timeout field, the client is unblocked and CoCreateInstance( ) returns the error code CO_E_ACTIVATIONFAILED_TIMEOUT (not E_TIMEOUT, as documented in the COM+ section of the MSDN). COM+ maintains a queue for each pool of waiting clients to handle the situation in which more than one client is blocked while waiting for an object to become available. COM+ services the clients in the queue on a first-come, first-served basis as objects are returned to the pool. A creation timeout of zero causes all client calls to fail, regardless of the state of the pool and availability of objects.

If the pool contains more objects than the configured minimum size, COM+ periodically cleans the pool and destroys the surplus objects. There is no documentation of when or how COM+ decides to do the cleanup.

Deciding on the minimum and maximum pool size configuration depends largely on the nature of your application and the work performed by your objects. For example, the pool size can be affected by:

  • Expected system load highs and lows

  • Performance profiling done on your product to optimize the usage of resources

  • Various parameters captured during installation, such as user preferences and memory size

  • The number of licenses your customer has paid for; you can set the pool size to that number and have an easy-to-manage licensing mechanism

In general, when configuring your pool size, try to balance available resources. You usually need to trade memory used to maintain a pool of a certain size and the pool management overhead in exchange for faster client access and use of objects.

Pooled Object Design Requirements

When you want to pool instances of your component, you must adhere to certain requirements and constraints. COM+ implements object pooling by aggregating your object in a COM+ supplied wrapper. The aggregating wrapper’s implementation of AddRef( ) and Release( ) manage the reference count and return the object to the pool when the client has released its reference. Your component must therefore support aggregation to be able to use object pooling. When you import a COM component into a COM+ application, COM+ verifies that your component supports aggregation. If it does not, COM+ disables object pooling in the Component Services Explorer. If you implement your object using ATL, make sure your code does not contain the ATL macro DECLARE_NOT_AGGREGATABLE( ) , as this macro prevents your object from being aggregated. By default, the Visual C++ 6.0 ATL Wizard inserts this macro into your component’s header file when generating MTS components. You must remove this macro to enable object pooling (it is safe to do so—there are no side effects in COM+).

Another design point to pay attention to is your pooled object’s threading model. A pooled object should have no thread affinity of any sort—it should make no assumption about the identity of the thread it executes on, or use thread local storage, because the execution thread can be different each time the object is pulled from the pool to serve a client. The pooled object therefore cannot use the single-threaded apartment model (STA) because STA objects always require execution on the same thread. When you import a component to a COM+ application, if the component’s threading model is marked as apartment (STA), COM+ disables object pooling for that component. A pooled object can only use the free multithreaded apartment model (MTA), the both model, or the neutral threaded apartment model (NTA, covered in Chapter 5). If performance is important to you, you may want to base your pooled component’s threading model on your clients’ threading model. If your clients are predominantly STA-based, mark your component as Both so that it can be loaded directly in the client’s STA. If your clients are predominantly MTA based, mark your component as either Free or Both (the Both model also allows direct use by STA clients). If your clients are of no particular apartment designation, mark your component as Neutral. For most practical purposes, the neutral-threading model should be the most flexible and performance-oriented model. Table 3-1 summarizes these decisions.

Table 3-1.  Pooled object threading model

Clients threading model

Recommended pooled object threading model

No particular model

NTA

STA

Both

MTA

Both/MTA

Both

Both

NTA

NTA

Deciding not to use STA has two important consequences:

  • Pooled objects cannot display a user interface because all user interfaces require the STA message loop.

  • You cannot develop pooled objects using Visual Basic 6.0 because all COM components developed in Version 6 are STA based and use thread local storage. The next version of Visual Basic, called Visual Basic.NET, allows you to develop multithreaded components.

Object Pooling and Context

When a pooled object is placed in the pool, it does not have any context. It is in stasis—frozen and waiting for the next client activation request. When it is brought out of the pool, COM+ uses its usual context activation logic to decide in which context to place the object—in its creator’s context (if the two are compatible) or in its own new context. From the object’s perspective, it is always placed in a new context; different from the one it had the last time it was activated. Objects often require context-specific initialization, such as retrieving interface pointers or fine-tuning security. Object pooling only saves you the cost of reconstructing a new object and initializing it to generic state. Each time an object is activated, you must still do a context-specific initialization, and you benefit from using object pooling only if the context-specific initialization time is short compared to that of the object’s constructor. But when context-specific initialization is used, how does the object know it has been placed in a new context? How does it object know when it has been returned to the pool? It knows by implementing the IObjectControl interface, defined as:

interface IObjectControl : IUnknown 
{
   HRESULT Activate(  );
   void    Deactivate(  );
   BOOL    CanBePooled(  );
};

COM+ automatically calls the IObjectControl methods at the appropriate times. Clients of your object don’t ever need to call these methods.

COM+ calls the Activate( ) method each time the object is pulled from the pool to serve a client—just after it is placed in the execution context, but before the actual call from the client. You should put context-specific initialization in the Activate( ) method. Activate( ) is your pooled object’s wakeup call—it tells it when it is about to start serving a new client. When using Activate( ), you should ensure that you have no leftovers in your object state (data members) from previous calls, or from a state that was modified from interaction with previous clients. Your object should be indistinguishable from a newly created object. The state should appear as if the object’s constructor was just called.

COM+ calls Deactivate( ) after the client releases the object, but before leaving the context. You should put any context-specific cleanup code in Deactivate( ).

When object pooling is enabled, after calling the Deactivate( ) method, COM+ invokes the CanBePooled( ) method to let your object decide whether it wants to be recycled. This is your object’s opportunity to override the configured object pooling setting at runtime. If your object returns FALSE from CanBePooled( ), the object is released and not returned to the pool. Usually, you can return FALSE when you cannot initialize the object’s state to that of a brand-new object, because of an inconsistency or error, or if you want to have runtime fine tuning of the pool size and the number of objects in it. In the most cases, your implementation of CanBePooled( ) should be one line: return TRUE;, and you should use the Component Services Explorer to administer the pool. Implementing IObjectControl is not required for a pooled object. If you choose not to implement it and you enable object pooling, your object is always returned to the pool after the client calls Release( ) on it.

Figure 3-5 emphasizes the calling sequence on a pooled object that supports IObjectControl. It shows when COM+ calls the methods of IObjectControl and when the object is part of a COM+ context.

The life cycle of a pooled object using IObjectControl

Figure 3-5.  The life cycle of a pooled object using IObjectControl

Finally, IObjectControl has two abnormalities worth mentioning: first, the interface contains two methods that do not return HRESULT, the required returned value according to the COM standard of any COM interface. IObjectControl's second abnormality is that only COM+ can invoke its methods. The interface is not accessible to the object’s clients or to the object itself. If a client queries for the IObjectControl interface, QueryInterface( ) returns E_NOINTERFACE.



[1] If you are not familiar with UML activities diagrams, read UML Distilled by Fowler and Scott (Addison Wesley, 1997). Chapter 9 in that book contains a detailed explanation and an example.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset