Although I am responsible for writing a good portion of the programmer documentation for WCF, there are always little things that you "realize" suddenly that you didn't quite understand. Today, I learned from Shy Cohen and Maheshwar Jayaraman (thanks guys!) about OperationContractAttribute.IsOneWay -- the external documentation for which is here and here and is wrong, or certainly not right enough. Sigh. Shall I try again?
The second link typifies my misunderstanding. It says:
A one-way operation is one in which a client invokes an operation and continues processing after WCF queues the message for sending by the client.
Ah... not quite. What I now understand is that IsOneWay operations are really only for the case when you do not require a return message. However, the client object call does not return until the data is succesfully written to the wire -- which means that if the service cannot read the data from the wire that the client object blocks. Let me say this again: clients can block on one-way operations because the client object only returns once channel.Send() returns.
For the most part, the scenario in which a client can block on a oneway call is when a client object makes a large number of one-way calls in a tight loop. For example in the following code the Print() method is a one-way operation.
PrintClient client = new PrintClient(); for (int i = 0; i < 3000; i++) // <= 300 will not block, 3000 will { client.Print("String #" + i.ToString()); } Console.WriteLine("Client is done");
In this case, the service's ability to retrieve data off the wire using the default settings is quickly reduced; suddenly the client.Print call begins blocking on the return from the Send() call at the transport level. A loop like the above but with 300 or fewer calls will print out "Client is done" usally before the service can print out that it has received a message. With 3000, however, the service can print out most messages before the client is able to complete the loop.
This behavior has several ramifications that I need to put into the documentation, but I'll write them out here first.
More generally, the way around this problem is to insert buffers that separate the client call from the transport send action. The problem is that any such buffer will have its own limits that you must be aware of. Options include the following.
In all cases, the buffer you use will still have some limit, especially to protect against denial of service attacks. The big takeaway is:
Of course, if the transport were to change and support a truer fire-and-forget model, such as UDP, you would not encounter this problem because the transport would return from Send once the data was sent regardless of the ability of the service to read the data. There it is. Now, to rewrite the documentation....