In
classic synchronous COM, the client knew
immediately about the success or failure of a method call by
inspecting the returned HRESULT
. A queued
component client interacts with the recorder only, and the returned
success code only indicates the success of recording the call. Once
recorded, a queued component call can fail because of delivery
problems or domain-specific errors. COM+ provides a few options for
handling errors on both the client side and the server side. These
options include an exception-like mechanism, auto-retries, and a few
administrative tools. You can always use a response object to handle
errors, as well.
Once a call is placed successfully in the application queue, plenty can still go wrong; perhaps the component was removed, its installation was corrupted, or the component failed while executing the call (for example, if the customer provided a bogus credit card number). In case of failure, if the call is simply returned back to the queue, COM+ could be trapped in an endless cycle of removing the call from the queue—trying to call the component, failing, placing it back in the queue, and so on. COM+ would never know when to stop retrying—perhaps the call could succeed the next time.
This scenario in distributed messaging systems is called the poison message syndrome because it can literally kill your application. COM+ addresses the poison message syndrome by keeping track of the number of retries and managing the interval between them.
Besides creating the application public queue (where the calls are placed), COM+ creates five private retry queues and one dead queue when you enable queuing for a COM+ application (seeFigure 8-7). The dead queue is the final resting place for a message for which all delivery attempts have failed—it is a suspected poison message.
When a call is posted to a queued component, it is placed in the application public queue and a player tries to invoke it.
If the invocation fails, the message is put into Queue 0. COM+ tries to process the message again three times from queue 0, with a one-minute interval between retries. If the call still fails, the message continues to move up the retry queues, where COM+ retries three times from each queue, with ever-increasing intervals between the retries. The higher the number of the retry queue, the longer the interval between retries (Q_1 is 2 minutes, Q_2 is 4, Q_3 is 8, and Q_4 is 16). After the last attempt from the last retry queue fails, COM+ puts the message in the dead queue, from which there are no more retries, and the message is deemed poisonous.
The dead queue can accumulate an infinite number of messages. It is up to your application administrator to manage the dead queue. One simple course of action available to the administrator is to simply purge the queue of all the messages it contains. Another option is to put the message back in the application or retry queues, if the administrator believes that the call will succeed this time.
Your application administrator can also delete one or more of the retry queues and by doing so control the number and length of the retries; COM+ continues to move a message that continuously fails up the remaining retry queues. If all retry queues are deleted, a message that fails will be moved directly to the dead queue.
Sometimes it may not be possible for the call to succeed due to domain-specific constraints. For example, a customer may attempt to withdraw money from an account that has insufficient funds, or the customer account may close when the message is in the queue. Or, plain security settings may be a problem—the user who issued the queued call simply does not have the right credentials to carry out the call.
If the situation is brought to your product administrator’s attention (on the client and the server side) he or she might be able to do something about it. COM+ lets you associate an exception class with your queued component. In case of repeated failure, COM+ creates the exception class object and notifies it about the failure.
You associate an exception class with your queued component on the Advanced tab of your component’s properties page by specifying the prog-ID of the exception calls (see Figure 8-8). If a queued call cannot reach your component, COM+ instantiates the exception class and lets it handle the failure.
A queued component exception class is a COM+ component that
implements all the queued interfaces supported by your component and
a special interface called
IPlaybackControl
. COM+ uses the exception class object
if the call could not be delivered to the queued component, or if the
call persistently failed.
IPlaybackControl
has only two methods and is
defined as:
interface IPlaybackControl : IUnknown { HRESULT FinalClientRetry( ); HRESULT FinalServerRetry( ); };
The terms client and server are defined loosely in the interface. It really refers to which side of the queued call the error occurred on. Both the client and the server administrators can install the exception class, although each will be more interested in what happened on their side.
Delivering a message to the queued component queue is never guaranteed. If all attempts to deliver the message to the queued component queue fail, COM+ places the call on the client side in a public queue called the Xact Dead Letter queue. The Xact Dead Letter queue is shared by all clients on the same machine.
The dead letter queue has a listener associated with it called the Dead Letter Queue Monitor (DLQM)—a COM+ server application installed on every Windows 2000 machine. You can start the DLQM application manually or programmatically by calling into the COM+ Catalog. When the DLQM app is running, and it detects the message in the queue, it retrieves the target component from the message and checks for an exception class.
If the component has an exception class associated with it, the DLQM
instantiates the exception class and queries it for
IPlaybackControl
. Since this is a client-side
failure, the DLQM calls IPlaybackControl::FinalClientRetry( )
on the exception class, letting it know that client-side
failure of delivery is the reason it is invoked.
Next, the DLQM plays back all method calls from the message to the exception class. Recall that the exception class is required to implement the same set of queued interfaces as the component it is associated with.
If the exception class returns a failure status from any one of the
method calls, the message is returned to the Xact Dead Letter queue.
The DLQM deletes the message from the Xact Dead Letter Queue only if
the exception class returns S_OK
on all calls.
This error-handling schema allows the exception class to implement an alternative behavior for messages that cannot be sent to the server. For example, the exception class could generate a compensating transaction. Another course of action would pass in a queued notification object as a method parameter. The exception class would call the notification object, letting it know which calls failed. The notification object can in turn send an email to the customers asking them to resubmit the order, or it can take some other domain-specific error handling action.
Because all COM+ knows about the exception class is its ID, you can even provide deployment-specific exception classes and have a per-customer error handling policy.
Successful delivery of the message to the server side does not mean that the call will succeed—it could still fail for domain-specific reasons, including invalid method parameters, corrupt installation, and missing components.
As explained before, the message moves up through the retry queues in
case of repetitive invocation failures. When the final retry on the
last retry queue fails, COM+ retrieves the target component from the
message and checks for an exception class. Similar to its handling of
the failure on the sending client side, if the component has an
exception class associated with it, COM+ instantiates the exception
class, queries for IPlaybackControl
, and calls
IPlaybackControl::FinalServerRetry( )
. It does
this to let the exception class know that the failure took place on
the server side and that the message is about to be placed in the
dead queue.
COM+ then plays back all method calls from the message to the
exception class. The exception class can do whatever it deems fit to
handle the error, from sending an email to the application
administrator to alerting the user. If the exception class returns
S_OK
from all method calls, COM+ considers the
message delivered. If the exception class returned failure on any of
the queued calls, COM+ puts the message in the dead letter queue.
Regardless of where the error took place (sending or receiving side), your system or application administrator has to deal with it. Application administrators usually do not develop software for a living and know nothing about COM+, queued components, MSMQ retry queues, etc. It is up to you, the enterprise application developer, to provide your application administrator with tools to manage your product. You should deliver your main product with an application-oriented administration utility to manage retries of asynchronous calls and dead calls (on the server and client side).
The application administration utility should use, in its user interface, terminology from the domain at hand (such as shipment details) rather than queue names. Internally, it will probably interact with exception classes and notification objects. Your helper utility will probably need to move messages between retry queues, the dead queue, and the application queue.
For example, suppose a queued call destined for a customer accounts
management component failed because the specified account number was
invalid. The administration utility may prompt the administrator to
enter the correct account number for the customer and then put the
message back in the application queue, this time with the correct
account number. To enable you to move messages between queues easily,
COM+ provides you with the
IMessageMover
interface and a standard
implementation of it. The standard implementation is available for
the C++ developer by calling CoCreateInstance( )
using
CLSID_MessageMover
and for the Visual Basic developer by calling CreateObject( )
using the prog-ID
QC.MessageMover
.
The interface IMessageMover
is defined as:
interface IMessageMover : IDispatch { [id(1),propget] HRESULT SourcePath([out,retval]BSTR* pbstrPath); [id(1),propput] HRESULT SourcePath([in] BSTR bstrPath); [id(2),propget] HRESULT DestPath([out,retval] BSTR* pbstrPath); [id(2),propput] HRESULT DestPath([in]BSTR bstrPath); [id(3),propget] HRESULT CommitBatchSize([out,retval]long* plSize); [id(3),propput] HRESULT CommitBatchSize([in]long lSize); [id(4)] HRESULT MoveMessages([out, retval]long* plMessagesMoved); };
As you can see, IMessageMover
is a simple
interface. You can set the source and destination queues and call
MoveMessages( )
,
as shown in Example 8-2, in Visual Basic 6.0.
Example 8-2. Using the IMessageMover interface to move messages from the last retry queue to the application queue
Dim MessageMover As Object Dim MessagesMoved As Long Set MessageMover = CreateObject("QC.MessageMover") 'move all the messages from the last retry queue to the application queue MessageMover.SourcePath = ".PRIVATE$MyApp_4" MessageMover.DestPath = ".PUBLIC$MyApp" MessagesMoved = MessageMover.MoveMessages
IMessageMover
does not provide you with a way to
move fewer than the total number of messages on the queue, but it
does save you the agony of interacting directly with the MSMQ APIs.
See the MSDN Library for more information about using
the
IMessageMover
interface.