feat(dal, templateMatcher): изменена логика работы Shortcodes сервиса в сторону самостоятельной дозагрузки данных из БД, в Template сервис добавлен метод атомарного резервирования шаблона для TemplateMatcher

This commit is contained in:
Mikhail Kuznetsov
2026-01-16 18:26:06 +10:00
parent 99a491ede8
commit d91877453a
25 changed files with 822 additions and 509 deletions

View File

@@ -1,7 +1,6 @@
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;
@@ -10,8 +9,6 @@ namespace PARR.TemplateMatcher.Services.Implementations;
internal class TemplateReuser : ITemplateReuser
{
private const int UnusedCandidateBatchSize = 10;
private readonly ILogger<TemplateReuser> logger;
private readonly ITemplateService templateService;
@@ -25,7 +22,7 @@ internal class TemplateReuser : ITemplateReuser
public async Task<Template?> TryReuseOneUnusedTemplateAsync(
Guid jobId,
Guid unitId,
Guid unitId,
HistoryInitiator initiator,
int maxAttempts = 3)
{
@@ -33,57 +30,47 @@ internal class TemplateReuser : ITemplateReuser
{
try
{
var unusedCandidates = await templateService.Get()
.Where(t => t.StatusTypeId == TemplateStatusTypeEnum.Unused)
.OrderBy(t => t.DateModified ?? t.DateCreated)
.Take(UnusedCandidateBatchSize)
.ToListAsync();
// Атомарно резервируем один шаблон через DAL
var templateId = await templateService.ReserveUnusedTemplateAsync(unitId, initiator);
if (!unusedCandidates.Any())
if (templateId == null)
{
logger.LogDebug("Нет Unused-шаблонов (попытка {Attempt}).", attempt);
logger.LogDebug("Нет доступных Unused-шаблонов для переиспользования (попытка {Attempt}).", attempt);
return null;
}
foreach (var candidate in unusedCandidates)
// Загружаем зарезервированный шаблон
var template = await templateService.Get()
.AsNoTracking()
.Include(t=>t.Unit)
.Include(t=>t.Job)
.ThenInclude(t=>t!.Tnk)
.Include(t => t.Job)
.ThenInclude(t => t!.Group)
.ThenInclude(t=>t!.GroupType)
.FirstOrDefaultAsync(t => t.Id == templateId);
if (template == null)
{
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;
}
logger.LogWarning("Зарезервированный шаблон {TemplateId} не найден при загрузке.", templateId);
continue;
}
if (attempt < maxAttempts)
await Task.Delay(Random.Shared.Next(5, 15) * attempt);
logger.LogInformation(
"Успешно захвачен шаблон {TemplateId} (старый Job {OldJobId}) для нового Job {NewJobId}, Unit {UnitId} (попытка {Attempt}).",
template.Id, template.JobId, jobId, unitId, attempt);
return template;
}
catch (Exception ex)
{
logger.LogError(ex, "Ошибка в попытке захвата (попытка {Attempt}).", attempt);
if (attempt == maxAttempts) throw;
logger.LogError(ex, "Ошибка при попытке захвата шаблона (попытка {Attempt}).", attempt);
if (attempt == maxAttempts)
throw;
// Небольшая задержка перед повтором
await Task.Delay(Random.Shared.Next(10, 50));
}
}