Bug 5742 - System.IO.IOException when closing HttpListener
Summary: System.IO.IOException when closing HttpListener
Status: RESOLVED INVALID
Alias: None
Product: Class Libraries
Classification: Mono
Component: System ()
Version: master
Hardware: PC Linux
: --- normal
Target Milestone: Untriaged
Assignee: Martin Baulig
URL:
Depends on:
Blocks:
 
Reported: 2012-06-19 07:32 UTC by Marek Safar
Modified: 2012-06-22 12:12 UTC (History)
3 users (show)

Tags:
Is this bug a regression?: ---
Last known good build:

Notice (2018-05-24): bugzilla.xamarin.com is now in read-only mode.

Please join us on Visual Studio Developer Community and in the Xamarin and Mono organizations on GitHub to continue tracking issues. Bugzilla will remain available for reference in read-only mode. We will continue to work on open Bugzilla bugs, copy them to the new locations as needed for follow-up, and add the new items under Related Links.

Our sincere thanks to everyone who has contributed on this bug tracker over the years. Thanks also for your understanding as we make these adjustments and improvements for the future.


Please create a new report on GitHub or Developer Community with your current version information, steps to reproduce, and relevant error messages or log files if you are hitting an issue that looks similar to this resolved bug and you do not yet see a matching new report.

Related Links:
Status:
RESOLVED INVALID

Description Marek Safar 2012-06-19 07:32:50 UTC
using System;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Text;

class C
{
	public class MyNetworkStream : NetworkStream
	{
		public MyNetworkStream (Socket sock) : base (sock, true)
		{
		}

		public Socket GetSocket ()
		{
			return Socket;
		}
	}

	public static void Main ()
	{
		while (true) {

			HttpListenerContext ctx;
			HttpListenerRequest request;
			NetworkStream ns;

			HttpListener listener = CreateAndStartListener (
				"http://127.0.0.1:9000/HasEntityBody/");

			// GET with chunked encoding
			ns = CreateNS (9000);
			Send (ns, "GET /HasEntityBody HTTP/1.1\r\nHost: 127.0.0.1\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n");
			ctx = listener.GetContext ();
			request = ctx.Request;
			Send (ctx.Response.OutputStream, "%%%OK%%%");

			// PUT with non-zero Content-Lenth
			ns = CreateNS (9000);
			Send (ns, "PUT /HasEntityBody/ HTTP/1.1\r\nHost: 127.0.0.1\r\nContent-Length: 3\r\n\r\n123");
			ctx = listener.GetContext ();

			listener.Close ();

			Console.Write (".");
		}
	}

	public static MyNetworkStream CreateNS (int port)
	{
		return CreateNS (port, 5000);
	}

	public static MyNetworkStream CreateNS (int port, int timeout_ms)
	{
		Socket sock = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
		sock.Connect (new IPEndPoint (IPAddress.Loopback, port));
		sock.SendTimeout = timeout_ms;
		sock.ReceiveTimeout = timeout_ms;
		return new MyNetworkStream (sock);
	}

	public static void Send (Stream stream, string str)
	{
		byte [] bytes = Encoding.ASCII.GetBytes (str);
		stream.Write (bytes, 0, bytes.Length);
	}

	public static HttpListener CreateAndStartListener (string prefix)
	{
		HttpListener listener = new HttpListener ();
		listener.Prefixes.Add (prefix);
		listener.Start ();
		return listener;
	}
}
Comment 1 Marek Safar 2012-06-19 07:33:28 UTC
When executed 

Unhandled Exception: System.IO.IOException: Write failure ---> System.Net.Sockets.SocketException: Connection reset by peer
Comment 2 Robert Wilkens 2012-06-20 21:57:16 UTC
Note: This appears to be a problem in mono 2.10.8 on windows as well but it works in MS.NET (as long as it's run with administrator privileges) :

Unhandled Exception: System.IO.IOException: Write failure ---> System.Net.Socket
s.SocketException: An existing connection was forcibly closed by the remote host
.

The wording on the exception is different, but i believe it's basically the same error.
Comment 3 Robert Wilkens 2012-06-21 17:13:27 UTC
I am going to walk away from this problem for a little while but i looked at it a little and i wanted to note what i found:

It looks like trailer_sent is not being set in some places where it should be set in ResponseStream (meaning anywhere it should be set), though i haven't traced through the code to find out where that should be, note if it should be set in some places, perhaps it should be reset in others, I don't have the time to find out which now.

However, right now, trailer_sent is only set in Close() and is not set anywhere else (it's default unassigned value is false) in ResponseStream.cs, even though there are other places it clearly seems to send the exact same data as the trailer which would, i presume (which is always bad to do), indicate end of connection to the remote end, giving the remote end the indication that it can 'hang up' the connection, resulting in the problem we have here (the remote ending the connection, then us sending more on close because it thinks we haven't sent the trailers yet).

It's only on about line 95 where we call InternalWrite() that we have this crash, if i comment out that line, the crash goes away, but that is probably not the right solution.  It's never on other calls to InternalWrite() in Close.  Note that on Line 95, we've established that the headers have already been sent, because otherwise we would do the InternalWrite() in the if (ms != null) condition in Close which never crashes, even though that also gets hit on some of the Close calls, but again it never crashes there (how's this for a run on sentence).

The code in question was introduced in this commit:
https://github.com/mono/mono/commit/07c165832bc3518ab4038ff84c70999355cd30cc
And again it's on line 95 of that commit (the InternalWrite call) where it will occasionally crash on connection close, it looked like it was basically doing the same thing before the commit, so i don't blame the one who made the commit.

I will try to look into this more when i get more time.  I could be completely wrong here, but again the fact that commenting out line 95 gets rid of the crash doesn't indicate that is the right fix, but it does indicate the trailers probably have already been sent but we just didn't mark it.
Comment 4 Robert Wilkens 2012-06-21 17:28:10 UTC
Just double checked myself when i read what i wrote and -- where i said:

 it clearly seems to send the exact same data as the
trailer which would,

I was wrong, sorry i was trying to make notes before leaving the issue behind for now.  The trailer apparently ends with two \r\n (cr+newline) combinations, and it is only in close where that happens.

Sorry, i was trying to make notes before i walked away.  I wish there were a way to delete my comments, in the future i won't make them until i am sure.
Comment 5 Robert Wilkens 2012-06-21 18:10:34 UTC
Ok, just for the record, i deduced where the problem is:

In the sample code, 'ns' is reassigned, and hence the original ns is probably closed before the listener is done with it.  Or did you already know that?

If I changed the main loop as follows it does not crash:


 public static void Main ()
    {
        while (true) {

            HttpListenerContext ctx;
            HttpListenerRequest request;
            NetworkStream ns,ns2;

            HttpListener listener = CreateAndStartListener (
                "http://127.0.0.1:9000/HasEntityBody/");

            // GET with chunked encoding
            ns = CreateNS (9000);
            Send (ns, "GET /HasEntityBody HTTP/1.1\r\nHost: 127.0.0.1\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n");
            ctx = listener.GetContext ();
            request = ctx.Request;
            Send (ctx.Response.OutputStream, "%%%OK%%%");

            // PUT with non-zero Content-Lenth
            ns2 = CreateNS (9000);
            Send (ns2, "PUT /HasEntityBody/ HTTP/1.1\r\nHost: 127.0.0.1\r\nContent-Length: 3\r\n\r\n123");
            ctx = listener.GetContext ();

            listener.Close ();

            Console.Write (".");
        }
    }

I'm not going to troubleshoot this further, I'll leave it to Martin.  Sorry for stepping in.
Comment 6 Robert Wilkens 2012-06-21 20:41:22 UTC
Update: If I let this run long enough in Windows (Microsoft .NET) it crashes too, I think we're dealing with bad sample code, the reason for the difference in delays before the crash probably has something to do with garbage collection differences (when does the trashed "ns" actually get closed), please try it again with the replaced 'main' i provided:

................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
......
Unhandled Exception: System.Net.HttpListenerException: An operation was attempte
d on a nonexistent network connection
   at System.Net.HttpResponseStream.Write(Byte[] buffer, Int32 offset, Int32 siz
e)
   at C.Send(Stream stream, String str)
   at C.Main()

---
Comment 7 Miguel de Icaza [MSFT] 2012-06-22 12:12:14 UTC
At this point, it seems that this is not a mono bug.