< Summary

Information
Class: OzonRepositories.Services.BitrixConnectionService
Assembly: OzonRepositories
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonRepositories/Services/BitrixConnectionService.cs
Line coverage
70%
Covered lines: 34
Uncovered lines: 14
Coverable lines: 48
Total lines: 124
Line coverage: 70.8%
Branch coverage
43%
Covered branches: 7
Total branches: 16
Branch coverage: 43.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%210%
.ctor(...)50%22100%
.ctor(...)0%620%
IsConnectionAvailableAsync()50%9880%
RefreshConnectionStatusAsync()100%210%
CheckConnectionInternalAsync()100%22100%
OpenMySqlConnectionAsync()0%620%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonRepositories/Services/BitrixConnectionService.cs

#LineLine coverage
 1using Microsoft.Extensions.Configuration;
 2using Microsoft.Extensions.Logging;
 3using MySqlConnector;
 4
 5namespace OzonRepositories.Services;
 6
 7/// <summary>
 8/// Сервис для проверки доступности соединения с Bitrix базой данных.
 9/// Кэширует результат проверки для оптимизации производительности.
 10/// </summary>
 11public interface IBitrixConnectionService
 12{
 13    /// <summary>
 14    /// Проверяет доступность соединения с Bitrix (с кэшированием результата)
 15    /// </summary>
 16    Task<bool> IsConnectionAvailableAsync();
 17
 18    /// <summary>
 19    /// Принудительно обновляет кэш состояния соединения
 20    /// </summary>
 21    Task RefreshConnectionStatusAsync();
 22}
 23
 24public class BitrixConnectionService : IBitrixConnectionService
 25{
 26    // Регрессия #2174, #2290: хостинг Bitrix изредка отвечает на новый вход «Access denied … to database»
 27    // (1044) при верных учётных данных — со второй/третьей попытки подключение проходит, рабочие запросы
 28    // BitrixContext при этом не падают. Повторные попытки сглаживают такие микросбои хостинга.
 029    internal static readonly TimeSpan RetryDelay = TimeSpan.FromSeconds(1);
 30    private const int MaxAttempts = 3;
 31
 32    private readonly string _connectionString;
 33    private readonly Func<CancellationToken, Task> _openConnection;
 34    private readonly TimeSpan _retryDelay;
 35    private readonly ILogger<BitrixConnectionService> _logger;
 36    private bool? _cachedStatus;
 437    private DateTime _lastCheckTime = DateTime.MinValue;
 438    private readonly TimeSpan _cacheTimeout = TimeSpan.FromMinutes(1);
 439    private readonly SemaphoreSlim _checkSemaphore = new(1, 1);
 40
 41    public BitrixConnectionService(IConfiguration configuration, ILogger<BitrixConnectionService> logger)
 042        : this(configuration.GetConnectionString("BitrixConnectionProd") ?? string.Empty, null, RetryDelay, logger)
 43    {
 044    }
 45
 446    internal BitrixConnectionService(
 447        string connectionString,
 448        Func<CancellationToken, Task>? openConnection,
 449        TimeSpan retryDelay,
 450        ILogger<BitrixConnectionService> logger)
 51    {
 452        _connectionString = connectionString;
 453        _openConnection = openConnection ?? OpenMySqlConnectionAsync;
 454        _retryDelay = retryDelay;
 455        _logger = logger;
 456    }
 57
 58    public async Task<bool> IsConnectionAvailableAsync()
 59    {
 460        if (_cachedStatus.HasValue && DateTime.UtcNow - _lastCheckTime < _cacheTimeout)
 061            return _cachedStatus.Value;
 62
 463        await _checkSemaphore.WaitAsync();
 64        try
 65        {
 466            if (_cachedStatus.HasValue && DateTime.UtcNow - _lastCheckTime < _cacheTimeout)
 067                return _cachedStatus.Value;
 68
 469            _cachedStatus = await CheckConnectionInternalAsync();
 470            _lastCheckTime = DateTime.UtcNow;
 471            return _cachedStatus.Value;
 72        }
 73        finally
 74        {
 475            _checkSemaphore.Release();
 76        }
 477    }
 78
 79    public async Task RefreshConnectionStatusAsync()
 80    {
 081        await _checkSemaphore.WaitAsync();
 82        try
 83        {
 084            _cachedStatus = await CheckConnectionInternalAsync();
 085            _lastCheckTime = DateTime.UtcNow;
 086        }
 87        finally
 88        {
 089            _checkSemaphore.Release();
 90        }
 091    }
 92
 93    private async Task<bool> CheckConnectionInternalAsync()
 94    {
 495        if (string.IsNullOrWhiteSpace(_connectionString))
 196            return false;
 97
 898        for (var attempt = 1; ; attempt++)
 99        {
 100            try
 101            {
 8102                using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3));
 8103                await _openConnection(cts.Token);
 2104                return true;
 105            }
 6106            catch (Exception ex) when (attempt < MaxAttempts)
 107            {
 5108                _logger.LogInformation("Bitrix connection check attempt {Attempt} failed, retrying: {Message}", attempt,
 5109                await Task.Delay(_retryDelay);
 110            }
 1111            catch (Exception ex)
 112            {
 1113                _logger.LogWarning(ex, "Bitrix connection check failed: {Message}", ex.Message);
 1114                return false;
 115            }
 116        }
 4117    }
 118
 119    private async Task OpenMySqlConnectionAsync(CancellationToken cancellationToken)
 120    {
 0121        await using var connection = new MySqlConnection(_connectionString);
 0122        await connection.OpenAsync(cancellationToken);
 0123    }
 124}