WCF: maxReceivedMessageSize and QuotaExceededException
Yesterday I came across a strange behaviour in WCF. If you use netMsmqBinding together with messages larger than 65536 bytes, your services can loop while trying to process poison messages.
The "thing" is a server side binding property for netMsmqBinding, named maxReceivedMessageSize. By default, this property has the value of 65536 bytes, and when larger messages are queued, the service simply rejects them, logging a QuotaExceededException in the e2e svctraceviewer file.
Apparently, this has nothing wrong. The problem is that, since Windows XP has no support to poison handling in MSMQ (this is an exclusive Vista feature, coming on MSMQ vNext), and the message is returned to the queue, the service will then process it again, and again, and again, in a FIFO basis.Normally, to workaround poison messages we normally would check the retrycound property from msmq messages in the service, but since this exception is thrown in the WCF pipeline, we can't do anything about it.
The MaxReceivedMessageSize property in the binding is, according to documentation, to prevent DOS attacks by sending too large messages and flooding the system. Until now, i found no solution or documentation about preventing this poison problem, and it seems that this generates exactly a DOS attack, and can't prevent it.
While trying to workaround this, I tried to catch the unhandledexception event from the appdomain, but without success.
In the last days, I was around the poison message handling in msmq applications, while using WCF, and my opinion is that this feature should come for XP users. It will be much more difficult to implement retry queues, poison queues and dead letter features, while doing this app by app, service by service, while this could be a platform feature. For example, while poison handling in WCF+Vista, can do all the queue routing stuff between retry and poison queues, you'll have to cheat using TTLs to do it. (this will stand for another post)
To simulate the large message problem., I'm using FEB CTP in a XP environment, running MSMQ in workgroup mode.
The code can be as simple as follows:
using System;
using System.Configuration;
using System.Messaging;
using System.ServiceModel;
// Define a service contract.
[ServiceContract()]
public interface IContract
{
[OperationContract(IsOneWay = true)]
void Operation(byte[] message);
}
public class Service : IContract
{
public void Operation(byte[] message)
{
Console.WriteLine(message.Length);
}
}
class Program
{
static void Main(string[] args)
{
string queueName = ConfigurationManager.AppSettings["queueName"];
if (!MessageQueue.Exists(queueName))
MessageQueue.Create(queueName, true);
using (ServiceHost serviceHost =
new ServiceHost(typeof(Service), new Uri(ConfigurationManager.AppSettings["baseAddress"])))
{
serviceHost.Open();
Console.WriteLine("The service is ready.");
using (ChannelFactory<IContract> factory = new ChannelFactory<IContract>(""))
{
IContract svc = factory.CreateChannel();
byte[] buffer = new byte[65535];
svc.Operation(buffer); //this will send a message larger than allowed
}
Console.WriteLine("Press <ENTER> to terminate service.");
Console.WriteLine();
Console.ReadLine();
// Close the ServiceHostBase to shutdown the service.
serviceHost.Close();
}
}
}
App.Config file:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="queueName" value=".\private$\wcftest" />
<add key ="baseAddress" value="http://localhost:8000/wcftest"/>
</appSettings>
<system.serviceModel>
<client>
<endpoint name=""
address="net.msmq://localhost/private/wcftest"
binding="netMsmqBinding"
bindingConfiguration="DefaultMsmqBinding"
contract="IContract" />
</client>
<services>
<service
name="Service">
<!-- Define NetMsmqEndpoint -->
<endpoint address="net.msmq://localhost/private/wcftest"
binding="netMsmqBinding"
bindingConfiguration="DefaultMsmqBinding"
contract="IContract" />
</service>
</services>
<bindings>
<netMsmqBinding><!--maxReceivedMessageSize ="65000"-->
<binding name="DefaultMsmqBinding" >
<security mode="None">
<transport msmqAuthenticationMode="None" msmqProtectionLevel="None"/>
</security>
</binding>
</netMsmqBinding>
</bindings>
</system.serviceModel>
<system.diagnostics>
<sources>
<!--<!– The ’switchValue’ determines the level of traces that will be outputted,
eg ‘Verbose’, ‘Warning’, ‘Error’, etc –>-->
<source name="System.ServiceModel" switchValue="Error" propagateActivity="true">
<listeners>
<clear />
<!--<!– The ‘initializeData’ attribute determines to which file the traces will be written –>-->
<add name="xml" type="System.Diagnostics.XmlWriterTraceListener"
initializeData="e2eTraceTest.e2e" />
<!--<!– To log to a Console, use this instead of previous Node:
<add name="console" type="System.Diagnostics.ConsoleTraceListener" />
–>-->
</listeners>
</source>
</sources>
<!--<!– Setting he ‘autoflush’ attribute to ‘true’ ensures that the trace sources flush to disk after each trace –>-->
<trace autoflush="true" />
</system.diagnostics>
</configuration>