JITA Pool Revisited.
Before I get back to the POX/REST story, I promised a few folks to re-publish an updated version of my „JITA pool“ that I published over three years ago. Many people have used the technique that this utility class enables and have seen dramatic performance improvements with their Enterprise Services applications. And until Indigo ships, Enterprise Services (a.k.a. “COM+” or, shorter, “ES”) is still the most powerful application server technology on the Windows platform, so I think it deserves continued attention.
One of the common misconceptions around ES is that keep hearing is that it comes with such a significant overhead for each call that turns your server application into a fat, slow moving thing. And as things are, there are always two sides to every story and it turns out that if you use ES correctly, it is probably faster than most other cross-process technologies you can easily use from managed code. I write “probably”, because as with every distributed systems technology, “your mileage may vary” based on what your scenario is.
Before I show you the code, let’s take a quick dive into the ES/COM+ internals so that you understand what I mean with “use ES correctly”. To use technology in an optimal way you either need to understand how things work and how all technology pieces fit together or you need absolutely precise documentation that gives you the guidance you’d otherwise have to work out yourself. The Microsoft of the “COM+ era” (2000 and earlier) didn’t do so well on giving us either. And unfortunately it’s still very often the case that Microsoft’s technical marketing teams drop a technology like a hot potato once it has shipped and the launch events are over. And so ES still suffers from former negligence and in managed code land it’s clearly overshadowed by ASP.NET, Windows Forms and everything else that has design-time support and demos easily. Anyways, let’s dig in:
Assume you had a ServicedComponent and you’d call it from a client application in the following way:
MyComponent myComp = new MyComponent();
myComp.MyMethod()
Surprising to many is that it turns out that the innocent looking “new” is sometimes the more costly of these two code lines. Invoking and executing the functionality you’ve been implementing might in some cases indeed be significantly quicker than constructing the component instance. Why is that so? Because constructing ES/COM+ component instances is a lot of work. The picture above is a simplified illustration of the moving parts involved in the process. (Mind that I am simplifying the explanation a bit. I’ve already written about the internals in 2003, but in a bit different context.)
The COM+ infrastructure is essentially an aspect engine that allows plugging interception sinks into the call chain that leads from the client proxy to the server component instance. Whenever a (any) COM object is constructed on Windows 2000 (or later), the CoCreateInstance(Ex) API function will route the request through a chain of “activators” that are registered in the COM+ catalog, which is a simple ISAM database with very fast read-capabilities that has been custom-built for COM+. If an component isn’t “configured” (registered in a proper COM+ application), the activation process shortcuts out of the activator chain and creates a “normal” COM object, otherwise the activator chain is fully processed. “Activators” are nothing but proper COM components implementing a special interface ISystemActivator whose CLSID is registered in the COM+ catalog. In the illustration about the activators are depicted as yellow circles. Whenever a new instance must be created, each activator is polled for whether it has interest in contributing a “policy” (blue rectangles) into the interceptor chain. It does so by checking the COM+ catalog for whether the component or the application has been configured to use the feature the activator is responsible for. Conceptually, each COM+ feature (Transactions, Security, JITA, Object Pooling) therefore has an activator even though one activator may (and in fact does) do the work for multiple features and install multiple policies.
A policy is also a COM component (or two) implementing the IPolicy and IPolicyMaker interfaces. The former interface has the interceptor methods that are being invoked before and after the method call and before and after leaving the context, the latter has methods for actually installing that interceptor into the call chain. The interceptor methods in IPolicy are the ones where the COM+ functionality actually sits. The transaction policy will open/close new transaction scopes here, the object pooling will pool/depool components, the JITA policy connects/disconnects instances, and the tracker starts and stops its stopwatch to measure the call times. The resulting policies form a sink-chain, which means that any policy can absorb a call or change the environment before it lets the call proceed. The policy objects have context-affinity, which means that they are created anew for each context.
What that all means is that whenever you create a new instance of your serviced component, you don’t create just one object, you create a whole lot and I haven’t really gone into the additional managed code object instances that are created for the proxy/dispatcher trickery of hooking it all up to the Remoting infrastructure. In addition to all that context setup work, a cross-process connection (on-machine and off-machine) does, of course, require a DCOM/MS-RPC connection to be established, which comes with additional cost.
Mind that while this mechanism is theoretically extensible using arbitrary plug-ins, it’s not practically possible to do so. Contrary to the open extensibility model of Indigo that was specifically designed for 3rd party extensibility, the COM+ model is so tightly coupled and there are so many interdependencies between the respective behaviors that you would practically need to sit on the same floor with the development team to do anything meaningful with the extensibility features even if you had the header and IDL files for it. |
And looking at all that internal effort that’s hidden in the guts of COM+/ES, it is now not very surprising that that creating a component is pretty costly, is it?
The goal for optimizing a COM+/ES application must therefore be to eliminate as much of that cost as possible. And it turns out that two of the policies sitting in the server context stage are incredibly helpful in getting this done. The “Just In Time Activation” feature of COM+/ES is in fact a policy that will drop the reference to the object instance on its right (in the illustration above) once the completion-bit in the context properties is set. Before it does so, it calls the Deactivate() method if the component supports IObjectControl. The object-pooling policy will grab such a deactivated object and move it into a server-side object pool is the object agrees to that by returning true from CanBePooled(). Even though the policy chain (“context”) has no active instance sitting at its far end, it is alive and doing quite well. Whenever the next call is made on the proxy, the call comes through the chain, the “Just in Time Activation” policy will create a new instance and, if present, the object pooling policy will supply one from the pool. Otherwise a new instance is created. The key aspect here is that the entire context and all that context setup work is reused, even though there’s an entirely new or a recycled component acting on the server side.
This is quite similar to databases where you have connections (context) and statements (instance/call). A serviced component proxy is not just a proxy. It’s got the whole policy chain and the server connection behind it. And as we know, database connections are pretty expensive to establish as well and therefore the infrastructure you use to connect to them (ADO, System.Data, OLE DB) commonly gives you “connection pools” from which pre-established connections are recycled even though the API creates the illusion that you are creating new connections every time you call Connect() or Open().
Consequently and learning from the database folks, what we’ll do for optimizing ES is to steal that idea and build a connection pool. The downloadable file below contains an implementation of such a pool. The JustInTimeActivationProxyPool<T> class implements a simple, lockless, SMP safe pool of static size that keeps references to existing proxies. To create a pool on the client side, you will typically add a static field like JustInTimeActivationProxyPool<MyComponent> myComponentPool to your client implementation and initialize it appropriately. Once you’ve done that, you can obtain a proxy with Pop() and return the proxy after use with Push(). Because all serviced components you write in managed code are implicitly multi-threading aware (MTA), this works perfectly well in multi-threaded environments.
However, due to the connection-oriented nature of COM, there’s a bit of a complication in case the server application recycles (stops and restarts). In that case, all the proxies you have cached are stale and will throw an exception with the HRESULT 0x800706bf (E RPC CALL FAILED DNE), which means that the call couldn’t be made. In that case you need to create a new proxy and simply reissue the call. The two other special cases for COM+/ES (security errors and general network failures aside) are those where the target application has been paused or disabled, in which case the proxies are also invalid. The older versions of my pool class did not address these issues, but this new one does.
The example below shows the proper usage of the pool with the pool’s Using() method and an anonymous inline method. This technique allows me to call the user code in three different contexts that I can establish inside the implementation of Using(). The first is when the call were to be executed within a preexisting transaction context, which disallows the use of pooled proxies, the second is the normal Pop()/Push() bracket around the call and the third is the new/Push() exception case for when the proxy is detected to be stale.
[WebService(Name="CatalogQueriesWebService",Namespace=Namespaces.CatalogQueriesNamespaceUri)] |
The “requirement” for the server component is that Just-In-Time Activation is enabled (the [JustInTimeActivation] attribute on the serviced-component class will do that and the [Transaction] attribute with the TransactionOption.Supported/Required/RequiresNew optional will implicitly do that, too) , even though stateful components could be pooled as well with this technique.
Download the code for the class below and try it out.