简体   繁体   中英

NetworkStream.ReadTimeout failing to block on NetworkStream.Read(…) when daylight savings are applied and clock moves backwards

I'm hoping someone can explain this behaviour, or if it's maybe a bug in .NET.

Moving backwards in time due to daylight savings means that NetworkStream pays no attention to its property ReadTimeout, and in the case of this code causes the loops to spin. (This is just an example to prove it's happening).

To reproduce the problem I am seeing, you will need to be set to a timezone that uses daylight savings, eg The United Kingdom.

  1. Set your TimeZone to UTC London and make sure the daylight savings time is ticked.
  2. Change your date back to 29th October 2017
  3. Set your time to 01:58:50 am
  4. Run code below and watch it spin when it applies the daylight saving time at what would be 2am, if it applies it correctly time should move back to 1am.
  5. Make sure you wait, it can take up to 30 seconds for it to start spinning.

Edit: After deeping investigation, after 1 hour, it stops spinning and the behaviour returns to normal and honours the ReadTimeout.

Any thoughts would be appreciated!

Client code:

class Program
{
    static bool running = false;

    static void Main(string[] args)
    {
        running = true;
        Task.Factory.StartNew(() => Run());

        Console.ReadKey();

        running = false;
    }


    static void Run()
    {
        TcpClient connection = new TcpClient("127.0.0.1", 1234);

        while (running)
        {
            if (connection != null && connection.Connected)
            {
                try
                {
                    NetworkStream stream = connection.GetStream();
                    stream.ReadTimeout = 1000;

                    byte[] buffer = new byte[1024];
                    int readCount = stream.Read(buffer, 0, 1024); // Should block here for the ReadTimeout duration if nothing received
                                                                  // However when daylight savings is applied and time moves backwards an hour, the stream.ReadTimeout = 1000; 
                                                                  // is not honoured and it falls through and spins

                    if (readCount > 0)
                    {
                        Console.WriteLine("Received some data");
                        //process read here
                    }
                    else
                    {
                        Console.WriteLine("ReadTimeout was not honoured");
                    }
                }
                catch (IOException)
                {
                    Console.WriteLine("Read timed out");
                }
            }
        }
    }
}

Server Code:

class Program
    {
        static bool running = false;

        public static void Main()
        {
            TcpListener server = null;
            try
            {
                // Set the TcpListener on port 13000.
                Int32 port = 5000;
                IPAddress localAddr = IPAddress.Parse("192.168.1.69");

                // TcpListener server = new TcpListener(port);
                server = new TcpListener(localAddr, port);

                // Start listening for client requests.
                server.Start();

                // Enter the listening loop.
                while (true)
                {
                    Console.Write("Waiting for a connection... ");

                    // Perform a blocking call to accept requests.
                    // You could also user server.AcceptSocket() here.
                    TcpClient client = server.AcceptTcpClient();
                    Console.WriteLine("Connected!");


                    // Get a stream object for reading and writing
                    NetworkStream stream = client.GetStream();

                    running = true;

                    Task.Factory.StartNew(() => Run(stream));


                    Console.ReadKey();

                    // Shutdown and end connection
                    client.Close();
                }
            }
            catch (SocketException e)
            {
                Console.WriteLine("SocketException: {0}", e);
            }
            finally
            {
                // Stop listening for new clients.
                server.Stop();
            }


            Console.WriteLine("\nHit enter to continue...");
            Console.Read();
        }

        static async Task Run(NetworkStream stream)
        {
            byte[] stuffToSend = Encoding.ASCII.GetBytes("Stuff to send");
            while (running)
            {
                stream.Write(stuffToSend, 0, stuffToSend.Length);
                await Task.Delay(1000);
            }
        }
    }

There are some very important notes in the OS documentation that aren't mentioned in the .NET ReadTimeout property:

SO_RCVTIMEO and SO_SNDTIMEO

When using the recv function, if no data arrives during the period specified in SO_RCVTIMEO , the recv function completes. In Windows versions prior to Windows 2000, any data received subsequently fails with WSAETIMEDOUT . In Windows 2000 and later, if no data arrives within the period specified in SO_RCVTIMEO , the recv function returns WSAETIMEDOUT , and if data is received, recv returns SUCCESS.

If a send or receive operation times out on a socket, the socket state is indeterminate, and should not be used; TCP sockets in this state have a potential for data loss, since the operation could be canceled at the same moment the operation was to be completed.

Currently, after each timeout you are looping and trying another receive operation on the same socket. But this note makes it very clear that you need to create a new connection.

Please read this. https://msdn.microsoft.com/en-us/library/ms973825.aspx

In general, if you are dealing with absolute elapsed time, such as measuring a timeout, performing arithmetic, or doing comparisons of different DateTime values, you should try and use a Universal time value if possible so that you get the best possible accuracy without effects of time zone and/or daylight savings having an impact.

Just adjust the thread to use universal time.

Edit 1.

I failed to understand your question. You did not want a workaround. Its a known bug related to how .net manages the, and related discussion has been done before here.

Cache.Add absolute expiration - UTC based or not?

During one hour at the end of daylight savings time, your local time is ambiguous, so you can get unexpected results, ie the absolute expiration can be one hour longer than expected.

Edit 2. Added another discussion thread more in context with the OP question.

https://social.msdn.microsoft.com/Forums/vstudio/en-US/cb0369f4-8c96-484a-a33e-5e4c850b995e/wcf-channels-and-datetime-change?forum=wcf

Here is the sourced code : http://referencesource.microsoft.com/#mscorlib/system/io/stream.cs,f956b0c07e86df64

    [ComVisible(false)]
        public virtual int ReadTimeout {
            get {
                Contract.Ensures(Contract.Result<int>() >= 0);
                throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_TimeoutsNotSupported"));
            }
            set {
                throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_TimeoutsNotSupported"));
            }
        }

        [ComVisible(false)]
        public virtual int WriteTimeout {
            get {
                Contract.Ensures(Contract.Result<int>() >= 0);
                throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_TimeoutsNotSupported"));
            }
            set {
                throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_TimeoutsNotSupported"));
            }
        }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM