Exemple #1
0
        /// <summary>
        /// Creates a connection (aka transport), if there isn't an active one.
        /// </summary>
        public void RequestConnection()
        {
            lock (Lock)
            {
                switch (_state)
                {
                case ConnectivityState.Idle:
                    SubchannelLog.ConnectionRequested(_logger, Id);

                    // Only start connecting underlying transport if in an idle state.
                    UpdateConnectivityState(ConnectivityState.Connecting, "Connection requested.");
                    break;

                case ConnectivityState.Connecting:
                case ConnectivityState.Ready:
                case ConnectivityState.TransientFailure:
                    SubchannelLog.ConnectionRequestedInNonIdleState(_logger, Id, _state);

                    // We're already attempting to connect to the transport.
                    // If the connection is waiting in a delayed backoff then interrupt
                    // the delay and immediately retry connection.
                    _delayInterruptTcs?.TrySetResult(null);
                    return;

                case ConnectivityState.Shutdown:
                    throw new InvalidOperationException($"Subchannel id '{Id}' has been shutdown.");

                default:
                    throw new ArgumentOutOfRangeException("state", _state, "Unexpected state.");
                }
            }

            _ = ConnectTransportAsync();
        }
Exemple #2
0
        internal Subchannel(ConnectionManager manager, IReadOnlyList <BalancerAddress> addresses)
        {
            Lock    = new object();
            _logger = manager.LoggerFactory.CreateLogger(GetType());

            Id         = manager.GetNextId();
            _addresses = addresses.ToList();
            _manager   = manager;
            Attributes = new BalancerAttributes();

            SubchannelLog.SubchannelCreated(_logger, Id, addresses);
        }
Exemple #3
0
        internal void RaiseStateChanged(ConnectivityState state, Status status)
        {
            SubchannelLog.SubchannelStateChanged(_logger, Id, state, status);

            if (_stateChangedRegistrations.Count > 0)
            {
                var subchannelState = new SubchannelState(state, status);
                foreach (var registration in _stateChangedRegistrations)
                {
                    registration.Invoke(subchannelState);
                }
            }
        }
Exemple #4
0
        /// <summary>
        /// Replaces the existing addresses used with this <see cref="Subchannel"/>.
        /// <para>
        /// If the subchannel has an active connection and the new addresses contain the connected address
        /// then the connection is reused. Otherwise the subchannel will reconnect.
        /// </para>
        /// </summary>
        /// <param name="addresses"></param>
        public void UpdateAddresses(IReadOnlyList <BalancerAddress> addresses)
        {
            var requireReconnect = false;

            lock (Lock)
            {
                if (_addresses.SequenceEqual(addresses, BalancerAddressEqualityComparer.Instance))
                {
                    // Don't do anything if new addresses match existing addresses.
                    return;
                }

                _addresses.Clear();
                _addresses.AddRange(addresses);

                switch (_state)
                {
                case ConnectivityState.Idle:
                    break;

                case ConnectivityState.Connecting:
                case ConnectivityState.TransientFailure:
                    SubchannelLog.AddressesUpdatedWhileConnecting(_logger, Id);
                    requireReconnect = true;
                    break;

                case ConnectivityState.Ready:
                    // Transport uses the subchannel lock but take copy in an abundance of caution.
                    var currentAddress = CurrentAddress;
                    if (currentAddress != null && !_addresses.Contains(currentAddress))
                    {
                        requireReconnect = true;
                        SubchannelLog.ConnectedAddressNotInUpdatedAddresses(_logger, Id, currentAddress);
                    }
                    break;

                case ConnectivityState.Shutdown:
                    throw new InvalidOperationException($"Subchannel id '{Id}' has been shutdown.");

                default:
                    throw new ArgumentOutOfRangeException("state", _state, "Unexpected state.");
                }
            }

            if (requireReconnect)
            {
                _connectCts?.Cancel();
                Transport.Disconnect();
                RequestConnection();
            }
        }
Exemple #5
0
        private async Task ConnectTransportAsync()
        {
            // There shouldn't be a previous connect in progress, but cancel the CTS to ensure they're no longer running.
            _connectCts?.Cancel();

            _connectCts = new CancellationTokenSource();

            var backoffPolicy = _manager.BackoffPolicyFactory.Create();

            try
            {
                SubchannelLog.ConnectingTransport(_logger, Id);

                for (var attempt = 0; ; attempt++)
                {
                    lock (Lock)
                    {
                        if (_state == ConnectivityState.Shutdown)
                        {
                            return;
                        }
                    }

                    if (await Transport.TryConnectAsync(_connectCts.Token).ConfigureAwait(false))
                    {
                        return;
                    }

                    _connectCts.Token.ThrowIfCancellationRequested();

                    _delayInterruptTcs = new TaskCompletionSource <object?>(TaskCreationOptions.RunContinuationsAsynchronously);
                    var delayCts = new CancellationTokenSource();

                    var backoffTicks = backoffPolicy.NextBackoff().Ticks;
                    // Task.Delay supports up to Int32.MaxValue milliseconds.
                    // Note that even if the maximum backoff is configured to this maximum, the jitter could push it over the limit.
                    // Force an upper bound here to ensure an unsupported backoff is never used.
                    backoffTicks = Math.Min(backoffTicks, TimeSpan.TicksPerMillisecond * int.MaxValue);

                    var backkoff = TimeSpan.FromTicks(backoffTicks);
                    SubchannelLog.StartingConnectBackoff(_logger, Id, backkoff);
                    var completedTask = await Task.WhenAny(Task.Delay(backkoff, delayCts.Token), _delayInterruptTcs.Task).ConfigureAwait(false);

                    if (completedTask != _delayInterruptTcs.Task)
                    {
                        // Task.Delay won. Check CTS to see if it won because of cancellation.
                        delayCts.Token.ThrowIfCancellationRequested();
                    }
                    else
                    {
                        SubchannelLog.ConnectBackoffInterrupted(_logger, Id);

                        // Delay interrupt was triggered. Reset back-off.
                        backoffPolicy = _manager.BackoffPolicyFactory.Create();

                        // Cancel the Task.Delay that's no longer needed.
                        // https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/519ef7d231c01116f02bc04354816a735f2a36b6/AsyncGuidance.md#using-a-timeout
                        delayCts.Cancel();
                    }
                }
            }
            catch (OperationCanceledException)
            {
                SubchannelLog.ConnectCanceled(_logger, Id);
            }
            catch (Exception ex)
            {
                SubchannelLog.ConnectError(_logger, Id, ex);

                UpdateConnectivityState(ConnectivityState.TransientFailure, "Error connecting to subchannel.");
            }
        }
Exemple #6
0
        private async Task ConnectTransportAsync()
        {
            // There shouldn't be a previous connect in progress, but cancel the CTS to ensure they're no longer running.
            _connectCts?.Cancel();

            _connectCts = new CancellationTokenSource();

            const int InitialBackOffMs = 1000;

            try
            {
                SubchannelLog.ConnectingTransport(_logger, Id);

                var backoffMs = InitialBackOffMs;
                for (var attempt = 0; ; attempt++)
                {
                    lock (Lock)
                    {
                        if (_state == ConnectivityState.Shutdown)
                        {
                            return;
                        }
                    }

                    if (await Transport.TryConnectAsync(_connectCts.Token).ConfigureAwait(false))
                    {
                        return;
                    }

                    _connectCts.Token.ThrowIfCancellationRequested();

                    _delayInterruptTcs = new TaskCompletionSource <object?>(TaskCreationOptions.RunContinuationsAsynchronously);
                    var delayCts = new CancellationTokenSource();

                    var delay = TimeSpan.FromMilliseconds(backoffMs);
                    SubchannelLog.StartingConnectBackoff(_logger, Id, delay);
                    var completedTask = await Task.WhenAny(Task.Delay(delay, delayCts.Token), _delayInterruptTcs.Task).ConfigureAwait(false);

                    if (completedTask != _delayInterruptTcs.Task)
                    {
                        // Task.Delay won. Check CTS to see if it won because of cancellation.
                        delayCts.Token.ThrowIfCancellationRequested();
                    }
                    else
                    {
                        SubchannelLog.ConnectBackoffInterrupted(_logger, Id);

                        // Delay interrupt was triggered. Reset back-off.
                        backoffMs = InitialBackOffMs;

                        // Cancel the Task.Delay that's no longer needed.
                        // https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/519ef7d231c01116f02bc04354816a735f2a36b6/AsyncGuidance.md#using-a-timeout
                        delayCts.Cancel();
                    }

                    // Exponential backoff with max.
                    backoffMs = (int)Math.Min(backoffMs * 1.6, 1000 * 120);
                }
            }
            catch (OperationCanceledException)
            {
                SubchannelLog.ConnectCanceled(_logger, Id);
            }
            catch (Exception ex)
            {
                SubchannelLog.ConnectError(_logger, Id, ex);

                UpdateConnectivityState(ConnectivityState.TransientFailure, "Error connecting to subchannel.");
            }
        }