private async Task AttemptDeviceConnection()

in DeviceBridge/Services/SubscriptionScheduler.cs [304:350]


        private async Task AttemptDeviceConnection(string deviceId)
        {
            _logger.Info("Starting scheduled connection attempt for device {deviceId}.", deviceId);

            // Synchronization is needed to avoid the race condition of a device connection being closed between the time we check
            // if a connection should be open (the device has subscriptions) and the connection open call being actually issued.
            var mutex = _dbAndConnectionStateSyncSemaphores.GetOrAdd(deviceId, new SemaphoreSlim(1, 1));
            await mutex.WaitAsync();

            try
            {
                if (!_hasDataSubscriptions.TryGetValue(deviceId, out _))
                {
                    _logger.Info("Skipping scheduled connection attempt for device {deviceId} as it no longer has data subscriptions.", deviceId);
                    return;
                }

                await _connectionManager.AssertDeviceConnectionOpenAsync(deviceId, false /* permanent */);
                _consecutiveConnectionFailures.TryRemove(deviceId, out int _);
                _logger.Info("Successfully opened scheduled connection for device {deviceId}.", deviceId);
            }
            catch (Exception e)
            {
                int failedAttempts;
                if (_consecutiveConnectionFailures.TryGetValue(deviceId, out failedAttempts))
                {
                    failedAttempts++;
                }
                else
                {
                    failedAttempts = 1;
                }

                _consecutiveConnectionFailures.AddOrUpdate(deviceId, failedAttempts, (key, oldValue) => failedAttempts);

                // A failed connection attempt already includes the regular device client retries and a DPS registration attempt,
                // so we back off after each failed attempt, up to 30 minutes.
                var backoff = new Random().Next(0, Math.Min(ConnectionBackoffPerFailedAttemptSeconds * failedAttempts, MaxConnectionBackoffSeconds));
                _logger.Error(e, "Failed to open scheduled connection for device {deviceId}. {failedAttempts} consecutive failed attempts so far. Connection scheduled for retry after {backoff} seconds.", deviceId, failedAttempts, backoff);
                var notBefore = DateTimeOffset.UtcNow.ToUnixTimeSeconds() + backoff;
                _scheduledConnectionsNotBefore.AddOrUpdate(deviceId, notBefore, (key, oldValue) => notBefore);
            }
            finally
            {
                mutex.Release();
            }
        }