C# Tips

C# Tip Article

Bug Fix : Cannot close stream until all bytes are written

This is about a bug that I found recently.

One component calling other company's Web API threw exceptions. Most of times the component worked fine but got errors for few cases.
The error message is something like this:

  Cannot close stream until all bytes are written.
  The request was aborted: The request was canceled.

And callstack of the exception looks like this:

   at System.Net.ConnectStream.CloseInternal(Boolean internalCall, Boolean aborting)
   at System.Net.ConnectStream.System.Net.ICloseEx.CloseEx(CloseExState closeState)
   at System.Net.ConnectStream.Dispose(Boolean disposing)
   at System.IO.Stream.Close()
   at Processor.SendRequest(String request)

Judging from the callstack, we might guess something wrong when closing stream. But, by looking at the source code, it does not look like something wrong about closing. Here is the code snippet.

private string SendRequest(string request)
{
	var webReq = (HttpWebRequest)WebRequest.Create(/* elided */);
	webReq.Method = "POST";
	//...
	webReq.ContentLength = request.Length;
	using (Stream writeStream = webReq.GetRequestStream())
	{
		UTF8Encoding encoding = new UTF8Encoding();
		byte[] bytes = encoding.GetBytes(request);
		writeStream.Write(bytes, 0, bytes.Length);
	}
	//...
	string result = string.Empty;
	using(var response = (HttpWebResponse)webReq.GetResponse())
	{
  	    using(var respStrm = response.GetResponseStream())
	    {
	         using (var rs = new StreamReader(respStrm, Encoding.UTF8))
	        {
	            result = rs.ReadToEnd();
	        }
	    }
	}
	return result;
}

Since the code uses using { } block, the stream is supposed to close correctly.
The keypoint here is when the error occurs. By investigating input data, I learned it does not work when non-English characters are passed.

Right, UTF-8 encoding will use one byte for ASCII characters, but for other foreign characters, it can be up to 4 bytes. The code sets ContentLength to input's string length, but actual bytes can be more than that if UTF-8 encoding uses 2~4 bytes. So WebRequest stream is considered as invalid, and the exception occurs when closing the stream at finally block (using {...} block interpreted as try...finally block).

So the fix will be to set correctly byte length. Perform UTF-8 encoding first and set correct byte length to ContentLength property. Something like this:

UTF8Encoding encoding = new UTF8Encoding();
byte[] bytes = encoding.GetBytes(request);
webReq.ContentLength = bytes.Length;
using (Stream writeStream = webReq.GetRequestStream())
{
    writeStream.Write(bytes, 0, bytes.Length);
}