views:

20

answers:

1

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?

A: 

I've had some experience with WCF and streaming.

Basically, if you don't set the TransferMode to streamed, then it'll default to buffered. So if you are sending large pieces of data, it's going to build up the data on your end in memory and then send it once all the data is loaded and ready to be sent. This is why you were getting out of memory errors because the data was very large and more than your machine's memory.

Now if you use streamed, then it'll immediately start sending chunks of data to the other endpoint instead of buffering it up, making memory usage very minimal.

But this doesn't mean that the receiver also has to be set up for streaming. They could be setup to buffer and will experience the same problem as the sender did if they do not have sufficient memory for your data.

For the best results, both endpoints should be setup to handle streaming (for large data files).

Typically, for streaming, you use MessageContracts instead of DataContracts because it gives you more control over the SOAP structure.

See these MSDN articles on MessageContracts and Datacontracts for more info. And here is more info about Buffered vs Streamed.

Bryan Denny
Thanks for the answer, however it's still does not add up: if I have a large complex object and don't use a MessageContract, it must still be serialized as a whole (in memory) before being sent. It's not like I'm reading a file and transferring it byte-wise.
Francesco De Vittori
I was reading through your links and the last sentence of the last link is interesting: "Changing the transfer mode from buffered to streamed also changes the native channel shape of the TCP and named pipe transports (...)". This may shed some light.
Francesco De Vittori
@Francesco hmm, I'm not certain. I could never get it to stream a file without a MessageContract, the memory would blow up. However, I was not using v4 and I was explicitly using a stream, not a serialized object.
Bryan Denny