Click Here to Install Silverlight*
United StatesChange|All Microsoft Sites
MSDN
|Developer Centers|Library|Downloads|Code Center|Subscriptions|MSDN Worldwide
Search for


Advanced Search
MSDN Home > MSJ > December 1999
December 1999

Microsoft Systems Journal Homepage

Basic Instincts

Ted Pattison is an instructor and researcher at DevelopMentor (http://www.develop.com), where he manages the Visual Basic curriculum. Ted is the author of Programming Distributed Applications with COM and Visual Basic 6.0 (Microsoft Press, 1998).

There's one mantra that we have heard consistently since the emergence of Microsoft® Transaction Services (MTS): always call SetComplete at the end of any method in a middle-tier object. However, I believe you should only call SetComplete when it truly makes sense.
A call to SetComplete tells the system that you'd like to deactivate your object when the current method call returns. This transparent deactivation of MTS/COM+ objects has been called stateless programming. In the short history of MTS, there has been a good deal of confusion about why statelessness is an essential aspect of the programming model. Some books and articles have even gone so far as to suggest that stateless programming is about reclaiming memory in the middle tier. They argue that destroying objects and reclaiming memory results in higher levels of scalability due to more efficient resource usage. This argument is both confusing and inaccurate. It's time to set the facts straight.
As it turns out, there are two compelling arguments for calling SetComplete. One argument has to do with controlling the outcome of a transaction, the other with object pooling. If your component isn't involved in a transaction and doesn't support object pooling, a call to SetComplete has either no effect or a negative effect. Blindly following the SetComplete mantra without understanding the issues at hand isn't a very sound programming practice.

Writing MTS Transactions

MTS provides two important transactional methods: SetComplete and SetAbort. These methods are critical for controlling the outcome of an MTS transaction. While it's important to know the specifics of when and how to call those methods inside the scope of an MTS transaction, I'm not going to discuss those details in this column (see my article in the October 1999 issue of MSJ for details). Instead, I'll focus on what happens at the end of a transaction. Whether a transaction is committed or rolled back, every object involved will be deactivated when the transaction is released.
When writing an MTS transaction, your objects will be acquiring and releasing locks on various resource managers. Through the use of SetComplete and SetAbort the MTS runtime provides support to acquire locks as late as possible and to release them as early as possible. Minimizing lock times for transactions is one of the most effective ways to optimize throughput and increase scalability in a distributed OLTP system. The recommended way to write MTS transactions is to call either SetComplete or SetAbort in every method in the root object.
When a method in the root object returns after calling either SetComplete or SetAbort, the MTS interception layer releases the transaction and tells the involved resource managers to release all the locks that they've acquired. The interception layer also deactivates every object associated with the transaction as part of the cleanup process. Furthermore, if the objects inside the transaction do not support object pooling, deactivation also results in the destruction of those objects. This means that the root object and any secondary objects will be created and destroyed inside the scope of a single method call.
It's important to note that the base client can't tell that the root object is being deactivated and destroyed. The client holds a persistent connection to the root object's context wrapper, as shown in Figure 1. Every time the client calls a method, another root object (and possibly a set of secondary objects) is created and destroyed inside the scope of a new transaction. This transparent creation and destruction of objects is a side effect of just-in-time (JIT) activation.

Figure 1  Object Creation in an MTS Transaction
Figure 1 Object Creation in an MTS Transaction

So why is it important for MTS to deactivate all the objects inside a transaction? It ensures the proper semantics of the transaction. The idea is that an object in an MTS transaction can see a data item in a consistent state only while the resource manager is holding a lock. If an MTS object were to hold a copy of a data item in the middle tier after the lock has been released, another transaction could modify the original data item inside the resource manager. The original data item and the copy would thus get out of sync and violate the ACID rules of a transaction.
MTS requires the deactivation of transactional objects so that any copy of a data item must be thrown away when the resource manager releases its locks. MTS requires you to release every transactional object so you don't compromise the consistency of your system. A call to SetComplete simply forces the end of the transaction and the inevitable deactivation of your objects.

SetComplete and Object Pooling

You know that a call to either SetComplete or SetAbort informs the interception layer that you'd like to deactivate your object when the current method call returns. In MTS 2.0 and earlier, objects were always destroyed by the system immediately after deactivation. Starting with Windows® 2000, COM+ objects can participate in object pooling. This means that an object can be recycled after it's been deactivated by the system. Object pooling puts a new spin on why you might like to call SetComplete. A call to SetComplete can speed up deactivation and return your object to a shared pool in a more timely manner.
The primary motivation 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 system can conserve the processing cycles associated with this ongoing object creation and initialization. Think of a pooled object as a shared middle-tier resource. By calling SetComplete in a pooled object, you can acquire it as late as possible and release it as early as possible.
You should not expect that any components will experience a significant performance boost when you add support for object pooling. While it's true that object pooling saves processing cycles associated with the continual creation and destruction of objects, it also has a cost. Pooling requires additional processing cycles for managing the pool, which are less than those typically required to create a lightweight object. The system must insert an object into the pool after deactivation, and it must fish another object out for every JIT activation. For components that do not process transactions, the difference between using pooling or not using pooling will be marginal. However, a small number of components will benefit dramatically from object pooling.
During the design phase you should think about whether a component is a good candidate for object pooling. Ask yourself, "Is the creation and initialization of this object type both expensive and generic?" If the answer is yes, then object pooling can be beneficial. If the answer is no, object pooling probably will not be very valuable.
Let's look at an example of a component that can benefit from object pooling. Say you're designing a component that's going to establish a connection to a mainframe application. It's going to take each object about five seconds to establish the connection. That's the expensive part. Once the object establishes the connection to the mainframe application, it can be used across many clients. That's the generic part. The object holds no client-specific state; it is equally useful to any client in its initialized state.

Figure 2 Object Pooling Settings
Figure 2 Object Pooling Settings

Figure 2 shows the Activation tab in the component properties dialog in the Component Services Explorer. This dialog allows you to configure the object pooling settings for a component in a COM+ application. Notice that the Minimum pool size has been set to 5. Once you've configured your component this way, the COM+ runtime will automatically create and initialize five objects when the application is launched. The initialization time for these objects is then amortized over the lifetime of the application.
What is the primary advantage of object pooling? Each client doesn't have to wait five seconds while an object in the middle tier establishes a connection. Each client simply acquires an object from the pool with an established connection. In a scenario like this, object pooling can significantly improve the throughput and increase the scalability of a COM+ application.
In addition to saving processing cycles that would be associated with object creation and initialization, object pooling can also be used to provide object throttling. Throttling is a way to limit the number of clients who are utilizing 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. Configuring the component to be throttled is an easy solution to this problem.
The maximum pool size restricts how many users are able to use the component at any one time. If you set the maximum pool size to 10, only 10 clients can use the component at a time. Once 10 clients have activated objects, any other clients will block until one of the other clients returns an object to the pool. A blocked client will simply wait for the next available object.
As long as these pooled objects call SetComplete, they are returned to the pool whenever a method returns to a client. Each client acquires, uses, and releases an object on a per-method call basis. In this example, it means that only 10 clients can be running method calls at the same time. If these objects don't call SetComplete, the maximum size indicates how many clients can create connections to an object. As you can see, calling SetComplete gives you throttling while providing higher levels of concurrency.
In addition to a maximum pool size, a component also has a configurable creation timeout. A client will only block for the duration of the creation timeout. If a client times out while attempting to activate an object from the pool, it will experience a runtime error. You must then deal with this by writing an error handler in the code that will attempt to activate the object.
Now let me summarize what object pooling is all about: reusing objects in which initialization is both expensive and generic. Besides my example of a component that must establish a connection to a mainframe application, there are many other situations where you might encounter components that meet this criteria. Object pooling and calls to SetComplete will increase scalability in this type of component. However, it's important to remember that a component will not really benefit from object pooling if it doesn't meet these criteria.

How Visual Basic Fits In

A COM+ component must meet a fairly rigid set of requirements to support object pooling. Unfortunately, Visual Basic® 6.0 can only create components that are apartment threaded. Furthermore, all Visual Basic-based objects exhibit thread affinity due to the usage of thread local storage in Visual Basic. These threading limitations, along with a few other shortcomings, prevent Visual Basic-based objects from participating in the object pooling scheme of COM+. When you install a Visual Basic component into a COM+ application, the object pooling settings will always be disabled; there's nothing you can do to make COM+ pool your objects. Using C++ along with the ActiveX® Template Library is the easiest and most straightforward way to create COM+ components that support pooling.
I hope that the Visual Basic development team will eventually enhance their product to support components that meet all the requirements for object pooling. Until that time, a call to SetComplete in a Visual Basic-based component will always result in the destruction of your object. If you are programming in Visual Basic, your only motivation for calling SetComplete or SetAbort is to control the outcome of a transaction.
If you set a component's transaction support property to Doesn't Support Transactions and you don't call SetComplete or SetAbort, your objects will remain alive and thus can maintain client-specific state across method calls. This type of component is known as a stateful component.
Many people believe that it's never acceptable to create stateful components for MTS or COM+. Quite simply, they're wrong. Those who suggest that stateful components shouldn't be used in MTS or COM+ are simply repeating the mantra they've heard from others. Don't let anyone convince you that stateful components don't have a place in MTS and COM+ application design. The most common misconception is that stateless components scale better due to a more efficient utilization of middle-tier memory and physical resources. Let's put an end to all the confusion once and for all.

Figure 3 Connection Between an MTS Object and a Client App
Figure 3 Connection Between an MTS Object and a Client App

Some argue that calling SetComplete is beneficial because it aggressively reclaims memory in the middle tier. Figure 3 shows the required pieces in a connection between an MTS object and a client application running across the network. When you call SetComplete and destroy an MTS object, you are not releasing either the server-side stub or the context wrapper. These two pieces add up to about 1KB of memory. (Take a look at Don Box's column in the March 1998 issue of MSJ for more details on this figure.)
In many cases the memory for the object you're destroying will be much smaller than the ongoing memory requirements for the stub and the context wrapper. For example, if you call SetComplete on a Visual Basic-based object that's 250 bytes, you're only reclaiming 20 percent of the memory that the client held in the first place. If you want to reclaim all the memory, you must destroy the stub and context wrapper by releasing the reference from the client application.
Also note that calling SetComplete does nothing with respect to MTS thread management that will improve your application's performance or concurrency. MTS allocates single-threaded apartment (STA) threads to clients by associating the context wrapper with an MTS activity. You do not break the association between a client and an STA thread by calling SetComplete. You can only do that by releasing the entire connection from the base client and releasing both the server-side stub and the context wrapper.

Creating Stateful Components

There are a few situations where stateful components make sense. Let's look at a common example. Assume you have an MTS application where base clients are creating objects from across the LAN. In this scenario, stateful components may have some advantages over a stateless one.
Consider the disadvantages of calling SetComplete and destroying your object every time you call a method. First, you are wasting processing cycles by destroying and recreating objects. Second, you must pass initialization parameters and reinitialize one or more objects in every method call.
For example, if you are designing a stateless customer component, you must pass the primary key or some other logical ID for the customer in each method call. Not only is this tedious when it comes to defining methods, but it has a definite runtime cost that decreases scalability. Method calls require more parameters, which may result in more network traffic if the size of the parameters exceed the network packet size. Every call requires additional processing cycles from the server for object creation and initialization. If your object is designed so that JIT activation requires complex calculations or database access, then calls to SetComplete can significantly reduce your application's performance. Note that these initializations usually can be placed within the appropriate transaction scope or maintained in an appropriate resource manager or dispenser.
From the design perspective, stateless programming is usually more difficult than stateful programming. If you can't maintain client-specific state inside MTS, your approach becomes more convoluted. You must either bring all the state back to the client, or you must go to the database more often. A stateful MTS object allows you to maintain a reasonable amount of client-specific state in the middle tier.
I don't mean to suggest that you can always maintain client-specific state in the middle tier. However, in scenarios where caching client-specific state is appropriate, stateful components make the application's design much more straightforward. After all, you can simply calculate how much memory each client needs and then multiply that number by your expected number of clients. Buy more memory for your servers and save yourself a few headaches during the design phase.
As you can see, you must carefully weigh the alternatives when deciding whether you really need stateless components. Objects that are stateful definitely have a place in the MTS and COM+ programming model. A stateful object can hold the client-specific state and save valuable processing cycles that are required to create and initialize new instances. You can also use stateful objects to accomplish many programming tasks that are impossible with stateless objects if you cannot use other resources such as Message Queuing or COM+ asynchronous calls. The bottom line is that you should know when to call—and when not to call—SetComplete. Don't simply call it because it's trendy.

Have a burning issue to resolve in Visual Basic? Send email to Ted at tedp@sublimnl.com.

From the December 1999 issue of Microsoft Systems Journal. Get it at your local newsstand, or better yet, subscribe.

© 1999 Microsoft Corporation. All rights reserved.
Terms of Use
.     Privacy Policy.

© 2016 Microsoft Corporation. All rights reserved. Contact Us |Terms of Use |Trademarks |Privacy & Cookies
Microsoft