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:
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 ofIObjectControl::Activate( )
. The object also needs to perform operations inIObjectControl::Deactivate( )
andIObjectControl::CanBePooled( )
, explained later.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.
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 callingIObjectContextInfo::GetTransactionId( )
. If the context the object is placed in is not part of a transaction, the returned transaction ID isGUID_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 callSQLSetConnectAttr( )
with theSQL_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.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). ReturningFALSE
fromCanBePooled( )
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.