Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.NET TcpListener Stop method does not stop the listener when there is a child process

I am working with some legacy TCP server code that works with sockets directly as it was written in .NET 2.0 and earlier. The server has a feature to 'stop' and 'start' accepting client connections.

To troubleshoot the issue I run the server in the console mode as admin user. On top of that I have eliminated the socket accept thread from the equation and all the code does is something like:

tcpListener = new TcpListener(IPAddress.Any, this.Port);
tcpListener.Start();

and

tcpListener.Stop();

This called from different methods. I have debugged the code and I am pretty sure the code executes only once. However, the issue is that call to Stop does not actually releases the socket address and subsequent call to Start therefore fails with the error "Only one usage of each socket address (protocol/network address/port) is normally permitted". I can also confirm from the ProcessExplorer that the server is still listening on the server port.

When I write a small console app that uses the same code snippets everything works fine. I have even tried tracing the .NET network and socket libraries, but there is no error or anything to indicate problems there.

It is not clear to me why call to Stop does not release the socket address?

Update: After more investigation it turns out that there is some strange effect of child process launch to the TcpListener. I have made a 'bare bone' sample code that illustrates the issue:

using System;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Threading;

namespace TcpListenerStartStop
{
    class MyTcpListener
    {
        public static void Main(string[] args)
        {
            Int32 port = 13000;

            if (args.Length > 0) // indicates child process
            {
                Thread.Sleep(4000); // as a child do nothing and wait for a few seconds
            }
            else // parent will play with the TcpListener
            {
                //LaunchChildProcess(); // launch child here and listener restart is fine?!?

                var tcpListener = new TcpListener(IPAddress.Any, port);
                tcpListener.Start();

                Console.WriteLine("Starting test in 2 seconds...");
                Thread.Sleep(2000);

                LaunchChildProcess(); // launch child here and listener restart is not fine?!?

                tcpListener.Stop();
                Console.WriteLine("Stopped.");
                Thread.Sleep(1000);

                tcpListener.Start();
                Console.WriteLine("Started");
            }

            Console.WriteLine("All is good, no exceptions :)");
        }

        private static void LaunchChildProcess()
        {
            Process process = new Process();
            var processStartInfo = new ProcessStartInfo
            {
                CreateNoWindow = true,
                FileName = Assembly.GetExecutingAssembly().Location,
                UseShellExecute = false, // comment out this line out listener restart is fine?!?
                Arguments = "child"
            };
            process.StartInfo = processStartInfo;

            process.Start();
        }
    }
}

As you can see from the code if I launch the child process before creating a listener everything is fine, if I do it after listener restart fails. This has something to do with the UseShellExecute = false option for the child process.

Not sure if this is .NET bug or some special behavior I was not aware of?

like image 305
Slobodan Savkovic Avatar asked Feb 08 '16 22:02

Slobodan Savkovic


1 Answers

The real reason for this behavior is that TcpListener socket handle is inherited by the child process together with many other handles. Some discussions on the topic can be found here and here.

One obvious solution is to launch the child process before initializing TcpListener.

Another solution is to have UseShellExecute = true to avoid this socket handle inheritance.

Ideal solution would be to set socket handle option to prevent the inheritance by the child process. It is possible to do it in .NET via a P/Invoke on the TcpListener socket handle:

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool SetHandleInformation(IntPtr hObject, uint dwMask, uint dwFlags);
    private const uint HANDLE_FLAG_INHERIT = 1;

    private static void MakeNotInheritable(TcpListener tcpListener)
    {
        var handle = tcpListener.Server.Handle;
        SetHandleInformation(handle, HANDLE_FLAG_INHERIT, 0);
    }
like image 63
Slobodan Savkovic Avatar answered Sep 28 '22 14:09

Slobodan Savkovic