Beispiel #1
0
        public async void StopTcpMessageListener()
        {
            Console.WriteLine("StopTcpMessageListener() exiting...");
            // you can finish current TcpConnections properly if it is important
            // to Dispose the TcpListener, this hack has to be used to do a Last, Final Connection: http://stackoverflow.com/questions/19220957/tcplistener-how-to-stop-listening-while-awainting-accepttcpclientasync
            TcpClient dummyClient = new TcpClient();
            // Connecting to listening-all-meta-address 0.0.0.0 is not possible. (SocketException : The requested address is not valid). We have to connect to 127.0.0.1 instead
            var dummyClientIp = (m_privateIP == ServerIp.LocalhostMetaAllPrivateIpWithIP) ? ServerIp.LocalhostLoopbackWithIP : m_privateIP;
            await dummyClient.ConnectAsync(dummyClientIp, m_port);

            Console.WriteLine($"StopTcpMessageListener(). Is DummyClient connected: {dummyClient.Connected}");
            Utils.TcpClientDispose(dummyClient);

            Console.WriteLine("StopTcpMessageListener() exiting..");
            if (m_tcpListener != null)
            {
                m_tcpListener.Stop();   // there is no Dispose() method
            }
            Console.WriteLine("StopTcpMessageListener() exiting.");

            // Tasks create Background Threads always, so the TcpListenerLoop() is a Background thread, it will exits when main thread exits, which is OK.
        }
Beispiel #2
0
        // 2020-06-07: After HTTP GET '/rtp' as real-time price query message sent to Vbroker, the server slowed down with 100% CPU.
        // 1 minute later Kestrel logged: #Warn: Heartbeat took longer than "00:00:01"
        // https://github.com/dotnet/aspnetcore/issues/17321    https://github.com/dotnet/aspnetcore/issues/4760
        // this is usually happens if thread pool is starved, because it cannot find any free threads in threadpool, because each of them is blocked and waiting.
        // It is very suspicios that somehow this Tcp connection/write/read left or the DelayTask was left on, and didn't finish properly.
        // next time it happens check Top again, showing nTH (Number of Threads). In normal cases, there are 22-23 threads in the Threadpool waiting for connections from clients.
        // https://www.golinuxcloud.com/check-threads-per-process-count-processes/
        // If it goes to 50, then it shows it is really the problem that there are no free threads. Then we have to rewrite this Tcp communication code.
        // Alternatively, it might be possible ASP.Net Core 2.0 has some bug, which was later corrected in ASP.NET core 3.0, and as we migrate from SqLab to SqCore, we don't really have to worry about this.
        // 2020-06-09: same thing. Number of threads: 25-28, it is not excessive.
        private async Task <string?> SendMessage()
        {
            // https://stackoverflow.com/questions/17118632/how-to-set-the-timeout-for-a-tcpclient/43237063#43237063
            string?   reply       = null;
            TcpClient client      = new TcpClient();
            Task?     connectTask = null;
            bool      wasTimeout  = false;

            try
            {
                // connectTask = client.ConnectAsync(TcpServerHost, TcpServerPort);      // usually, we create a task with a CancellationToken. However, this task is not cancellable. I cannot cancel it. I have to wait for its finish.
                IPAddress serverIP = IPAddress.Parse(TcpServerHost);        // it can remove the overhead of the DNS resolution every time.
                connectTask = client.ConnectAsync(serverIP, TcpServerPort); // usually, we create a task with a CancellationToken. However, this task is not cancellable. I cannot cancel it. I have to wait for its finish.

                // Problem: if the timeout cancellation completes first we return to the caller the empty string. Fine.
                // And THEN maybe 10 minutes later the connectTask really terminates with an Exception,
                // then, we should observe that exception, otherwise TaskScheduler.UnobservedTaskException will be raised
                // We should ALWAYS observe the connectTask.Exception (both in timeout, and no timeout cases)
                Task connectContinueTask = connectTask.ContinueWith(connTask =>
                {
                    // we should observe that exception, otherwise TaskScheduler.UnobservedTaskException will be raised
                    Utils.Logger.Debug("TcpMessage.SendMessage(). connectContinueTask BEGIN.");
                    if (connTask.Exception != null) // don't raise Error (which logs to console), just a warning. Caller should decide if this is expected sometimes or it is error.
                    {
                        Utils.Logger.Warn(connTask.Exception, $"Warn:TcpMessage.SendMessage(). Exception in ConnectAsync({TcpServerHost}:{TcpServerPort}).");
                    }

                    // If there was a timeout cancellation, we try to dispose it here, because we couldn't do it in the main thread.
                    if (wasTimeout && connTask.IsCompleted)
                    {
                        connTask.Dispose();
                    }
                });

                var   cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));      // timout 30sec. After which cts.Cancel() is called. That will trigger cancellation of the task
                var   taskWithTimeoutCancellation = connectContinueTask.WithCancellation(cts.Token);
                await taskWithTimeoutCancellation;

                if (connectTask.Exception != null)
                {
                    // don't raise Error (which logs to console), just a warning. Caller should decide if this is expected sometimes or it is error.
                    Utils.Logger.Warn($"Warn. TcpMessage.SendMessage(). client.ConnectAsync({TcpServerHost}:{TcpServerPort}) completed without timeout, but Exception occured.");
                }
                else
                {
                    Utils.Logger.Debug("TcpMessage.SendMessage(). client.ConnectAsync({TcpServerHost}:{TcpServerPort}) completed without timeout and no Exception occured");
                    BinaryWriter bw = new BinaryWriter(client.GetStream()); // sometimes "System.InvalidOperationException: The operation is not allowed on non-connected sockets." at TcpClient.GetStream()
                    SerializeTo(bw);

                    if (ResponseFormat != TcpMessageResponseFormat.None)
                    {
                        BinaryReader br = new BinaryReader(client.GetStream());
                        reply = br.ReadString(); // sometimes "System.IO.EndOfStreamException: Unable to read beyond the end of the stream." at ReadString()
                    }
                }
            }
            catch (Exception e) // in local Win development, Exception: 'No connection could be made because the target machine actively refused it' comes here.
            {
                Utils.Logger.Error(e, "Error:TcpMessage.SendMessage exception. Check both AWS and Linux firewalls!");

                if (e is OperationCanceledException)
                {
                    wasTimeout = true;
                    Utils.Logger.Error(e, "Error:TcpMessage.SendMessage exception. connectTask was cancelled by our timeout");
                }

                // we should observe that exception, otherwise TaskScheduler.UnobservedTaskException will be raised
                if (connectTask != null && connectTask.Exception != null)
                {
                    Utils.Logger.Error(connectTask.Exception, "Error:TcpMessage.SendMessage(). Exception in ConnectAsync() task.");
                }
            }
            finally
            {
                // 'A task may only be disposed if it is in a completion state (RanToCompletion, Faulted or Canceled).'
                // If there was a timeout cancellation, we cannot dispose it. It is still running and it may finish 10min later and GC will dispose it.
                if (connectTask != null && connectTask.IsCompleted)
                {
                    connectTask.Dispose();
                }
                Utils.TcpClientDispose(client);
            }
            return(reply);  // in case of timeout, return null string to the caller.
        }