September 1998
Take Advantage of MTS in Your Distributed System with Custom Resource Dispensers
Download RDcode.exe (264 KB)
Maros Cunderlik is a Lead Analyst at 3M. He focuses on application design and distributed component architectures. Maros can be
reached at mcunderlik@mmm.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.
Resource Management
|
Figure 1 MTS Resource Management Architecture |
Resource Pooling
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:
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.
Transaction Enlistment
|
Figure 2 Program Initiated Transactions |
Resource Dispenser as RM Proxy
Building Your Own MTS Resource Dispenser
|
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: |
|
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.
IDispenserDriver Interface
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.
Managing Resources
Enlisting in Transactions
Acting as a Proxy
Other Implementation Considerations
Conclusion
From the September 1998 issue of Microsoft Systems Journal.
|