I have a self-hosted WCF service (v4 framework) that is exposed through a HttpTransport
-based custom binding. The binding uses a custom MessageEncoder
that is pretty much a BinaryMessageEncoder
with the addition of gzip compression functionality.
A Silverlight and a Windows client consume the web service.
Problem: in some cases the service had to return very large objects and occasionally threw OutOfMemory exceptions when responding to several concurrent requests (even if Task Manager reported ~600 Mb for the process). The exception happened in the custom encoder, when the message was about to be compressed, but I believe this was just a symptom and not the cause. The exception stated "failed to allocate x Mb" where x was 16, 32 or 64, not a overly huge amount -for this reason I believe something else already put the process near some limit before that.
The service endpoint is defined as follows:
var transport = new HttpTransportBindingElement(); // quotas omitted for simplicity
var binaryEncoder = new BinaryMessageEncodingBindingElement(); // Readerquotas omitted for simplicity
var customBinding = new CustomBinding(new GZipMessageEncodingBindingElement(binaryEncoder), transport);
Then I did an experiment: I changed TransferMode
from Buffered
to StreamedResponse
(and modified the client accordingly). This is the new service definition:
var transport = new HttpTransportBindingElement()
{
TransferMode = TransferMode.StreamedResponse // <-- this is the only change
};
var binaryEncoder = new BinaryMessageEncodingBindingElement(); // Readerquotas omitted for simplicity
var customBinding = new CustomBinding(new GZipMessageEncodingBindingElement(binaryEncoder), transport);
Magically, no OutOfMemory exceptions anymore. The service is a bit slower for small messages, but the difference gets smaller and smaller as message size grows. The behavior (both for speed and OutOfMemory exceptions) is reproducible, I did several tests with both configurations and these results are consistent.
Problem solved, BUT: I cannot explain myself what is happening here. My surprise stems from the fact that I did not change the contract in any way. I.e. I did not create a contract with a single Stream
parameter, etc., as you usually do for streamed messages. I am still using my complex classes with the same DataContract and DataMember attribute. I just modified the endpoint, that's all.
I thought that setting TransferMode was just a way to enable streaming for properly formed contracts, but obviously there is more than that.
Can anybody explain what actually happens under the hood when you change TransferMode
?