Transactions and Object Pooling

As discussed in Chapter 3, to speed up performance, your pooled object acquires expensive resources, such as database connections, at creation time and holds onto them while pooled. The problem is that one of the requirements for resource managers is auto-enlistment in transactions. When an object creates a resource such as a database connection, the connection (actually the resource manager) auto-enlists with the object’s transaction. A pooled object only creates the resources once, and then the object is called out of the pool to serve clients. Every time the object is retrieved from the pool, it could potentially be part of a different transaction. If the pooled object is forced to re-create the expensive resources it holds to allow them to auto-enlist, that would negate the whole point of using object pooling.

Unfortunately, the only way to combine transactions with a pooled object that holds references to resource managers is to give up on auto-enlistment. The pooled object has to manually enlist the resources it holds in the transactions it participates with.

The pooled object must follow these steps:

  1. The object must implement the IObjectControl interface. The object needs to manually enlist the resource managers it holds when it is placed in an activation context in its implementation of IObjectControl::Activate( ) . The object also needs to perform operations in IObjectControl::Deactivate( ) and IObjectControl::CanBePooled( ) , explained later.

  2. After creating the connection to the resource manager, the pooled object turns off the resource manager’s auto-enlistment. This step requires programming against the resource manager API. All resource managers support this functionality, although in slightly different ways and syntax.

  3. When the object is called out of the pool to serve a client and is placed in a COM+ context, it must detect whether a transaction is in progress. This detection is done either by calling IObjectContextInfo::IsInTransaction( ) or calling IObjectContextInfo::GetTransactionId( ) . If the context the object is placed in is not part of a transaction, the returned transaction ID is GUID_NULL. If a transaction is in progress, the object must manually enlist any resource manager it holds. Enlisting manually is done in a resource-specific manner. For example, in ODBC, the object should call SQLSetConnectAttr( ) with the SQL_COPT_SS_ENLIST_IN_DTC attribute.

    Note that IObjectControl::Activate( ) is called before the actual call from the client is allowed to access the object. The client call is executed against an object with enlisted resource managers.

  4. The object must reflect the current state of its resources and indicate in IObjectControl::CanBePooled( ) when it can’t be reused (if a connection is bad). Returning FALSE from CanBePooled( ) dooms a transaction.

Clearly, mixing resource managers with pooled objects is not for the faint of heart. Besides laborious programming, manually enlisting all resources the object holds every time the object is called from the pool implies a needless performance penalty if the object is called to serve a client in the same transaction as the previous activation.

COM+ is aware of the performance penalty and it provides a simple solution. As you saw in Chapter 3, COM+ maintains a pool per component type. However, if a component is configured to use object pooling and require a transaction, COM+ maintains transaction-specific pools for objects of that type.

COM+ actually optimizes object pooling: when the client requesting an object has a transaction associated with it, COM+ scans the pool for an available object that is already associated with that transaction. If an object with the right transaction affinity is found, it is returned to the client. Otherwise, an object from the general pool is returned. In essence, this situation is equivalent to maintaining special subpools containing objects with affinity for a particular transaction in progress. Once the transaction ends, the objects from that transaction’s pool are returned to the general pool with no transaction affinity, ready to serve any client.

With this feature, a transactional-pooled object can relieve the performance penalty. Before manually enlisting its resources in a transaction, it should first check to see whether it has already enlisted them in that transaction. If so, there is no need to enlist them again. Your object can achieve that by keeping track of the last transaction ID and comparing it to the current transaction ID using IObjectContextInfo::GetTransactionId( ).

Example 4-7 shows a pooled object that takes advantage of COM+ subpooling. In the object’s implementation of IObjectControl::Activate( ), it gets the current transaction ID. The object verifies that a transaction is in progress (the transaction ID is not GUID_NULL) and that the current transaction ID is different from the transaction ID saved during the previous activation. If this transaction is new, then the object enlists a resource manager it holds manually. To manually enlist the resource, the object passes the current transaction object (in the form of ITransaction*) to the private helper method EnlistResource( ) .

The object saves the current transaction ID in its implementation of IObjectControl::Deactivate( ). The object uses the private helper method IsResourceOK( ) in IObjectControl::CanBePooled( ) to verify that it returns to the pool only if the resource manager is in a consistent state.

Example 4-7. A transactional pooled object manually enlisting a resource manager it holds between activations

HRESULT CMyPooledObj::Activate(  )
{
   HRESULT hres = S_OK;
   GUID guidCurrentTras = GUID_NULL;
   hres =  ::CoGetObjectContext(IID_IObjectContextInfo,
                                (void**)&m_pObjectContextInfo);

   hres = m_pObjectContextInfo->GetTransactionId(&guidCurrentTras);
   if(guidCurrentTras!= GUID_NULL && guidCurrentTras != m_guidLastTrans)
   {
      ITransaction* pTransaction = NULL;
      hres = m_pObjectContextInfo->GetTransaction(&pTransaction);
      hres = EnlistResource(pTransaction);//Helper Method 
   }  
   return hres;
} 
void CMyPooledObj::Deactivate(  )
{
   //Save the current transaction ID
   m_pObjectContextInfo->GetTransactionId(&m_guidLastTrans);
   //if no transaction, m_guidLastTrans will be GUID_NULL
   m_pObjectContextInfo->Release(  );
}  
BOOL CMyPooledObj::CanBePooled(  )
{
   return IsResourceOK(  );//Helper Method
}

Note that though the object is a transactional object, it maintains state across transactions and activations. This maintenance is possible because the object is not really destroyed (only returned to the pool) and its internal state does not jeopardize system consistency.

COM+ does subpooling regardless of whether your transactional-pooled object manages its own resource managers. If your transactional-pooled object does not manually enlist resource managers, then you can just ignore the subpooling.

Get COM & .NET Component Services now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.