Take Advantage of MTS in
Your Distributed System with Custom Resource Dispensers
| Any large system must be able to rely on common services. By building custom resource dispensers, you can use MTS services and maintain enough flexibility for business components. You can choose what to implement in your resource dispenser and when you want to put it in.|
This article assumes you're familiar with COM, ATL, and MTS|
Maros Cunderlik is a Lead Analyst at 3M. He focuses on application design and distributed component architectures. Maros can be
reached at email@example.com..|
An increasing number of
developers use Microsoft® Transaction Server (MTS) as a new architecture for COM programming. The benefits of a runtime framework that provides
common services to the components are clear: easier transaction management across process and machine boundaries, pooling of common resources, and consistent state management.
When implementing any large system you must be able to rely on common services. Large and distributed systems rarely get built from scratch. More likely, the new system is required to access legacy or nonstandard resources such as indexed files, socket connections, queues, and DBMS with proprietary APIs. These resources are often nontransactional and, at least initially, do not need to participate in distributed transactions. Of course, MTS cannot provide out-of-box pooling and transaction propagation for such resources.
MTS does offer a standard model for managing resource pools. By building custom resource dispensers, you can both use MTS services and also maintain enough flexibility for business components. As a result, you can choose what functionality to implement in your resource dispenser and when you want to put it in. For example, you can decide that the dispenser will initially only provide pooled access to the underlying durable resources that do not support transactions. Later, when the resource manager that supports transactions for this resource is available, you can easily modify the existing dispenser to automatically enlist the shared resources in transactions.
Let's explore the MTS resource management model. I will describe the main entities of and activities involved in the MTS resource pooling framework. I'll also discuss details and major responsibilities of resource dispensers. Finally, I will show how to implement the custom resource dispenser in C++ and COM.
Coding for MTS is fundamentally different in several ways from traditional COM programming. There is support for two-phase commit transaction management, resource pooling, and thread management. Also, MTS extends COM by forcing you to be explicit about how your components manage their state and what their transactional requirements are. Since the efficient resource pooling mechanism reduces the costs of acquiring resources, MTS components should aggressively release acquired resources. For more about the MTS programming model, see David Chappell's article "How Microsoft Transaction Server Changes the COM Programming Model" in the January 1998 issue of MSJ.
As MTS components declare their transaction expectations, the MTS runtime environment automatically initiates transactions. The transaction information determines how resources participate in transactions. For every component, MTS creates a corresponding context object that contains information such as the transaction identity, the current activity identifier, and the security ID of the caller. The context object is loaded as a part of MTS Executive (mtxex.dll). In addition to holding the context object, the MTS Executive participates in transaction processing on behalf of the component. The components and MTS Executive then typically execute in a separate host process provided by MTS (mtx.exe).
To carry out distributed, two-phase commit transactions, MTS uses OLE Transactions technology. The OLE Transactions model defines three entities in the transaction processing: the client application, the resource manager, and the transaction manager. Microsoft provides an implementation of the transaction manager called MS DTC. A resource manager (RM) is basically anything that manages persistent, durable resources such as a database or file system. In general, the RM allows transactional access to the underlying data. To be OLE Transactions-compliant, the RM must implement two COM interfaces (IResourceManagerSink and ITransactionResourceSync), and follow defined commit and recovery protocols. The clients communicate with the resource manager via the resource manager proxyan in-process, client-side representation of the RM.
In contrast with the resource managers, the resource dispensers (RDs) manage nondurable shared data. The resource dispensers keep a pool of resources that are loaded into memory. These resources are dynamically created as needed, and are not persisted. As soon as the resource dispenser shuts down, all of its data is lost. Typical resources include the database connections, network and socket connections, and various memory structures and blocks. Since the connections to the RM are in fact a shared and transient resource, you can build MTS RDs to manage these connections. This is demonstrated by the world's most famous RD: the ODBC driver manager. While SQL Server 6.5 itself is the resource manager that holds persistent data, the ODBC driver manager is the resource dispenser that provides a pool of shared connections to the corresponding resource manager. The resource dispensers request information about the current transaction context from the dispenser manager. The dispenser manager (mtxdm.dll) also manages the actual pools of resources, and ensures that all active resources are properly enlisted in the client's transaction.
|Figure 1 MTS Resource Management Architecture|
Figure 1 illustrates how the MTS components, MS DTC, and MTS participate in carrying out transactions. When MTS loads the MTS component marked as transactional, the MTS Executive automatically contacts MS DTC to begin the transaction. As clients start acquiring resources, the resource dispensers are loaded. The dispensers first register with the dispenser manager, which in turn creates the initially empty resource pool. As the application component requests resources, the dispenser manager assigns the resources from the pool. Before returning the resource to the client, the dispenser manager examines the transaction requirements, and if necessary, it instructs the dispenser to enlist the resource in the current transaction. The application component then performs a series of requests on the resource using resource dispenser interfaces.
As soon as the MTS component does not need a resource, it can release it. The resource will be reset and placed back into the pool. On an application's request to commit (or abort), the MTS Executive will initiate the two-phase commit by contacting MS DTC. MS DTC will take over and coordinate the commit process with all registered resource managers. Ultimately, the transaction is either successfully committed, or all changes are rolled back.
So far I've talked about scenarios in which the objects registered with the MTS are the only clients. The resource dispenser must also be ready to service objects running outside of MTS. Since the dispenser manager is capable of running without the MTS Executive (that is, outside of MTS), the client objects running without MTS on the server where MTS is installed will also benefit from the resource pooling. In this case, there is no current transaction, so the resource dispenser will never be instructed to enlist its resources in transactions.
As I described earlier, the MTS resource dispensers manage nondurable shared data. The RD has the following fundamental responsibilities:
- Participate in managing a pool of connections to the RM. Create, destroy, reset, and rate resources.
- Automatically enlist resources in the application's transaction.
- Act as the RM proxy. Provide the client MTS application with a set of public interfaces, or APIs to access the underlying RM.
The RD and the dispenser manager together monitor resource pools for the current process. At startup, the RD retrieves a reference to the manager by calling GetDispenserManager. The RD then uses the obtained reference to register itself with the dispenser manager. The manager acknowledges the new RD and creates a Holder object for this resource dispenser. The Holder object maintains the actual list of the resource inventory for each dispenser.
When the client requests a new resource of this type, the RD simply forwards the request onto the holder. It is the holder's responsibility to examine the pool and decide whether to ask for the new resource or assign one from the pool. If the holder decides that there is no adequate resource in the inventory, it will call the RD and ask it to create a new one. The just-created resource is placed into inventory and returned to the RD, which can return it to the client. At first, the sequence of events seems odd: the RD calls the holder, which calls the RD back, returning the resource that is finally passed back to the RD. Why not skip the holder, and simply create the resource directly? The answer lies in the separation of resource access and management. The holder object provides a generic pooling mechanism and has no knowledge of how the given resource is created, destroyed, or used. On the other hand, while the RD provides direct access to the RM and has intimate knowledge of the resource, it does not know anything about the resource pool.
As resources are created, destroyed, and kept idle in the pool, the MTS defines four possible states that each resource can be in:
- Resources in unenlisted inventory. These resources
are not in use by the business objects and are not enlisted in a transaction. The resources are available for assignment.
- Resources in enlisted inventory. These resources are not in use by the business objects, but are currently enlisted in a transaction. The resources are available for assignment only to business objects running in the same transaction. A resource is moved from enlisted inventory to unenlisted inventory when the MTS Executive notifies the dispenser manager that the current transaction is complete.
- Resources in unenlisted use. The resources are in use by the business objects, but are not enlisted in a transaction.
- Resources in enlisted use. The resources are in use by the transactional business objects, and were also successfully enlisted in the current transaction.
The potential for performance gains becomes apparent when some of the active resources are deactivated
(instead of destroyed), reset, and placed back into the pool. The next time around, the inactive resource can be assigned to the client. As resources get used and released, the pool of general and unlisted inventory grows.
Contrary to popular belief, significant performance gains from MTS are not guaranteed in every situation and system. Any real improvements are largely dependent on the relative costs of creating the new resource as compared to the extra round-trips made by the dispenser manager and the holder. For example, if the resource is a connection to a share on the remote network, the process of acquiring such a resource can be quite slow, hence the costs of extra calls to the holder are irrelevant.
One important but often-misunderstood aspect of MTS resource pooling is that the resource pools are created on a per-process basis. There is absolutely no cross-process poolingeach process has its own separate inventory. This might seem quite strange and limiting at first, but it was designed that way for a good reason. Remember that the dispensers manage transient data such as threads and memory blocks, resources that are meaningful only in the process where they were created. Even if you assume that the resources contain information meaningful across processes, the resource dispensers and dispenser manager are still in-process components. As a result, there is one instance of the holder object in each process. For these instances to share the same inventory, the holder objects would need to implement some mechanism for persisting and sharing their state. While this is not impossible, it certainly is not consistent with the definition of the resource dispensers as managers of nondurable shared data.
To clean up the resource pools, the dispenser manager periodically (every 10 seconds in MTS 2.0) interrogates each holder to allow them to readjust their inventory. The holder subsequently calls the inventory statistics manager object that suggests the appropriate inventory levels. Based on the inventory statistics manager analysis of the resource pool, the holder then instructs the associated dispenser either to create or destroy some resources. As of MTS 2.0, the inventory statistics manager uses a quite boring algorithm to decide what the appropriate level should be. It simply searches the inventory for expired resources that have been inactive for more than the predefined timeout value. These resources are then removed from the inventory and destroyed.
MTS does a good job of isolating MTS components from the details of how transactions are carried out. The MTS Executive, the dispenser manager, and the MS DTC do most of the work required to perform two-phase commit, distributed transactions behind the scenes. This architecture is very beneficial for component developers because their objects can remain completely independent of how a particular resource participates in the transaction. To achieve such transparency, MTS must take on all major responsibilities of the client application, as shown in Figure 2. From Figure 2, it should be apparent that to be OLE Transactions-compliant, MTS must be able to carry out the following tasks:
- Create transactions and maintain the transaction context
- Instruct MS DTC to commit or abort the current transaction
- Propagate the transaction context to all participating objects and enlist the appropriate resources
|Figure 2 Program Initiated Transactions|
Creating transactions, managing the transaction context, propagating the context to new objects in the same activity, and terminating transactions are done by the MTS Executive. When the MTS transactional object attempts to create a new resource by calling the resource dispenser, the RD asks the holder to allocate the resource. After the resource is allocated, the dispenser manager obtains the current transaction context (Transaction ID) from the MTS Executive, and calls back to the resource dispenser and asks it to enlist the resource in the transaction. After the resource is enlisted, it is returned to the dispenser manager, which immediately returns the obtained resource to the calling object. When the MTS object calls SetComplete or SetAbort, the MTS Executive notifies the dispenser manager, which in turn notifies each resource dispenser's holder that any resources enlisted in the current transaction can now be moved back to general inventory.
It is important to understand that the transaction enlistment is optional. It is up to the dispenser whether to enlist the resource in the current transaction. It is perfectly legal for the dispenser to decide that a given resource manager or resource type is nontransactional and should not participate in the transaction. This flexibility gives the dispenser developer a number of interesting design and implementation options. You can, for example, write dispensers for nontransactional resource managers that cannot participate in transactions. Even though the resource dispenser model was created to be the common architecture for accessing transactional resource managers, it is quite possible to implement the dispenser to manage resources that are not associated with any resource managers controlling persistent data. Suppose your application uses large and complex memory structures that require lengthy initialization. In this case, it would be desirable to write a custom dispenser that would maintain a pool of preinitialized structures that can be shared among active object instances. In addition, the dispenser could perform various administration tasks such as allocating a given number of resources at startup or limiting the size of the resource pool.
Resource Dispenser as RM Proxy
As I mentioned, the RD architecture was primarily
created to provide pooling and automatic transaction enlistment of durable resources managed by OLE Transactions-compliant resource managers. Under this scenario, the dispenser is also responsible for acting as the RM proxy, that is, providing the public interface to the underlying resource manager. The RD can expose either a simple API or COM interface. The ODBC Driver Manager is a dispenser that exposes a common API. Application components use standard ODBC calls such as SQLConnect, SQLDriverConnect, or SQLDisconnect. ODBC Driver Manager 3.5 also demonstrates how the RD can alter its behavior based on the type of resource. For example, by setting the CPTimeout registry value (HKLM\Software\ODBC\
ODBCINST.INI\driver name\[CPTimeout]) you can enable or disable pooling and specify the connection timeout value. While the ability to run under MTS is critical, it is equally important for the RD to function properly outside of MTS while presenting the same interface to the client. Component developers want to use only one interface, so you should rely on the dispenser to figure out whether to activate pooling or participate in the transactions.
When discussing resources and resource managers, databases are the first thing that you probably think of. While databases are indeed the most common resource manager of durable data, a number of other system components can be treated as resource managers. A typical order processing system can be composed of a main module and one or more subsystems that are responsible for tasks such as pricing, material allocation, and demand planning. These subsystems often reside on separate nodes or even in different geographical locations. To take it a step further, these subsystems can require authentication and certain connection protocols. Finally, it could be very beneficial to have the subsystems participate in transactions. This would require writing an OLE Transactions-compliant resource manager, which is beyond the scope of this article. On the other hand, it is fairly easy to write a resource manager that exposes just a common interface and does not support transactions. When business reasons arise, you can add the transaction support and make the resource manager OLE Transactions-compliant.
Building Your Own MTS Resource Dispenser
Let's discuss how to create your own resource dispenser. First, you need something to dispense. Let's assume you want to write a resource dispenser that will manage connections to the business subsystem and that the subsystem is responsible for job scheduling in one of your factories. Various clients use this subsystem to add a job to the production schedule, revoke the job, or just monitor the job progress in the factory. You also have access to the resource manager representing the subsystem. This manager does not support transactions now. Your task is to write a resource dispenser that will manage the resource pool and act as the RM proxy. The users need to be able to dynamically change some operational parameters, such as the expiration timeout for inactive resources.
There are several important design factors to take into account when implementing the RD. The RD is required to be an in-process component that manages the resource pool for a given process. Since the dispenser manager is an in-process singleton COM object, there is at most one instance of the manager per process. Similarly, in a given process there is a single holder object assigned to the resource dispenser object. Therefore, you must ensure that there is only a single corresponding instance of the RD. In other words, the RD must be implemented as an in-process singleton object. If it were not, each instance of the resource dispenser would have its own holder and consequently its own resource pool. As a result, each client instance would be assigned its own pool, and cross-client resource sharing would be impossible. There are several popular ways of implementing COM singletons. Since that discussion is not particularly germane, you can settle for the standard Active Template Library (ATL) implementation of the COM singleton. You do so by adding a DECLARE_CLASSFACTORY_SINGLETON statement to the class declaration. For more details on implementing COM singletons see the ActiveX® (COM) column in the July 1997 issue of MSJ.
It is important to understand the implications of making the dispenser a COM singleton. Since there will always be at most one instance of this class, the RD must be ready to receive calls from any client running on any thread. Therefore, the implementation must be fully reentrant and thread-safe. This means you have to use the InterlockedIncrement and InterlockedDecrement functions for a thread-safe reference count, and that you must protect all instance data by using critical sections.
Since the RD object is thread-safe, the RD should be marked as threading model "Both" and should implement the freethreaded marshaler (FTM) to efficiently handle cross-thread marshaling from both STA and MTA-based objects. Using the freethreaded marshaler lets the calling objects avoid ORPC calls and proxies for cross-apartment communication. The calling objects can then use direct pointers. More importantly, you will also avoid making a thread switch when changing apartments; this would otherwise result in losing the current object context and the transaction information associated with it. The dispenser manager would then treat the client as nontransactional and the participating resources would not be enlisted in the current transaction.
To create the RD as the FTM, let's again rely on ATL. Checking the "Free Threaded Marshaler" option on the Attributes page when adding an ATL object will result in creating the object as the FTM. The ATL object wizard automatically adds the appropriate data member and code to your implementation class.
Once you've got all those implementation details worked out, you need to decide what interfaces the dispenser is going to support. Every RD must provide at least two interfaces: IDispenserDriver and the RM proxy interface. IDispenserDriver is the standard MTS interface that the dispenser manager and the holder use to interact with
the RD. Figure 3 shows the declarations of IDispenserDriver, IHolder, and IDispenserManager. The other interface needed is the RM proxy that will be used by the clients to access the RD. This interface can be one or more COM interfaces or a simple API. Let's make things a bit more interesting by exposing two custom interfaces, IRDisp and IRDispAdmin:
interface IRDisp : IUnknown
HRESULT Connect([in] BSTR strConnectInfo,
[out] long* lResourceID);
HRESULT Disconnect([in] long lResourceID);
HRESULT ScheduleJob([in] long lResourceID,
[in] long lJobID);
HRESULT CancelJob([in] long lResourceID,
[in] long lJobID);
interface IRDispAdmin : IUnknown
HRESULT SetTimeout([in] long lTimeout);
HRESULT GetInventoryStatus([out] long* lSize,
[out] long* lInUse);
|The IRDisp interface will be used by business objects to access the underlying resource manager (scheduling subsystem). In a typical scenario, the client would first establish the connection to the resource by calling the Connect method. The client would provide necessary login information such as the user ID and the subsystem it is trying to connect to. Upon successfully connecting to the requested subsystem, the RD would return an opaque handle to the resource. The client would store this handle and pass it on with each request, similar to using an ODBC connection handle to allocate and then execute SQL statements. The users of your RD could then either add a new job to the factory schedule or cancel previously scheduled jobs by having the RD call ScheduleJob or CancelJob. When finished, the client would call the Disconnect method to close the connection. In response to the Disconnect command, the RD informs its holder that the connection can be returned to the resource pool.
To make this example more realistic, the RD will also expose the IRDispAdmin interface that will be used to manage the resource dispenser. In every production system, system administrators need to be able to monitor the use of common resources. In this example, the administrative interface exposes three methods. The SetTimeout method can be used to reset the amount of time after which the idle resource will be deleted from the pool. The GetInventoryStatus method provides basic information about the resource pool, like the total number of allocated resources (active and inactive) and the number of resources that are currently actively used by the clients. By subtracting these two values, you can easily find out the number of inactive resources that are currently in the pool. Finally, the DestroyInactive method allows the administrators to deplete the resource pool by explicitly releasing all inactive resources. It is easy to imagine how you could expand your administrative interface with more than these three methods. You could have methods that would return connection information for a given resource, search for resources based on performed activities, or even close the active connection that is held by the client.
Figure 4 shows the declaration of the CRDisp class that implements the resource dispenser. The class implements the three previously described interfaces. It also contains the required singleton DECLARE_CLASSFACTORY_
SINGLETON(CRDisp) macro and the FTM m_pUnkMarshaler data member. Since you are going to monitor the resource pool (using the IRDispAdmin::GetInventoryStatus method), the C++ map holding all resources and their status must be defined:
typedef map<long, bool> ResourceMap
|The long is designated for the resource ID. The Boolean value will refer to the resource's status: "true" for currently active and "false" for idle resources waiting in the pool.
Every RD must implement the IDispenserDriver interface. Since the dispenser manager and holder only manage the pool, they do not have intimate knowledge of the underlying resource. Therefore, they must use the IDispenserDriver interface to ask the RD to perform resource-specific tasks on their behalf. In particular, the RD must know how to:
- Create a new resource of specific type
- Destroy expired resources that are being removed from the inventory
- Reset the resources before returning them to the resource pool
- Rate suitability of existing resources
- Enlist resources in the client's transaction
Creating New Resources, Resource Types, and Resource IDs
When the holder determines that it needs a new resource for its pool, it will call the RD's CreateResource method. As shown in Figure 3, the caller supplies the resource type identifier, and receives an ID that uniquely identifies the created resource and the resource timeout value.
The resource type ID (RESTYPID) is the DWORD value that identifies the type of the resources that the RD can create. It can be a single value, but more likely it is a pointer to a detailed description of the resource. Keep in mind that this is the only parameter that the RD can use when creating the new resource. In addition, this value rates how the inactive resources from the resource pool fit the client requests. How descriptive the resource type gets is up to you. For example, if your scheduling subsystem has one common login and entry point, you can use a single value to identify the resource. If you require user IDs, you might want to keep this information as part of the resource type to ensure that the users are given back only the resources that are connected under their own IDs. In this case, the RD would maintain the list of valid resource types in memory, and use the pointer as RESTYPID.
Like the resource type, the resource ID (RESID) is a DWORD; it is up to you what the value really represents. Since the resources are loaded into memory, this value is usually the direct pointer to the resource.
Figure 5 shows how the sample CRDisp class implements IDispenserDriver::CreateResource. If the RM is a COM object, the RD should always maintain an instance variable holding the pointer to the single RM instance. One caveat with the FTM is that the implementing class must ensure that the interface pointers held as member variables can also be used from an apartment other than the one in which it was originally createdsomething that is not commonly allowed in COM. To address this you can use the Global Interface Table (GIT) facility introduced in the Windows NT® 4.0 Service Pack 3 SDK. By placing the interface pointer into the GIT and then explicitly retrieving the pointer in each method, you are always guaranteed to have the correct interface pointer. It is also important to note that the same rules are not specific to the RM pointer,
but rather apply to all member variables that contain interface pointers.
Since the sample RM is implemented as a simple DLL, you don't have to worry about this issue. Simply allocate the resource by calling the RM's RM_Connect method. The returned handle is used as the resource ID, and will ultimately be returned to the client.
After the holder determines that a given resource is no longer needed, it deletes the resource by calling DestroyResource. The method implementation is very similar to CreateResource. You first obtain the pointer to the resource manager and then instruct it to close the resource. The IDispenserDriver interface also defines the DestroyResourceS method that accepts a Unicode string representation of the resource ID (SRESID). According to
the MTS documentation, SRESID should be used with resources in which the complete resource description can be maintained in a single string (such as a connection string). Since this is not the case here, return E_NOTIMPL indicating that this method is not implemented.
As soon as the client decides to disconnect the resource, the holder object is informed that the given resource is not needed and can be released back to the resource pool. Just before placing the resource into the pool, the holder calls the resource dispenser and gives it a chance to get the resource ready for the next use. In a typical scenario, the RD forwards this resource ID onto the RM that will then make sure that all data and tasks associated with this resource were properly released. This might include terminating any outstanding requests, commands, and resetting instance data.
When the client asks the resource dispenser to create a new resource, the RD forwards the request with the required resource type onto the holder. The holder now decides whether to create a new resource or use one from the resource pool. The holder examines its general and enlisted inventory and selects all resources that match the requested resource type. The holder then calls the RD's RateResource method to inquire about how well the given resource fits the required type. The RD returns a number from 0 (not suitable) to 100 (perfect fit). If 100 is returned, the holder stops the search and uses the resource with a perfect score. If 0 is returned for all eligible resources, the holder instructs the RD to create the new resource. If there is at least one candidate resource with a rating higher than 0 and less than 100, the resources are sorted according to their ratings; the one with the highest score is removed for the inventory and returned to the client.
How the RD determines the fit of the resource and the resource type is completely up to the resource dispenser. Keep in mind that the holder will only consider rating resources of the same resource type, and the resource type is the only information that is available for determining the ratings. For example, if you set up one common resource type for the whole scheduling subsystem, as shown in Figure 6, all resources will be of the same type and will be submitted for rating. Since the resource type does not contain any meaningful information, there is really no good way to figure out how well it fits. Therefore, it is often beneficial to set up RESTYPIDs as pointers to the extended resource type information.
If the managed resources are transactional and it takes a significant amount of time to perform enlistment, it is a good idea to give already enlisted resources higher ratings. As a part of the method call, the holder provides the fRequiresTransactionEnlistment parameter. If set to false, the resource is already enlisted in the client's transaction and a call to EnlistResource will be skipped.
Enlisting in Transactions
After receiving RESID from CreateResource or removing the resource from general inventory, the dispenser manager asks the MTS Executive to provide the transaction context of the calling object. If the object runs within a transaction, the dispenser manager will attempt to register the resource with the current transaction by calling EnlistResource and providing the resource ID and the transaction context (TRANSID). TRANSID is really an ITransaction* MS DTC interface pointer that identifies the client's transaction. If the RD decides that a given resource is transactional and supports the OLE Transactions protocol, the RD will enlist the resource by obtaining the transaction export object, associating the current transaction with it. The RD then passes the export object to the RM, which will import the transaction and contact the MS DTC to finish the resource enlistment. After the calling object completes the current transaction, the MTS Executive notifies the dispenser manager, which asks the holder to move the resource from the enlisted to the general inventory.
If a given resource manager does not support transactions, the RD can return S_FALSE from its EnlistResource method to inform the dispenser manager that the resource will not participate in the client's transaction. Since the sample scheduling resource manager does not support transactions, CRDisp::EnlistResource returns S_FALSE.
Acting as a Proxy
If you examine the declaration of the CRDisp class in Figure 4, you will notice that the IDispenserDriver interface is not exposed as a public COM interface. Only the dispenser manager, the holder, and possibly the class itself should call IDispenserDriver methods. The client objects use the separate public interfaces to interact with the RD. With respect to the clients, the RD then acts as the RM proxy.
The IRDisp interface of the CRDisp class provides the basic operations on the scheduling subsystem, such as obtaining a connection, adding a job, canceling a job, and closing a connection.
The IRDisp::Connect method is used by the MTS objects to obtain an initial reference to the resource managerthe scheduling subsystem. If the RD is running under the MTS, it then calls its holder and asks it for the new resource. As mentioned before, the holder then either creates the new resource or uses one from its inventory.
Since it is quite common to have the RD run both with and without MTS, you must also make sure that the RD can allocate resources without the presence of the dispenser manager and the holder object. In this case, the RD can simply call its own CreateResource method directly. The pseudocode in Figure 7 illustrates the generic template for the RD's Connect method.
After the RD allocates the new resource, the opaque handle is returned back to the client. The client can then use this handle to perform other operations on the resource. The complete implementation of CRDisp::Connect is shown in Figure 6.
The IRDisp interface also allows the clients to carry out two basic tasks: job scheduling and cancellation. As you see in Figure 6, the implementation of these methods is rather trivial: first obtain a lock to protect the instance state, retrieve the RM pointer, do the work, release the RM pointer, and finally forward the result back to the client.
When the client is ready to terminate the connection, it calls CRDisp::Disconnect. The RD receives the resource ID, and if running under MTS, it instructs its holder to free the given resource by calling IHolder::FreeResource. Note that upon the FreeResource call, the holder immediately calls back to reset the resource (IDispenserManager::ResetResource), and then returns the resource back to the inventory.
According to the MTS SDK, when calling the holder's FreeResource method with an invalid resource handle, the holder returns E_INVALIDARG (0x80070057L). As it turns out, when MTS 2.0 is confronted with an invalid resource ID, mtx.exe host process crashes and records an error in the event log indicating that the exception source is Transaction Server, the category is Context Wrapper, and the event ID is 4104. This is a confirmed bug that will be fixed in the next release of MTS (COM+). For now, you need to use a simple workaroundkeep a list of assigned resource IDs, and always perform validation before making the FreeResource call.
In addition to the IRDisp interface that represents the RM proxy, the sample RD also exposes the IRDispAdmin interface. This interface provides basic administrative tasks such as cleaning up the holder's general and enlisted inventory, setting the resource timeout, and retrieving the RD statistics. As shown in Figure 8, these basic operations are performed using a map of all allocated resources and the Boolean value representing the current status of the resource (trueactive, falseplaced in inventory). As resources are created, destroyed, and reset, you can add, remove, or set the appropriate map values.
Other Implementation Considerations
Before building the RD, I need to address a few more design and implementation issues, namely the RD startup and shutdown. When using ATL, all startup and shutdown code should be placed into the FinalConstruct and FinalRelease methods, respectively. When the RD starts it must somehow obtain the reference to the dispenser manager. It does so by calling GetDispenserManager, which is exported by mtxdm.dll. If the function call returns E_FAIL, there is no dispenser manager present, so no pooling will be provided. As a result, the RD must avoid using the holder object when creating and freeing inventory. If the dispenser manager is available, the RD must next register itself with the manager by calling IDispenserManager::RegisterResource. Because of the known bug in MTS 1.x and MTS 2.0, this method call does not call AddRef properly. To account for the missing AddRef, simply skip the Release call on the pointer when you're ready to exit (see Figure 9).
The MTS SDK also describes an "auto-reclamation" feature. According to the documentation, the MTS Executive should notify the dispenser manager when objects are being released. The dispenser manager then ensures that all registered holders will release the resources associated with the object that is being released. This seems to be a very useful feature, but unfortunately it was never implemented and the SDK was never updated. As a result, there is no easy way to determine that the client object has been released and its resources are still allocated. For now, the best you can do is to ensure that when the resource dispenser shuts down, all unreleased resources are freed (see the implementation of CRDisp.FinalRelease in Figure 9).
As companies struggle to move their large legacy systems into a more flexible and cost-effective environment, Microsoft Transaction Server addresses the need for a set of standardized runtime services to business objects. MTS, a natural evolution of traditional COM programming, introduces concepts such as state management, automatic transactions, thread management, and resource pooling.
The MTS resource management architecture is centered on resource dispensers: custom COM objects that manage nondurable data within a process. This data often represents a connection to the underlying resource manager that controls persistent storage. MTS resource dispensers manage the pools of reusable resources, automatically enlist them in transactions, and provide clients with standard interfaces to perform operations on these resources. By building custom MTS resource dispensers, you can integrate traditionally disconnected or nontransactional resources into the overall MTS programming framework.
As a final heads up, while MTS RDs are here today and will continue to work in the future, COM+ and Windows NT 5.0 will offer pooled objects as the recommended way to implement resource pooling. The benefits of pooled objects will come from two areas: a common programming model (thus abandoning a variety of legacy APIs) and a significant performance improvement over RDs. The understanding of MTS and specifically RDs is fundamental and will certainly help in your ongoing design decisions.
From the September 1998 issue of Microsoft Systems Journal.
Get it at your local newsstand, or better yet, subscribe.