Sharing Poolable Objects

[Previous] [Next]

So far, this chapter has discussed sharing threads, memory, and database connections across a set of clients. In addition to these types of resources, COM+ also provides a recycling mechanism for sharing a set of objects across a set of clients with a scheme known as object pooling. Object pooling can improve an application's scalability because it reduces the need to create and destroy objects throughout the lifetime of a process. I must state up front that Visual Basic isn't capable of producing components that create poolable objects. This limitation of Visual Basic is due to problems associated with thread affinity. However, you should still have a basic understanding of how object pooling works and why it's important.

Each pool represents a set of objects created from the same CLSID. Once client A has finished using an object, the COM+ runtime deactivates the object and places it back in the pool. When client B needs an object of the same CLSID, there's no reason to create another one. The COM+ runtime can simply take an object from the pool and activate it within the activity of client B. At a high level, object pooling is very similar to database connection pooling. Many different clients acquire and release a specific type of resource, and system-provided code matches up a small number of resources to a large number of clients.

Object pooling requires the use of just-in-time activation. When a client has finished using an object, the COM+ runtime must break the association between the client and the object. This is known as deactivation. When the client needs another object, the COM+ runtime must find another object and associate it with the client. This is known as activation. What's really neat about this scheme is that the client doesn't perceive that objects are being deactivated and reactivated. It simply acquires a connection to a logical object and invokes a series of method calls. The client has no idea that the physical object that executes the first method call might be different from one that executes the second method call.

Poolable objects are created from configured components with a JustInTimeActivation attribute setting of True. When COM+ creates a new context for an object that is configured for just-in-time activation, it adds a special Boolean flag known as the done bit, as shown in Figure 7-11. Every time a call returns from the object, the COM+ interception code examines the done bit. If the done bit is set to True, the COM+ runtime deactivates the object. Now, things get a little tricky because the client that created the object is holding onto a proxy that doesn't have an object on the other side. However, on the next method call the COM+ runtime performs a just-in-time activation to service the client's request.

click to view at full size.

Figure 7-11 Object pooling is controlled through just-in-time activation. A pooled object is typically activated and deactivated on each method call.

As you can see, the done bit is a pretty important part of this scheme. In most cases, the done bit should be set to True to release a poolable object back to the pool as soon as possible. You can set the done bit to True in two ways. One way is to configure a method-level attribute named AutoComplete. When this attribute is turned on, the interception code sets the done bit to True before executing the method. You can also manipulate the done bit programmatically. The IContextState interface has a method named SetDeactivateOnReturn that allows you to set the done bit to either True or False. Note that the ObjectContext interface has a method named SetComplete that sets the done bit to True in addition to casting a vote to commit the current transaction.

Even though you can't create poolable objects using Visual Basic, you still need to understand how the done bit works if you plan to create transactional components. Transactional components are like poolable components in that they also require just-in-time activation. In Chapter 8, I'll revisit this topic and spend more time discussing the need to deactivate Visual Basic objects running in a transaction.

Why Is Object Pooling Important?

The primary motive behind object pooling is that some middle-tier objects are expensive to continually create and initialize. If a set of these "expensive" objects can be created once and recycled across a set of clients, the server computer can conserve the processing cycles associated with this ongoing object creation and initialization. Think of a pooled object in the same way that you think of any other shared middle-tier resource.

You shouldn't expect that any component will experience a significant performance boost when you add support for object pooling. While object pooling saves processing cycles associated with the continual creation and destruction of objects, it requires additional processing cycles for managing the pool. The system must insert an object into the pool after deactivation and must fish out another object for every just-in-time activation. For most lightweight components, the difference between using pooling and not using pooling is marginally small. However, certain types of components benefit dramatically from object pooling.

During the design phase you should ask yourself whether a component is a good candidate for object pooling. Specifically, you should ask whether the creation and initialization of this object type is both expensive and generic. If the answer is yes, object pooling can be beneficial. If the answer is no, object pooling probably won't be very valuable.

Let's look at an example of a component that can benefit from object pooling. Assume that you're designing a component that will establish a connection to a mainframe application. It will take each object about 5 seconds to establish the connection. That's the expensive part. However, once the object establishes the connection to the mainframe application, the object and its connection can be used across many different clients. That's the generic part. The object holds no client-specific state. It's equally useful to any client in its initialized state.

Figure 7-12 shows the Activation tab of the component's Properties dialog box (accessible through the Component Services administrative tool) where you can configure the object pooling settings for a component in a COM+ application. (These settings are always disabled for a Visual Basic component.) Notice that the Minimum Pool Size is set to 10. When you configure your component like this, the COM+ runtime automatically creates and initializes 10 objects when the application is launched. The initialization time for these objects is then amortized over the lifetime of the application.

Figure 7-12 You can configure a C++ component for object pooling by using the component's Properties dialog box.

The primary advantage of object pooling is that each client doesn't have to wait 5 seconds while an object in the middle tier establishes a connection. The client simply acquires an object with an existing connection from the pool. In a scenario such as this, object pooling can significantly improve client response times as well as the application's overall throughput.

In addition to saving processing cycles associated with object creation and initialization, object pooling can also provide resource throttling. Throttling provides a way to limit the number of clients that use a set of objects and their associated resources. For instance, you might want to limit the maximum number of outstanding connections to the mainframe application at any one time. By configuring the component to be throttled, you can easily do so.

The Maximum Pool Size setting restricts how many users can use the component at any one time. If you specify a setting of 25, only 25 clients can use the component at a time. Once 25 clients have activated objects, any other clients will block until one of the other clients releases an object back to the pool. A blocked client will simply wait for the next available object.

As long as the done bit is set, an object is returned to the pool whenever a method returns to a client. Setting the done bit isn't required, but it's preferred in most situations. When the done bit is set, each client acquires, uses, and releases an object on a per-method-call basis. In our example, it means that only 25 clients can run method calls at the same time. If these objects don't set the done bit, the maximum size indicates how many clients can create connections to an object. As you can see, setting the done bit allows for throttling and provides higher levels of concurrency than you'd see were the done bit not set.

In addition to a maximum pool size, a component also has a configurable Creation Timeout setting. A client will block only for the duration of the creation timeout. If a client times out while attempting to activate an object from the pool, it experiences a runtime error. You must then deal with this by providing an error handler for the code that attempts to activate the object.

Let's summarize what object pooling is all about. It's about reusing objects for which initialization is both expensive and generic. I used an example of a component that must establish a connection to a mainframe application. There are many other situations in which you might encounter components that also meet this criterion. However, it's important to remember that a component can't really benefit from object pooling if it doesn't meet this criterion.

Object Pooling vs. Database Connection Pooling

Object pooling solves a few problems associated with the database connection-pooling scheme built into OLE-DB. With database connection pooling, you can't throttle the number of available connections. Object pooling is better because you tune each process to use the optimal number of connections. The COM+ runtime is your friend because it automatically blocks one client until another client releases one of the pooled objects.

Also note that poolable objects can detect when the connection they hold has gone bad. This means that a poolable object can establish a new connection when the existing connection has been dropped. This makes it possible to design poolable objects that are self-healing. You can hide certain problems that can't be hidden by using pooled database connections. When a SQL Server computer is rebooted and a pool of database connections has gone bad, the code that uses these connections requires an error-handler. Code that uses pooled objects can be shielded from such problems.

If these issues seem important, you might find it advantageous to add poolable components to your application. Poolable components can encapsulate the code that establishes and manages a set of connections to a database server or a mainframe application. You can also use them to establish sockets-based connections to other applications. However, a poolable component must meet several requirements. It must be created with C++, its ThreadingModel attribute must be set to Neutral or Both, and it can't exhibit thread affinity. There are a few other requirements as well.

Using C++ along with the Active Template Library (ATL) is the easiest and most straightforward way to create COM+ components that support pooling. The ATL Wizard does most of the work to set things up properly. However, adding support for a poolable component that's involved in a transaction dramatically increases the coding requirements. This is one area of COM+ development in which you unfortunately can't use Visual Basic.

Summary

So, what have we learned? An application's ability to scale starts at the process level. And within each process there must be code dedicated to sharing resources as efficiently as possible. Some of this code is supplied by Microsoft, and some of it you must write.

Sharing data across a lot of users is hard. Concurrency and data consistency are antagonistic by nature. When you write code to get more of one, it usually means you get less of the other. The key to building the fastest possible application is to understand when locking must occur and to avoid locking when it's not necessary. I'll build on this theme in the next chapter, which covers transactions.

And speaking of COM+ transactions, you already have a pretty good head start learning how they work. Knowing about activities, just-in-time activation, and the done bit is critical to understanding the transactional programming model. These concepts are important if you intend to design and create transactional components.

Over the last few years, Visual Basic has advanced impressively in its ability to produce middle-tier components. If fact, many companies refuse to write their business logic and database access code in any other language. However, some areas of COM+ development still require C++. If you need a poolable component or some code that's written directly against OLE-DB, you must bring in the guy who's not afraid of pointers and semicolons. If he can give you what you need, pay him whatever he asks and definitely don't ask him to explain what he's done for you.



Programming Distributed Applications with COM+ and Microsoft Visual Basic 6.0
Programming Distributed Applications with Com and Microsoft Visual Basic 6.0 (Programming/Visual Basic)
ISBN: 1572319615
EAN: 2147483647
Year: 2000
Pages: 70
Authors: Ted Pattison

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net