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

1

6

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?

Hunk answered 8/2, 2016 at 22:29 Comment(1)
From the documentation: "Notes to Callers: The Stop method also closes the underlying Socket, and creates a new Socket for the TcpListener. If you set any properties on the underlying Socket prior to calling the Stop method, those properties will not carry over to the new Socket.", from that it sounds like calling Stop() does release the socket but it immedatly starts a new one. EDIT: nevermind, it is a unbound socket.Asphyxiant
H
7

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);
    }
Hunk answered 9/2, 2016 at 22:8 Comment(2)
Great question, and great answer! Good repro example tooPejorative
Excellent answer. Benn stuck on this issue for more than a day. The P/Invoke fixed it.Coles

© 2022 - 2025 — McMap. All rights reserved.