Esempio n. 1
0
        private static Task WachdogInactivity()
        {
            if (!Inactivity)
            {
                Inactivity = true;

                return(Task.Run(() =>
                {
                    for (int i = Settings.Profile.TimeoutIdle; i > 0; i--)
                    {
                        if (!Inactivity)
                        {
                            goto Normal;
                        }
                        Task.Run(() => InactivityTimer?.Invoke(i));
                        Thread.Sleep(1000);
                    }
                    if (!Inactivity)
                    {
                        goto Normal;
                    }
                    Task.Run(() => InactivityTimer?.Invoke(0));
                    Task.Run(() => InactivityError?.Invoke());
                    Waching = false;
                    return;

                    Normal:
                    Task.Run(() => InactivityTimer?.Invoke(-1));
                    return;
                }));
            }
            return(null);
        }
Esempio n. 2
0
        private async Task WriteInternalAsync(byte[] bytes, CancellationToken cancellationToken)
        {
            InactivityTimer?.Reset();

            var totalBytesWritten = 0;

            try
            {
                while (totalBytesWritten < bytes.Length)
                {
                    var bytesRemaining = bytes.Length - totalBytesWritten;
                    var bytesToWrite   = bytesRemaining > TcpClient.Client.SendBufferSize ? TcpClient.Client.SendBufferSize : bytesRemaining;

                    await Stream.WriteAsync(bytes, totalBytesWritten, bytesToWrite, cancellationToken).ConfigureAwait(false);

                    totalBytesWritten += bytesToWrite;

                    DataWritten?.Invoke(this, new ConnectionDataEventArgs(totalBytesWritten, bytes.Length));
                    InactivityTimer?.Reset();
                }
            }
            catch (Exception ex) when(!(ex is TimeoutException) && !(ex is OperationCanceledException))
            {
                Disconnect($"Write error: {ex.Message}");
                throw new ConnectionWriteException($"Failed to write {bytes.Length} bytes to {IPAddress}:{Port}: {ex.Message}", ex);
            }
        }
Esempio n. 3
0
        /// <summary>
        ///     Asynchronously connects the client to the configured <see cref="IPAddress"/> and <see cref="Port"/>.
        /// </summary>
        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
        /// <returns>A Task representing the asynchronous operation.</returns>
        /// <exception cref="InvalidOperationException">
        ///     Thrown when the connection is already connected, or is transitioning between states.
        /// </exception>
        /// <exception cref="TimeoutException">
        ///     Thrown when the time attempting to connect exceeds the configured <see cref="ConnectionOptions.ConnectTimeout"/> value.
        /// </exception>
        /// <exception cref="OperationCanceledException">
        ///     Thrown when <paramref name="cancellationToken"/> cancellation is requested.
        /// </exception>
        /// <exception cref="ConnectionException">Thrown when an unexpected error occurs.</exception>
        public async Task ConnectAsync(CancellationToken?cancellationToken = null)
        {
            if (State != ConnectionState.Pending && State != ConnectionState.Disconnected)
            {
                throw new InvalidOperationException($"Invalid attempt to connect a connected or transitioning connection (current state: {State})");
            }

            cancellationToken = cancellationToken ?? CancellationToken.None;

            // create a new TCS to serve as the trigger which will throw when the CTS times out a TCS is basically a 'fake' task
            // that ends when the result is set programmatically. create another for cancellation via the externally provided token.
            var timeoutTaskCompletionSource      = new TaskCompletionSource <bool>();
            var cancellationTaskCompletionSource = new TaskCompletionSource <bool>();

            try
            {
                ChangeState(ConnectionState.Connecting, $"Connecting to {IPAddress}:{Port}");

                // create a new CTS with our desired timeout. when the timeout expires, the cancellation will fire
                using (var timeoutCancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(Options.ConnectTimeout)))
                {
                    var connectTask = TcpClient.ConnectAsync(IPAddress, Port);

                    // register the TCS with the CTS. when the cancellation fires (due to timeout), it will set the value of the
                    // TCS via the registered delegate, ending the 'fake' task, then bind the externally supplied CT with the same
                    // TCS. either the timeout or the external token can now cancel the operation.
                    using (timeoutCancellationTokenSource.Token.Register(() => timeoutTaskCompletionSource.TrySetResult(true)))
                        using (((CancellationToken)cancellationToken).Register(() => cancellationTaskCompletionSource.TrySetResult(true)))
                        {
                            var completedTask = await Task.WhenAny(connectTask, timeoutTaskCompletionSource.Task, cancellationTaskCompletionSource.Task).ConfigureAwait(false);

                            if (completedTask == timeoutTaskCompletionSource.Task)
                            {
                                throw new TimeoutException($"Operation timed out after {Options.ConnectTimeout} seconds");
                            }
                            else if (completedTask == cancellationTaskCompletionSource.Task)
                            {
                                throw new OperationCanceledException("Operation cancelled.");
                            }

                            if (connectTask.Exception?.InnerException != null)
                            {
                                throw connectTask.Exception.InnerException;
                            }
                        }
                }

                InactivityTimer?.Start();
                WatchdogTimer.Start();
                Stream = TcpClient.GetStream();

                ChangeState(ConnectionState.Connected, $"Connected to {IPAddress}:{Port}");
            }
            catch (Exception ex) when(!(ex is TimeoutException) && !(ex is OperationCanceledException))
            {
                ChangeState(ConnectionState.Disconnected, $"Connection Error: {ex.Message}");

                throw new ConnectionException($"Failed to connect to {IPAddress}:{Port}: {ex.Message}", ex);
            }
        }
Esempio n. 4
0
        public void InactivityTimerShouldResetAndCallbackWhenResetIsCalled()
        {
            var timer = new InactivityTimer(this.TimerCallback);

            timer.ResetTimer(TimeSpan.FromMilliseconds(1));
            this.timerEvent.Wait(1000);
            Assert.AreEqual(1, this.callBackCount, "Should have fired once.");
        }
Esempio n. 5
0
        /// <summary>
        ///     Initializes a new instance of the <see cref="Connection"/> class.
        /// </summary>
        /// <param name="ipEndPoint">The remote IP endpoint of the connection.</param>
        /// <param name="options">The optional options for the connection.</param>
        /// <param name="tcpClient">The optional TcpClient instance to use.</param>
        public Connection(IPEndPoint ipEndPoint, ConnectionOptions options = null, ITcpClient tcpClient = null)
        {
            Id = Guid.NewGuid();

            IPEndPoint = ipEndPoint;
            Options    = options ?? new ConnectionOptions();

            TcpClient = tcpClient ?? new TcpClientAdapter(new TcpClient());

            // invoke the configuration delegate to allow implementing code to configure
            // the socket.  .NET standard has a limited feature set with respect to SetSocketOptions()
            // and there's a vast number of possible tweaks here, so delegating to implementing code
            // is pretty much the only option.
            Options.ConfigureSocket(TcpClient.Client);

            WriteQueueSemaphore = new SemaphoreSlim(Options.WriteQueueSize);

            if (Options.InactivityTimeout > 0)
            {
                InactivityTimer = new SystemTimer()
                {
                    Enabled   = false,
                    AutoReset = false,
                    Interval  = Options.InactivityTimeout,
                };

                InactivityTimer.Elapsed += (sender, e) =>
                {
                    var ex = new TimeoutException($"Inactivity timeout of {Options.InactivityTimeout} milliseconds was reached");
                    Disconnect(ex.Message, ex);
                };
            }

            WatchdogTimer = new SystemTimer()
            {
                Enabled   = false,
                AutoReset = true,
                Interval  = 250,
            };

            WatchdogTimer.Elapsed += (sender, e) =>
            {
                if (TcpClient == null || !TcpClient.Connected)
                {
                    Disconnect("The server connection was closed unexpectedly");
                }
            };

            if (TcpClient.Connected)
            {
                State = ConnectionState.Connected;
                InactivityTimer?.Start();
                WatchdogTimer.Start();
                Stream = TcpClient.GetStream();
            }
        }
 private void StopInactivityTimer()
 {
     if (InactivityTimer == null)
     {
         return;
     }
     InactivityTimer.Stop();
     InactivityTimer.Enabled = false;
     InactivityTimer.Dispose();
 }
Esempio n. 7
0
        /// <summary>
        ///     Initializes a new instance of the <see cref="Connection"/> class.
        /// </summary>
        /// <param name="ipEndPoint">The remote IP endpoint of the connection.</param>
        /// <param name="options">The optional options for the connection.</param>
        /// <param name="tcpClient">The optional TcpClient instance to use.</param>
        public Connection(IPEndPoint ipEndPoint, ConnectionOptions options = null, ITcpClient tcpClient = null)
        {
            Id = Guid.NewGuid();

            IPEndPoint = ipEndPoint;
            Options    = options ?? new ConnectionOptions();

            TcpClient = tcpClient ?? new TcpClientAdapter(new TcpClient());
            TcpClient.Client.ReceiveBufferSize = Options.ReadBufferSize;
            TcpClient.Client.SendBufferSize    = Options.WriteBufferSize;

            if (Options.InactivityTimeout > 0)
            {
                InactivityTimer = new SystemTimer()
                {
                    Enabled   = false,
                    AutoReset = false,
                    Interval  = Options.InactivityTimeout,
                };

                InactivityTimer.Elapsed += (sender, e) =>
                {
                    var ex = new TimeoutException($"Inactivity timeout of {Options.InactivityTimeout} milliseconds was reached");
                    Disconnect(ex.Message, ex);
                };
            }

            WatchdogTimer = new SystemTimer()
            {
                Enabled   = false,
                AutoReset = true,
                Interval  = 250,
            };

            WatchdogTimer.Elapsed += (sender, e) =>
            {
                if (TcpClient == null || !TcpClient.Connected)
                {
                    Disconnect($"The server connection was closed unexpectedly");
                }
            };

            if (TcpClient.Connected)
            {
                State = ConnectionState.Connected;
                InactivityTimer?.Start();
                WatchdogTimer.Start();
                Stream = TcpClient.GetStream();
            }
        }
Esempio n. 8
0
        private async Task WriteInternalAsync(byte[] bytes, CancellationToken cancellationToken)
        {
            try
            {
                await Stream.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false);

                InactivityTimer?.Reset();
            }
            catch (Exception ex)
            {
                Disconnect($"Write error: {ex.Message}");
                throw new ConnectionWriteException($"Failed to write {bytes.Length} bytes to {IPAddress}:{Port}: {ex.Message}", ex);
            }
        }
Esempio n. 9
0
        /// <summary>
        ///     Disconnects the client.
        /// </summary>
        /// <param name="message">The optional message or reason for the disconnect.</param>
        public void Disconnect(string message = null)
        {
            if (State != ConnectionState.Disconnected && State != ConnectionState.Disconnecting)
            {
                ChangeState(ConnectionState.Disconnecting, message);

                InactivityTimer?.Stop();
                WatchdogTimer?.Stop();
                Stream?.Close();
                TcpClient?.Close();

                ChangeState(ConnectionState.Disconnected, message);
            }
        }
Esempio n. 10
0
        /// <summary>
        ///     Releases the managed and unmanaged resources used by the <see cref="Connection"/>.
        /// </summary>
        /// <param name="disposing">A value indicating whether the object is in the process of disposing.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (!Disposed)
            {
                if (disposing)
                {
                    Disconnect("Connection is being disposed", new ObjectDisposedException(GetType().Name));
                    InactivityTimer?.Dispose();
                    WatchdogTimer?.Dispose();
                    Stream?.Dispose();
                    TcpClient?.Dispose();
                }

                Disposed = true;
            }
        }
Esempio n. 11
0
        /// <summary>
        ///     Releases the managed and unmanaged resources used by the <see cref="Connection"/>.
        /// </summary>
        /// <param name="disposing">A value indicating whether the object is in the process of disposing.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (!Disposed)
            {
                if (disposing)
                {
                    Disconnect();
                    InactivityTimer?.Dispose();
                    WatchdogTimer?.Dispose();
                    Stream?.Dispose();
                    TcpClient?.Dispose();
                }

                Disposed = true;
            }
        }
Esempio n. 12
0
        private async Task ReadInternalAsync(long length, Stream outputStream, Func <CancellationToken, Task> governor, CancellationToken cancellationToken)
        {
            InactivityTimer?.Reset();

            var buffer         = new byte[TcpClient.Client.ReceiveBufferSize];
            var totalBytesRead = 0;

            try
            {
                while (totalBytesRead < length)
                {
                    await governor(cancellationToken).ConfigureAwait(false);

                    var bytesRemaining = length - totalBytesRead;
                    var bytesToRead    = bytesRemaining > buffer.Length ? buffer.Length : (int)bytesRemaining; // cast to int is safe because of the check against buffer length.

                    var bytesRead = await Stream.ReadAsync(buffer, 0, bytesToRead, cancellationToken).ConfigureAwait(false);

                    if (bytesRead == 0)
                    {
                        throw new ConnectionException($"Remote connection closed");
                    }

                    totalBytesRead += bytesRead;

                    await outputStream.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);

                    Interlocked.CompareExchange(ref DataRead, null, null)?
                    .Invoke(this, new ConnectionDataEventArgs(totalBytesRead, length));

                    InactivityTimer?.Reset();
                }

                await outputStream.FlushAsync(cancellationToken).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                Disconnect($"Read error: {ex.Message}", ex);

                if (ex is TimeoutException || ex is OperationCanceledException)
                {
                    throw;
                }

                throw new ConnectionReadException($"Failed to read {length} bytes from {IPEndPoint}: {ex.Message}", ex);
            }
        }
Esempio n. 13
0
        /// <summary>
        ///     Initializes a new instance of the <see cref="Connection"/> class.
        /// </summary>
        /// <param name="ipAddress">The remote IP address of the connection.</param>
        /// <param name="port">The remote port of the connection.</param>
        /// <param name="options">The optional options for the connection.</param>
        /// <param name="tcpClient">The optional TcpClient instance to use.</param>
        public Connection(IPAddress ipAddress, int port, ConnectionOptions options = null, ITcpClient tcpClient = null)
        {
            IPAddress = ipAddress;
            Port      = port;
            Options   = options ?? new ConnectionOptions();

            TcpClient = tcpClient ?? new TcpClientAdapter(new TcpClient());
            TcpClient.Client.ReceiveBufferSize = Options.ReadBufferSize;
            TcpClient.Client.SendBufferSize    = Options.WriteBufferSize;

            if (Options.InactivityTimeout > 0)
            {
                InactivityTimer = new SystemTimer()
                {
                    Enabled   = false,
                    AutoReset = false,
                    Interval  = Options.InactivityTimeout * 1000,
                };

                InactivityTimer.Elapsed += (sender, e) => Disconnect($"Inactivity timeout of {Options.InactivityTimeout} seconds was reached.");
            }

            WatchdogTimer = new SystemTimer()
            {
                Enabled   = false,
                AutoReset = true,
                Interval  = 250,
            };

            WatchdogTimer.Elapsed += (sender, e) =>
            {
                if (!TcpClient.Connected)
                {
                    Disconnect($"The server connection was closed unexpectedly.");
                }
            };

            if (TcpClient.Connected)
            {
                State = ConnectionState.Connected;
                InactivityTimer?.Start();
                WatchdogTimer.Start();
                Stream = TcpClient.GetStream();
            }
        }
Esempio n. 14
0
        private async Task WriteInternalAsync(long length, Stream inputStream, Func <CancellationToken, Task> governor, CancellationToken cancellationToken)
        {
            InactivityTimer?.Reset();

            var sendBufferSize    = TcpClient.Client.SendBufferSize;
            var inputBuffer       = new byte[TcpClient.Client.SendBufferSize];
            var totalBytesWritten = 0;

            try
            {
                while (totalBytesWritten < length)
                {
                    await governor(cancellationToken).ConfigureAwait(false);

                    var bytesRemaining = length - totalBytesWritten;

                    var bytesToRead = bytesRemaining > sendBufferSize ? sendBufferSize : (int)bytesRemaining;
                    var bytesRead   = await inputStream.ReadAsync(inputBuffer, 0, bytesToRead, cancellationToken).ConfigureAwait(false);

                    await Stream.WriteAsync(inputBuffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);

                    totalBytesWritten += bytesRead;

                    Interlocked.CompareExchange(ref DataWritten, null, null)?
                    .Invoke(this, new ConnectionDataEventArgs(totalBytesWritten, length));

                    InactivityTimer?.Reset();
                }
            }
            catch (Exception ex)
            {
                Disconnect($"Write error: {ex.Message}", ex);

                if (ex is TimeoutException || ex is OperationCanceledException)
                {
                    throw;
                }

                throw new ConnectionWriteException($"Failed to write {length} bytes to {IPEndPoint}: {ex.Message}", ex);
            }
        }
Esempio n. 15
0
        private async Task <byte[]> ReadInternalAsync(int length, CancellationToken cancellationToken)
        {
            InactivityTimer?.Reset();

            var result = new List <byte>();

            var buffer         = new byte[Options.BufferSize];
            var totalBytesRead = 0;

            try
            {
                while (totalBytesRead < length)
                {
                    var bytesRemaining = length - totalBytesRead;
                    var bytesToRead    = bytesRemaining > buffer.Length ? buffer.Length : bytesRemaining;

                    var bytesRead = await Stream.ReadAsync(buffer, 0, bytesToRead, cancellationToken).ConfigureAwait(false);

                    if (bytesRead == 0)
                    {
                        Disconnect($"Remote connection closed.");
                        break;
                    }

                    totalBytesRead += bytesRead;
                    var data = buffer.Take(bytesRead);
                    result.AddRange(data);

                    DataRead?.Invoke(this, new ConnectionDataEventArgs(data.ToArray(), totalBytesRead, length));
                    InactivityTimer?.Reset();
                }

                return(result.ToArray());
            }
            catch (Exception ex)
            {
                Disconnect($"Read error: {ex.Message}");
                throw new ConnectionReadException($"Failed to read {length} bytes from {IPAddress}:{Port}: {ex.Message}", ex);
            }
        }
Esempio n. 16
0
        private async Task <byte[]> ReadInternalAsync(long length, CancellationToken cancellationToken)
        {
            InactivityTimer?.Reset();

            var result = new List <byte>();

            var buffer         = new byte[TcpClient.Client.ReceiveBufferSize];
            var totalBytesRead = 0;

            try
            {
                while (totalBytesRead < length)
                {
                    var bytesRemaining = length - totalBytesRead;
                    var bytesToRead    = bytesRemaining > buffer.Length ? buffer.Length : (int)bytesRemaining; // cast to int is safe because of the check against buffer length.

                    var bytesRead = await Stream.ReadAsync(buffer, 0, bytesToRead, cancellationToken).ConfigureAwait(false);

                    if (bytesRead == 0)
                    {
                        throw new ConnectionException($"Remote connection closed");
                    }

                    totalBytesRead += bytesRead;
                    var data = buffer.Take(bytesRead);
                    result.AddRange(data);

                    DataRead?.Invoke(this, new ConnectionDataEventArgs(totalBytesRead, length));
                    InactivityTimer?.Reset();
                }

                return(result.ToArray());
            }
            catch (Exception ex) when(!(ex is TimeoutException) && !(ex is OperationCanceledException))
            {
                Disconnect($"Read error: {ex.Message}");
                throw new ConnectionReadException($"Failed to read {length} bytes from {IPAddress}:{Port}: {ex.Message}", ex);
            }
        }
Esempio n. 17
0
        public static void StopMiner(bool manually = false)
        {
            if (!process?.HasExited == true)
            {
                ManuallyStoped = true;
            }

            if (manually)
            {
                inactivity = false;
                InactivityTimer?.Invoke(-1);
            }
            else
            {
                WachdogInactivity();
            }

            Waching = false;
            WachdogDelayTimer?.Invoke(-1);
            LowHashrate = false;
            LowHashrateTimer?.Invoke(-1);

            //Kill miner
            var processes = Process.GetProcesses().
                            Where(p => ProcessNames.Contains(p.ProcessName));
            var res = Parallel.ForEach(processes, p =>
            {
                while (!p.HasExited)
                {
                    try { p.Kill(); } catch { }
                }
            });

            while (!res.IsCompleted)
            {
                Thread.Sleep(50);
            }
        }
Esempio n. 18
0
 private void ResetInactivityTime()
 {
     InactivityTimer?.Reset();
     LastActivityTime = DateTime.UtcNow;
 }
Esempio n. 19
0
        private static void StartWaching(double minhash)
        {
            WachdogThread?.Abort();
            WachdogThread = new Thread(() =>
            {
                WachdogInactivity();
                Waching = true;
                double?[] hashes;
                IEnumerable <double?> activehashes;
                for (int i = Settings.Profile.TimeoutWachdog; i > -1; i--)
                {
                    if (!Waching)
                    {
                        return;
                    }
                    Task.Run(() =>
                    {
                        try
                        {
                            activehashes = GetMinerInfo().Hashrates.Where(h => h != null);
                            if (activehashes.Sum() > minhash)
                            {
                                inactivity = false;
                                InactivityTimer?.Invoke(-1);
                            }
                        }
                        catch { }
                    });
                    Task.Run(() => { WachdogDelayTimer?.Invoke(i); });
                    Thread.Sleep(1000);
                }
                Action WachdogLoop = (() =>
                {
                    hashes = GetMinerInfo().Hashrates;
                    if (hashes != null)
                    {
                        activehashes = hashes.Where(h => h != null);
                        //Zero
                        if (activehashes.Sum() == 0)
                        {
                            ErrorsCounter++;
                            if (ErrorsCounter > 4)
                            {
                                Task.Run(() => ZeroHash?.Invoke());
                                WachdogInactivity();
                                Waching = false;
                                return;
                            }
                        }
                        else
                        {
                            // низкий хешрейт
                            if (activehashes.Sum() < minhash)
                            {
                                WachdogLowHashrate();
                            }
                            else
                            {
                                // блок хорошего поведения
                                LowHashrate = false;
                                Inactivity = false;
                                ErrorsCounter = 0;
                            }

                            //отвал карт
                            if (hashes.Contains(0))
                            {
                                ErrorsCounter++;
                                if (ErrorsCounter > 4)
                                {
                                    List <int> gpus = new List <int>();
                                    for (int i = 0; i < hashes.Length; i++)
                                    {
                                        if (hashes[i] == 0)
                                        {
                                            gpus.Add(i);
                                        }
                                    }
                                    Task.Run(() => GPUsfalled?.Invoke(gpus.ToArray()));
                                    Waching = false;
                                    return;
                                }
                            }
                        }
                        return;
                    }
                    else
                    {
                        //бездаействие
                        ErrorsCounter++;
                        if (ErrorsCounter > 4)
                        {
                            Task.Run(() => ZeroHash?.Invoke());
                            WachdogInactivity();
                            Waching = false;
                            return;
                        }
                    }
                    WachdogInactivity();
                });
                while (Waching)
                {
                    var WachResult = WachdogLoop.BeginInvoke(null, null);
                    Thread.Sleep(1000);
                    WachdogLoop.EndInvoke(WachResult);
                }
            });
            WachdogThread.Start();
        }
Esempio n. 20
0
        /// <summary>
        ///     Asynchronously connects the client to the configured <see cref="IPEndPoint"/>.
        /// </summary>
        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
        /// <returns>A Task representing the asynchronous operation.</returns>
        /// <exception cref="InvalidOperationException">
        ///     Thrown when the connection is already connected, or is transitioning between states.
        /// </exception>
        /// <exception cref="TimeoutException">
        ///     Thrown when the time attempting to connect exceeds the configured <see cref="ConnectionOptions.ConnectTimeout"/> value.
        /// </exception>
        /// <exception cref="OperationCanceledException">
        ///     Thrown when <paramref name="cancellationToken"/> cancellation is requested.
        /// </exception>
        /// <exception cref="ConnectionException">Thrown when an unexpected error occurs.</exception>
        public async Task ConnectAsync(CancellationToken?cancellationToken = null)
        {
            if (State != ConnectionState.Pending && State != ConnectionState.Disconnected)
            {
                throw new InvalidOperationException($"Invalid attempt to connect a connected or transitioning connection (current state: {State})");
            }

            cancellationToken ??= CancellationToken.None;

            // create a new TCS to serve as the trigger which will throw when the CTS times out a TCS is basically a 'fake' task
            // that ends when the result is set programmatically. create another for cancellation via the externally provided token.
            var timeoutTaskCompletionSource      = new TaskCompletionSource <bool>(TaskCreationOptions.RunContinuationsAsynchronously);
            var cancellationTaskCompletionSource = new TaskCompletionSource <bool>(TaskCreationOptions.RunContinuationsAsynchronously);

            try
            {
                ChangeState(ConnectionState.Connecting, $"Connecting to {IPEndPoint}");

                // create a new CTS with our desired timeout. when the timeout expires, the cancellation will fire
                using (var timeoutCancellationTokenSource = new CancellationTokenSource(TimeSpan.FromMilliseconds(Options.ConnectTimeout)))
                {
                    Task connectTask;

                    if (Options.ProxyOptions != default)
                    {
                        var proxy = Options.ProxyOptions;

                        connectTask = TcpClient.ConnectThroughProxyAsync(
                            proxy.IPEndPoint.Address,
                            proxy.IPEndPoint.Port,
                            IPEndPoint.Address,
                            IPEndPoint.Port,
                            proxy.Username,
                            proxy.Password,
                            cancellationToken);
                    }
                    else
                    {
                        connectTask = TcpClient.ConnectAsync(IPEndPoint.Address, IPEndPoint.Port);
                    }

                    // register the TCS with the CTS. when the cancellation fires (due to timeout), it will set the value of the
                    // TCS via the registered delegate, ending the 'fake' task, then bind the externally supplied CT with the same
                    // TCS. either the timeout or the external token can now cancel the operation.
#if NETSTANDARD2_0
                    using (timeoutCancellationTokenSource.Token.Register(() => timeoutTaskCompletionSource.TrySetResult(true)))
                        using (((CancellationToken)cancellationToken).Register(() => cancellationTaskCompletionSource.TrySetResult(true)))
#else
                    await using (timeoutCancellationTokenSource.Token.Register(() => timeoutTaskCompletionSource.TrySetResult(true)))
                        await using (((CancellationToken)cancellationToken).Register(() => cancellationTaskCompletionSource.TrySetResult(true)))
#endif
                        {
                            var completedTask = await Task.WhenAny(connectTask, timeoutTaskCompletionSource.Task, cancellationTaskCompletionSource.Task).ConfigureAwait(false);

                            if (completedTask == timeoutTaskCompletionSource.Task)
                            {
                                throw new TimeoutException($"Operation timed out after {Options.ConnectTimeout} milliseconds");
                            }
                            else if (completedTask == cancellationTaskCompletionSource.Task)
                            {
                                throw new OperationCanceledException("Operation cancelled", cancellationToken.Value);
                            }

                            if (connectTask.Exception?.InnerException != null)
                            {
                                throw connectTask.Exception.InnerException;
                            }
                        }
                }

                InactivityTimer?.Start();
                WatchdogTimer.Start();
                Stream = TcpClient.GetStream();

                ChangeState(ConnectionState.Connected, $"Connected to {IPEndPoint}");
            }
            catch (Exception ex)
            {
                Disconnect($"Connection Error: {ex.Message}", ex);

                if (ex is TimeoutException || ex is OperationCanceledException)
                {
                    throw;
                }

                throw new ConnectionException($"Failed to connect to {IPEndPoint}: {ex.Message}", ex);
            }
        }