private void WorkerThreadProc()

in sources/Google.Solutions.Ssh/SshWorkerThread.cs [197:417]


        private void WorkerThreadProc()
        {
            //
            // NB. libssh2 has limited support for multi-threading and in general,
            // it's best to use a libssh2 session from a single thread only. 
            // Therefore, all libssh2 operations are performed by this one thead.
            //

            using (SshTraceSource.Log.TraceMethod().WithoutParameters())
            {
                try
                {
                    using (workerThreadRundownProtection.Acquire())
                    using (var session = new Libssh2Session())
                    {
                        session.SetTraceHandler(
                            LIBSSH2_TRACE.SOCKET |
                                LIBSSH2_TRACE.ERROR |
                                LIBSSH2_TRACE.CONN |
                                LIBSSH2_TRACE.AUTH |
                                LIBSSH2_TRACE.KEX |
                                LIBSSH2_TRACE.SFTP,
                            SshTraceSource.Log.TraceVerbose);

                        if (!string.IsNullOrEmpty(this.Banner))
                        {
                            session.Banner = this.Banner!;
                        }

                        session.Timeout = this.ConnectionTimeout;

                        //
                        // Open connection and perform handshake using blocking I/O.
                        //
                        using (var connectedSession = session.Connect(this.endpoint))
                        using (var authenticatedSession = connectedSession.Authenticate(
                            this.credential,
                            this.keyboardHandler))
                        using (Disposable.Create(() => OnBeforeCloseSession()))
                        {
                            //
                            // Make sure the readyToSend handle remains valid throughout
                            // this thread's lifetime.
                            //
                            var readyToSendHandleSafeToUse = false;
                            this.readyToSend.DangerousAddRef(ref readyToSendHandleSafeToUse);
                            Debug.Assert(readyToSendHandleSafeToUse);

                            //
                            // With the channel established, switch to non-blocking I/O.
                            // Use a disposable scope to make sure that tearing down the 
                            // connection is done using blocking I/O again.
                            //
                            using (session.AsNonBlocking())
                            using (Disposable.Create(() => this.readyToSend.DangerousRelease()))
                            using (var readyToReceive = NativeMethods.WSACreateEvent())
                            {
                                //
                                // Create an event that is signalled whenever there is data
                                // available to read on the socket.
                                //
                                // NB. This is a manual-reset event that must be reset by 
                                // calling WSAEnumNetworkEvents.
                                //

                                if (NativeMethods.WSAEventSelect(
                                    connectedSession.Socket.Handle,
                                    readyToReceive,
                                    NativeMethods.FD_READ) != 0)
                                {
                                    throw new Win32Exception(
                                        NativeMethods.WSAGetLastError(),
                                        "WSAEventSelect failed");
                                }

                                //
                                // Looks good so far, consider the connection successful.
                                //
                                OnConnected();

                                //
                                // Set up keepalives. Because we use non-blocking I/O, we have to 
                                // send keepalives by ourselves.
                                //
                                // NB. This method must not be called before the handshake has completed.
                                //
                                connectedSession.ConfigureKeepAlive(false, this.KeepAliveInterval);

                                var waitHandles = new[]
                                {
                                    readyToReceive.DangerousGetHandle(),
                                    this.readyToSend.DangerousGetHandle()
                                };

                                while (!this.workerCancellationSource.IsCancellationRequested)
                                {
                                    var currentOperation = Operation.Receiving | Operation.Sending;

                                    try
                                    {
                                        // 
                                        // In each iteration, wait for 
                                        // (data received on socket) OR (user data to send)
                                        //
                                        // NB. The timeout should not be lower than approx.
                                        // one second, otherwise we spend too much time calling
                                        // libssh2's keepalive function, which causes the terminal
                                        // to become sluggish.
                                        //

                                        var waitResult = NativeMethods.WSAWaitForMultipleEvents(
                                            (uint)waitHandles.Length,
                                            waitHandles,
                                            false,
                                            (uint)this.SocketWaitInterval.TotalMilliseconds,
                                            false);

                                        if (waitResult == NativeMethods.WSA_WAIT_EVENT_0)
                                        {
                                            //
                                            // Socket has data available. 
                                            //
                                            currentOperation = Operation.Receiving;

                                            //
                                            // Reset the WSA event.
                                            //
                                            var wsaEvents = new NativeMethods.WSANETWORKEVENTS()
                                            {
                                                iErrorCode = new int[10]
                                            };

                                            if (NativeMethods.WSAEnumNetworkEvents(
                                                connectedSession.Socket.Handle,
                                                readyToReceive,
                                                ref wsaEvents) != 0)
                                            {
                                                throw new Win32Exception(
                                                    NativeMethods.WSAGetLastError(),
                                                    "WSAEnumNetworkEvents failed");
                                            }

                                            // 
                                            // Perform whatever receiving operation we need to do.
                                            //
                                            // NB. We already reset the WSA event, so we must now read
                                            // all data that's available.
                                            //
                                            OnReadyToReceive(authenticatedSession);
                                        }
                                        else if (waitResult == NativeMethods.WSA_WAIT_EVENT_0 + 1)
                                        {
                                            //
                                            // User has data to send. Perform whatever send operation 
                                            // we need to do.
                                            // 
                                            currentOperation = Operation.Sending;
                                            OnReadyToSend(authenticatedSession);
                                        }
                                        else if (waitResult == NativeMethods.WSA_WAIT_TIMEOUT)
                                        {
                                            // 
                                            // Channel is idle - use the opportunity to send a 
                                            // keepalive. Libssh2 will ignore the call if no
                                            // keepalive is due yet.
                                            // 
                                            connectedSession.SendKeepAlive();
                                        }
                                        else if (waitResult == NativeMethods.WSA_WAIT_FAILED)
                                        {
                                            throw new Win32Exception(
                                                NativeMethods.WSAGetLastError(),
                                                "WSAWaitForMultipleEvents failed");
                                        }
                                    }
                                    catch (Libssh2Exception e) when (e.ErrorCode == LIBSSH2_ERROR.EAGAIN)
                                    {
                                        //
                                        // Retry operation.
                                        //
                                    }
                                    catch (Exception e)
                                    {
                                        SshTraceSource.Log.TraceError(
                                            "Socket I/O failed for {0}: {1}",
                                            Thread.CurrentThread.Name,
                                            e);

                                        if ((currentOperation & Operation.Sending) != 0)
                                        {
                                            OnSendError(e);
                                        }
                                        else
                                        {
                                            //
                                            // Consider it a receive error.
                                            //
                                            OnReceiveError(e);
                                        }

                                        //
                                        // Bail out.
                                        //
                                        return;
                                    }
                                } // while
                            } // nonblocking
                        }
                    }
                }
                catch (Exception e)
                {
                    SshTraceSource.Log.TraceError(
                        "Connection failed for {0}: {1}",
                        Thread.CurrentThread.Name,
                        e);

                    OnConnectionError(e);
                }
            }
        }