Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A simple solution for managing state of stateless transactional COM+ components

0.00/5 (No votes)
5 Dec 2001 1  
A reusable object-oriented framework for preserving state of stateless components.

Abstract

Building a multi-tier distributed systems that can manage many client requests concurrently has always been a challenge for the COM developers.

COM+ has introduced the idea of "object-per-client" and its JITA extension -"method-per-client" models . The framework provides a highly efficient runtime environment for implementing extremely scalable enterprise solutions. The main idea behind these mechanisms is to suggest an architecture for implementing thread-safe classes that allow concurrent access to their methods and attributes along with providing a means for simultaneous method invocation required by the performance and scalability requirements. To simplify the development of distributed systems, COM+ run-time environment provides a framework that offers set of services. In my opinion, certainly the most important one is the transaction management service. In the theory a transaction is defined as a single entity of work done on behalf of a particular client initiator. On the other hand, taking the advantage of declarative transactions requires unavoidably use of JITA - "Just In Time Activation" feature of COM+ [EWA-2001]. This service is related to the object lifetime management. An object that uses JITA feature has the option of detaching from its stub and being released from its context before its client actually frees it. When next time the client invokes a method of this object, COM+ environment attaches to the stub new instance.

Problem

If you are implementing scalable server-side COM+ components, designed to manage relatively large number of clients, more likely you would make them stateless. The issue here is that often such systems require maintaining client specific information that must be kept between successive COM+ object method calls.

Solution

COM+ provides a number of techniques for preserving the state between consecutive client requests (method calls). The solution I will present is based on the COM+ internal architecture and demonstrates how to design and implement complex stateless transactional components that can easily maintain client-specific information. Having an insight into COM+ context internals we can see that actually it is just a well-defined territory within an executable. Each context is responsible for providing particular pre-defined set of services to the objects executing inside it. Each COM+ object has only one context. COM+ is in charge of the context generation as well as associations between objects and contexts. The framework ensures that the objects run in appropriate context and if two objects have the same service requirements they will be associated with the same context. However, most configured classes default attributes are set up in a way that each new COM object instance has to be placed in a new context on its own. Moreover, if the COM component supports declarative transactions and JITA service then COM+ forces the objects to live in context on their own [EWA-2001]. Turning on the attribute EventTrackingEnabled (which controls COM+ statistics feature) requires each new COM+ object instance to be associated with instance-dedicated context as well [EWA-2001]. A detailed analysis of consecutive method calls shows that if the conditions above are met - COM+ maps an unique pair of object's stub and COM+ context ID. Each proxy pointer kept by the client code is "related" to exactly one unique context ID. Therefore we can keep client-specific information on the server-side mapping it to a context ID.

Design and implementation

JITA service allows an object to deactivates itself before the client releases it. COM+ interception layer inspects "done" bit at the end of each COM call. That's why it is important to set bit "done" using IObjectContext::SetComplete() or IObjectContext::SetAbort() in order to inform COM+ environment that the object has to be deactivated at the end of each method call. If the class supports transactions and a DTC transaction has been initiated, setting the "done" bit could be very crucial. It in fact determines the outcome of the distributed transaction. We can override the default behavior and force object to be deactivated automatically when a call completes though setting the AutoComplete attribute to TRUE. Each time when a client makes a method call and IObjectControl::Activate() is called, the context ID is retrieved using IObjectContextInfo::GetContextId(). This ID will be used as key for storing and locating client-specific information in a map container. A COM+ component that keeps the state of transactional COM+ objects can store the information in a global thread-safe STL map in order to achieve better performance and efficiency. An important matter we should take under consideration is that the associative container should be designed as a thread-safe one in order to provide a proper access to information it maintains. Follows the interface of base CSafeMap template class:

 template <class BOOL bThreadSafe>
class CSafeMap: private map<tstring, T*, CNocaseCmp>
{
    //

    // ... Other details ignored for the sake of simplicity...

    // 

public:
    //

    // An efficient "add or update" method

    // Returns: TRUE if pObject has been added to the map

    //          FALSE if pObject has replaced existing one

    //

    BOOL AddOrUpdate(
        TCHAR* pszKey, // Key 

        T* pObject     // Pointer to a valid T object

        );
    //

    // Remove an element from the container

    //

    void Remove(TCHAR* pszKey);
    //

    // Returns a copy of an element by its key

    //

    BOOL GetObjectCopyByKey(TCHAR* pszKey, T& copyObject);
};
		

In order to minimize coupling and not overuse inheritance in CSafeMap I apply private inheritance. Nonpublic inheritance expresses "IS-IMPLEMENTED-IN-TERMS-OF". Despite that CSafeMap doesn't override any of map's virtual functions I compromised for the sake of simplicity and decided to make use of "IS-IMPLEMENTED-IN-TERMS-OF" instead of "HAS-A" (i.e. composition) which is preferable considering all functional requirements of CSafeMap. For more details see [SUT-2000] Item 24.

Actually CSafeMap is simple associative map container using context ID as key and object pointers (e.g. CCompObjectState) for their corresponding values. Associative containers require that their elements can be ordered. By default operator "std::less" is used to define the order. To change the default comparison I designed CNocaseCmp class. It implements the operator bool operator()(const tstring& x, const tstring& y) for case-insensitive comparison of strings. More attention in regards to the CSafeMap implementation deserves AddOrUpdate() method. It is based on a perfect Scott Meyers's realization. Briefly, in order to achieve better performance, this method uses map::insert() method for adding and updating elements to the map. For more details see [MEY-2001] Item 24. Follows a snippet with part of AddOrUpdate() implementation:

//

// An efficient "add or update" method

// Returns: TRUE if pObject has been added to the map

// FALSE if pObject has replaced existing one

//

BOOL AddOrUpdate(
    TCHAR* pszKey, // Key 

    T* pObject     // Pointer to a valid T object

    )
{
    CLockMgr<CCSWrapper> lockMgr(m_Mutex, bThreadSafe);
    BOOL bResult;

    // Find where pszKey is or should be

    CSafeMap::iterator lb = lower_bound(pszKey);
    // if lb points to a pair whose key is equivalent to the pszKey

    if ( ( lb != end() ) && !( key_comp()(pszKey, lb->first) ) )
    {
        if ( m_bOwnsObjects ) 
        {
            T* pLastObj = lb->second;
            //

            // If the map owns all objects we should release 

            // the this instance in order to prevent memory leaks

            //

            delete pLastObj;
        }
        //

        // Updates the pair's value

        //

        lb->second = pObject;
        //

        // replaces existing one

        //

        bResult = FALSE;
    }
    else
    {
        //

        // when an "add" is performed, insert() is more efficient

        // than operator[].

        // For more details see -item 24 page 109 "Effective STL" by Meyers

        //

        // Adds pair(pszKey, pObject) to the map

        insert( lb, value_type(pszKey, pObject) );
        //

        // added to the map

        //

        bResult = TRUE;
    }

    return bResult;
}

Another important feature of CSafeMap is that it provides an option for owning stored objects through setting m_bOwnsObjects attribute. It allows CSafeMap to free objects when they are removed from the map or the map is destroyed. By default the value of m_bOwnsObjects is set to TRUE. However it can be altered using Set_OwnsObjects() accessor method.

CSafeMap is a thread-safe class when you instantiate it with a parameter bThreadSafe = TRUE, that is - it is designed to allow multiple threads to access it, store and get information to/from it. The container class aggregates an instance of CCSWrapper, which is a simple CRITICAL_SECTION wrapper. However the implementation of CSafeMap doesn't use directly CCSWrapper - it does it by means of the template class CLockMgr. Rather than call to Enter() and Leave() methods - the implementation of CSafeMap instantiate a CLockMgr on the stack, thus when a thread-safe method of CSafeMap enters, CLockMgr's constructor invokes CCSWrapper::Enter() to acquire the lock and when the method leaves, CLockMgr goes out of the scope, which forces a call to its destructor, where the release of the CCSWrapper's lock takes place. The advantage of this approach is that we don't have to worry about exception handling that should release the lock in case of thrown exception inside CSafeMap's method.

Last but not least, I implemented a class CStateMgr that inherits from CSafeMap and uses CCompObjectState as template parameter. This class encapsulates implementation of singelton pattern and model "IS-A" CSafeMap class. CStateMgr implementation is straightforward and combines "The double-checking locking pattern" explained by Andrei Alexandrescu's "Modern C++ Design" and Meyers singelton described in "More Effective C++" item 26. Follows the implementation of GetInstance() static method.

CStateMgr& CStateMgr::GetInstance()
{
	if (!sm_pInstance)
	{
		CLockMgr<CCSWrapper> guard(g_SingeltonLock, TRUE);
		if (!sm_pInstance)
		{
			static CStateMgr instance;
			sm_pInstance = &instance;
		}
	} // if

	return *sm_pInstance;
}
		

In order to complete the entire picture we should declare two additional interface methods of our COM object CTrickyObject - AssignState() and RemoveState(). Their purpose is to register and unregister interest in preserving state for a particular object. I defined them in a separated interface ICasheState, but it is up to the implementer to decide whether they should be part of any other COM interfaces implemented by the same COM object. As long as the implementation provides two interface methods for assigning state and releasing allocated resources - it will work. Follows the snippet of the COM class interface:

  
  class ATL_NO_VTABLE CTrickyObject: 
public CComObjectRootEx<...>,
    public CComCoClass<CTrickyObject...>,
    public IDispatchImpl<ITrickyObject...>,
    public IObjectControl,
    public ICasheState
{
    //

    // Other details ignored for the sake of simplicity 

    //

//

// ICasheState

//

public:
    //

    // Assigns state information

    //

    STDMETHOD(AssignState)(/*[in]*/ BSTR bstrParam);
    //

    // Releases resources used for maintaining state information 

    //

    STDMETHOD(RemoveState)();
};
		

Here is the implementation of AssignState() method. Notice that if m_guidContextId was not initialized (i.e. is GUID_NULL)- the result code returned by this method is CO_E_NOT_SUPPORTED.

STDMETHODIMP CTrickyObject::AssignState(BSTR bstrParam)
{     
    HRESULT hr = S_OK;
    // 

    // Get the only one instance of the state manager

    // 

    CStateMgr& stateMgr = CStateMgr::GetInstance(); 
    if (m_guidContextId != GUID_NULL)
    { 
	_bstr_t bstrProperty(bstrParam); 
	CCompObjectState* pState = new CCompObjectState();
	// 

	// Let's keep the data we would like to preserve

	//

	pState->Set_Param( (TCHAR*)bstrProperty ); 
	//

	// Adds/updates an element to the map using context ID

	// as a key

	//

	stateMgr.AddOrUpdate(
		_bstr_t( CComBSTR( m_guidContextId ) ),
		pState
		);
    } // if

    else
    {
        //

        // The operation attempted is not supported.

        //

        hr = CO_E_NOT_SUPPORTED;
    }
    return hr;
}
		

Finally we can have a look at the client side. It is a simple console application that implements two demo functions - StatelessCalls() and StatefulCalls(). StatelessCalls() demonstrates regular COM+ object manipulations. StatefulCalls() uses provided mechanism for storing information per COM+ object. It shows how the component maintains information among consecutive COM+ method calls.

Compiling and debugging TrickyComp.DLL using VC++ 6.0 

  1. Make sure you turn on "Exception handling" Use multithreaded debug version of VC RTL.
  2. If you would like to debug it - set active configuration to "Debug"
  3. Set "Executable for debug session"
  4. Compile TrickyComp.DLL
  5. Compile the client project
  6. Register TrickyComp.DLL using Component Services MMS snap-in.

Creating a new COM+ component using presented framework

  1. Create new ATL COM project and select DLL with MTS support
  2. Add following files to your project:
    • Common.h
    • CompObjectState.h
    • CompObjectState.cpp
    • LockMgr.h
    • LockMgr.cpp
    • StateMgr.h
    • StateMgr.cpp
    • SafeMap.h
  3. Declare m_guidContextId attribute in your COM object. It will keep the value of context ID retrieved by IObjectContextInfo::GetContextId in IObjectControl::Activate() implementation. Make sure that you set it up to GUID_NULL at the beginning of the Activate() method.
  4. Declare and implement AssignState() and RemoveState() methods. For more details see the sample code.

Summary

Managing state in a stateless environment could be handled by using many different approaches. Presented mechanism is quite efficient and applicable for various scenarios. However you should consider very carefully, analyze the exact system requirements and then make the decision whether or not you really need to turn transactional COM+ components to ones that can maintain client-specific information.

References

[EWA-2001] Tim Ewald "Transactional COM+", 2001
[SUT-2000] Herb Sutter "Exceptional C++: 47 Engineering Puzzles, Programming Problems, and Solutions", 2000
[MEY-2001] Scott Meyers "Effective STL: 50 Specific Ways to Improve Your Use of the Standard Template Library" 2001
[ALE-2001] Andrei Alexandrescu "Modern C++ Design: Generic Programming and Design Patterns Applied"
[MEY-1995] Scott Meyers "More Effective C++: 35 New Ways to Improve Your Programs and Designs"
[TAP-2000] Pradeep Tapadiya "COM+ Programming: A Practical Guide Using Visual C++ and ATL"
Platform SDK, COM+ Component Services

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here