using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using PARR.Common.Domain; using PARR.Constants; using PARR.DAL.Models; using PARR.DAL.Services.Interfaces; using PARR.TemplateMatcher.Services.Interfaces; namespace PARR.TemplateMatcher.Services.Implementations; internal class TemplateReuser : ITemplateReuser { private const int UnusedCandidateBatchSize = 10; private readonly ILogger logger; private readonly ITemplateService templateService; public TemplateReuser( ILogger logger, ITemplateService templateService) { this.logger = logger; this.templateService = templateService; } public async Task TryReuseOneUnusedTemplateAsync( Guid jobId, Guid unitId, HistoryInitiator initiator, int maxAttempts = 3) { for (int attempt = 1; attempt <= maxAttempts; attempt++) { try { var unusedCandidates = await templateService.Get() .Where(t => t.StatusTypeId == TemplateStatusTypeEnum.Unused) .OrderBy(t => t.DateModified ?? t.DateCreated) .Take(UnusedCandidateBatchSize) .ToListAsync(); if (!unusedCandidates.Any()) { logger.LogDebug("Нет Unused-шаблонов (попытка {Attempt}).", attempt); return null; } foreach (var candidate in unusedCandidates) { var originalStatus = candidate.StatusTypeId; var originalModified = candidate.DateModified; try { candidate.StatusTypeId = TemplateStatusTypeEnum.Updating; candidate.DateModified = DateTimeOffset.UtcNow; if (await templateService.CommitAsync(initiator)) { logger.LogInformation("Успешно захвачен шаблон {TemplateId} для переиспользования (попытка {Attempt}).", candidate.Id, attempt); return candidate; // Возвращаем захваченный шаблон } // Откат при неудаче candidate.StatusTypeId = originalStatus; candidate.DateModified = originalModified; } catch (Exception ex) when ( ex is DbUpdateException || ex.InnerException?.Message.Contains("deadlock", StringComparison.OrdinalIgnoreCase) == true || ex.InnerException?.Message.Contains("timeout", StringComparison.OrdinalIgnoreCase) == true) { logger.LogWarning(ex, "Конфликт при захвате шаблона {TemplateId} (попытка {Attempt}).", candidate.Id, attempt); candidate.StatusTypeId = originalStatus; candidate.DateModified = originalModified; } } if (attempt < maxAttempts) await Task.Delay(Random.Shared.Next(5, 15) * attempt); } catch (Exception ex) { logger.LogError(ex, "Ошибка в попытке захвата (попытка {Attempt}).", attempt); if (attempt == maxAttempts) throw; } } return null; } }