As mentioned before, a transactional object votes whether to commit or abort the transaction by setting the value of a bit in the context object. That bit is called the consistency bit. The name is appropriate. Consistency is the only transaction property under the application’s objects control. COM+ can manage atomicity and the resource managers guarantee isolation and durability, but only the objects know whether the changes they make to the system state are consistent or if they encounter errors that merit aborting the transaction.
When COM+ creates a transactional object, it puts it in its own
private context and sets the context object consistency bit to
TRUE
. As a result, if the object makes no explicit
attempt to set the consistency bit to FALSE
, the
object’s votes to commit the transaction.
An object can actually share its context with other objects whose transaction setting is set to Disabled.
The object can set the value of the consistency bit by accessing the
context object and getting
hold of
IContextState
interface, defined as:
enum tagTransactionVote { TxCommit= 0, TxAbort = TxCommit + 1 }TransactionVote; interface IContextState : IUnknown { HRESULT SetDeactivateOnReturn([in] BOOL bDeactivate); HRESULT GetDeactivateOnReturn([out]BOOL* pbDeactivate); HRESULT SetMyTransactionVote ([in]TransactionVote txVote); HRESULT GetMyTransactionVote ([out]TransactionVote* ptxVote); }
IContextState
is also discussed in Chapter 3, in the context of deactivating JITA objects.
IContextState
provides the method
SetMyTransactionVote( )
used to set the transaction vote. You can pass
SetMyTransactionVote( )
the enum value
TxCommit
, if you want to commit, or the enum value
TxAbort
, if you want to abort the transaction.
SetMyTransactionVote( )
returns
CONTEXT_E_NOTRANSACTION
when called by a nontransactional
component.
Your
object
should vote to abort the transaction when it encounters an error that
merits aborting the transaction. If all went well, your object should
vote to commit the transaction. Example 4-1 shows a
typical voting sequence. The object performs some work (the
DoWork( )
method) and, according to its success or
failure, votes to commit or abort the transaction. If your component
decides to abort the transaction, it should return an error code
indicating to its client that it aborted the transaction. The client
can then decide to retry the transactional operation or handle the
error some other way. This is why the component in Example 4-1 returns
CONTEXT_E_ABORTING
from the method after aborting the
transaction. CONTEXT_E_ABORTING
is the standard
returned value from a component that aborted a transaction.
Example 4-1. Voting on the transaction’s outcome by accessing the IContextState interface and calling SetMyTransactionVote( )
STDMETHODIMP CMyComponent::MyMethod( ) { HRESULT hres = S_OK; hres = DoWork( ); //Vote on the transaction outcome IContextState* pContextState = NULL; ::CoGetObjectContext(IID_IContextState,(void**)&pContextState); ASSERT(pContextState!= NULL);//Not a configured component if(FAILED(hres)) { hres = pContextState->SetMyTransactionVote
(TxAbort); ASSERT(hres != CONTEXT_E_NOTRANSACTION);//No transaction support hres = CONTEXT_E_ABORTING; } else { hres = pContextState->SetMyTransactionVote
(TxCommit); ASSERT(hres != CONTEXT_E_NOTRANSACTION);//No transaction support } pContextState->Release( ); return hres; }
However, what should the client do if an inner object (not the root)
votes to abort the transaction? The root object may not know that an
inner component has aborted the transaction (and may still vote to
commit and return S_OK
to the client). If
S_OK
is allowed to return to the client, then the
client never knows that its request was aborted. To prevent this
situation, the interceptor between the root object and its client
detects that the transaction is already doomed if an inner object
votes to abort and the root object votes to commit and tries to
return S_OK
to the client; it returns
CONTEXT_E_ABORTED
to the client
instead.