views:

1427

answers:

4

I have an application that uses MSMQ for asynchronous processing of certain things.

I use WCF to put messages on to the queue and have a WCF MSMQ listener (a windows service) to receive messages and deal with them.

My problem is keeping this stable. What is the correct way to deal with (for example) the queue server (which is a separate box) going down? The other day this happened and the service just sat there - no exceptions were thrown, it just stopped receiving messages. I would want it to throw an exception when the queue server went down and then re-try to connect to it until it is able.

I have also noticed that executing a "stop" on the service often causes it to hang for quite a while before it finally stops.

Any code suggests or criticisms would be welcome. Obviously I did Google for this first, but most examples show me pretty much what I already have and I would like to make my system more robust than that.

Currently I have this:
(Note: IMyExampleServiceContract is my WCF service contract and QueueHandler is what implements it)

using System;
using System.ServiceProcess;
using System.ServiceModel;
using xyz.Common.Logging;
using xyz.MyExample.ServiceContracts;
using xyz.Common.Config;

namespace xyz.MyExample.MSMQListener
{
    /// <summary>
    /// The class that handles starting and stopping of the WCF MSMQ Listener windows service.
    /// It will respond to start and stop commands from within the windows services administration snap-in
    /// It creates a WCF NetMsmqBinding that watches a particular queue for messaages defined by a contract
    /// in the ServiceContracts project.
    /// </summary>
    public partial class MsmqListenerService : ServiceBase
    {
        /// <summary>
        /// The WCF service host
        /// </summary>
        private ServiceHost _serviceHost;

        /// <summary>
        /// Defines the maximum size for a WCF message
        /// </summary>
        private const long MaxMessageSize = 1024 * 1024 * 1024; // 1 gb
        /// <summary>
        /// Defines the maximum size for a WCF array
        /// </summary>
        private const int MaxArraySize = 1024 * 1024 * 1024; // 1 gb

        /// <summary>
        /// The queue name
        /// </summary>
        private readonly string _queueName;
        /// <summary>
        /// The queue server
        /// </summary>
        private readonly string _queueServer;

        /// <summary>
        /// Initializes a new instance of the <see cref="MsmqListenerService"/> class.
        /// </summary>
        public MsmqListenerService()
        {
            InitializeComponent();
            using (ConfigManager config = new ConfigManager())
            {
                _queueName = config.GetAppSetting("QueueName");
                _queueServer = config.GetAppSetting("QueueServer");
            }
        }

        /// <summary>
        /// When implemented in a derived class, executes when a Start command is sent to the service by the Service Control Manager (SCM) or when the operating system starts (for a service that starts automatically). Specifies actions to take when the service starts.
        /// <para>
        /// The logic in this method creates a WCF service host (i.e. something that listens for messages) using the <see cref="IMyExampleServiceContract"/> contract.
        /// The WCF end point is a NetMSMQBinding to the MyExample MSMQ server/queue.
        /// It sets up this end point and provides a class to handle the messages received on it.
        /// The NetMSMQBinding is a Microsoft WCF binding that handles serialisation of data to MSMQ. It is a ms proprietary format and means that the message on the queue
        /// can only be read by a WCF service with the correct contract information.
        /// </para>
        /// </summary>
        /// <param name="args">Data passed by the start command.</param>
        protected override void OnStart(string[] args)
        {
            try
            {
                Logger.Write("MyExample MSMQ listener service started.", StandardCategories.Information);

                Uri serviceUri = new Uri("net.msmq://" + QueueServer + QueueName);

                NetMsmqBinding serviceBinding = new NetMsmqBinding();
                serviceBinding.Security.Transport.MsmqAuthenticationMode = MsmqAuthenticationMode.None;
                serviceBinding.Security.Transport.MsmqProtectionLevel = System.Net.Security.ProtectionLevel.None;
                serviceBinding.MaxReceivedMessageSize = MaxMessageSize;
                serviceBinding.ReaderQuotas.MaxArrayLength = MaxArraySize;

                //QueueHandler implements IMyExampleServiceContract
                _serviceHost = new ServiceHost(typeof(QueueHandler));
                _serviceHost.AddServiceEndpoint(typeof(IMyExampleServiceContract), serviceBinding, serviceUri);

                _serviceHost.Open();
                Logger.Write("MyExample MSMQ listener service completed OnStart method.", StandardCategories.Information);
            }
            catch (Exception ex)
            {
                ExceptionReporting.ReportException(ex, "DefaultExceptionPolicy");
                throw;
            }

        }

        /// <summary>
        /// Gets the name of the queue to send to. 
        /// This is retrieved from the application settings under QueueName
        /// </summary>
        private string QueueName
        {
            get { return _queueName; }
        }

        /// <summary>
        /// Gets the name of the queue server to send to. 
        /// This is retrieved from the application settings under QueueServer
        /// </summary>
        private string QueueServer
        {
            get { return _queueServer; }
        }

        /// <summary>
        /// When implemented in a derived class, executes when a Stop command is sent to the service by the Service Control Manager (SCM). Specifies actions to take when a service stops running.
        /// </summary>
        protected override void OnStop()
        {
            if (_serviceHost != null)
            {
                _serviceHost.Close();
                _serviceHost = null;
            }
        }

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        public static void Main()
        {
            //Code will have to be compiled in release mode to be installed as a windows service
            #if (!DEBUG)
                try
                {
                    Logger.Write("Attempting to start queue listener service.", StandardCategories.Information);
                    ServiceBase[] ServicesToRun;
                    ServicesToRun = new ServiceBase[]
                   {
                new MsmqListenerService()
                   };
                    ServiceBase.Run(ServicesToRun);
                    Logger.Write("Finished ServiceBase.Run of queue listener service.", StandardCategories.Information);
                }
                catch (Exception e)
                {
                    ExceptionReporting.ReportException(e, "DefaultExceptionPolicy");
                    throw;
                }
            #else
                //This allows us to run from within visual studio
                MsmqListenerService service = new MsmqListenerService();
                service.OnStart(null);
                System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite);
            #endif

        }
    }
}

Cheers,
David

+3  A: 

I'm not sure why your service host is hanging, but I can definitely think of a couple of things to try to make it more reliable:

  • I'd make sure to hook into the Faulted event of the service host. That's usually a good place to recognize that you need to respawn your host again.
  • I'd set up a way for the service to ping itself, by having a special health-status queue on the remote queue server and have a second custom WCF service listening on that queue. Then I'd have the service host just fire messages into that queue regularly and check that:

a) it can send them successfully and

b) that the messages are being picked up and processed by the local WCF health service listening on that queue. That could be used to detect some possible error conditions.

tomasr
The faulted event looks handy. I assume you believe it won't enter this state if the MSMQ server fails though? (as you are recommending the "ping" service)The "ping" service sounds like a good work-around for testing the servers availability, I just thought that there would have been something built into WCF that would deal with this for me.Anyway, I'll give the Faulted event a try, so thanks for pointing me in it's direction.
David
To be honest, I just don't know for sure, hence why I recommended both options :)
tomasr
+2  A: 

The underlying WCF MSMQ listener is probably throwing an exception before it ever reaches your code. Which is a frustrating situation because it looks like nothing happens and worst your message gets dropped. Turn on WCF service tracing in your service configuration file.

Now when you run your service it will trace and give you more detail. Instead of straining your eyes through the XML bring up this log file with the MS Service Trace Viewer.

When I had this problem I was getting "System.ServiceModel.ProtocolException": An incoming MSMQ message contained invalid or unexpected .NET Message Framing information in its body. The message cannot be received. Ensure that the sender is using a compatible service contract with a matching SessionMode. My service contract had been changed to have the attribute SessionMode = SessionMode.Required, but the clients were not sending messages with a Transaction.

Helder
Thanks for the idea, I will turn on the tracing and give it a go.
David
A: 

If your main purpose of using MSMQ is to do asynchronous processing of function calls, why don't you create your application (or just this part of it) as a queued COM+ component. Ultimately, COM+ would also use MSMQ to dispatch and process the function calls, but you would be able to focus on high-level design decisions and let COM+ handle the plumbing jobs.

I have only dealt with COM+ using C++, but a quick Google search on "COM+ queued component C#" showed many links to tutorials on how to do this.

If you insist on doing this yourself, here are a few suggestions:

1) Are you using MSMQ in workgroup mode? This happens when your computer is not part of a domain. You might have permissions problem. See this question or here for solutions

2) Did you check the Event Logs of both the sender and receiver machine for error messages. It's easier to fix problems once you know what's wrong.

3) Is there any messages in the outgoing queue?

4) There's a Microsoft tool that let you test your queues, but I can't find it in a hurry. Any one remember it?

MaDDoG
A: 

Although WCF adds some cool features to MSMQ, sometimes you will achieve your goal just as easily and with greater control if you manually code the MSMQ processing.

If you manually process your queue you will be able to see exactly what is going on & deal with the MessageQueueExceptions that are thrown, e.g. you will be able to catch MessageQueueErrorCodes such as QueueNotFound or MachineNotFound.

Unfortunately this means also managing the poison message queue, adding transactions to the processing, adding a timeout period to the queue etc. All the stuff that WCF nicely takes care of for you.

The main benefit of using WCF is that you can then use WAS to instantiate a web application, rather than having a continually running Windows Service. If you aren't utilising this benefit then I don't really see any benefit to WCF - it is just abstracting away a lot of what you need to touch and see.


Just as a side note; perhaps you could check whether the server is available when you are putting messages on the queue? If the server is available to put the messages on the queue then the listener will be able to immediately process them.

Kirk Broadhurst