fix(templateMatcher): Добавлено управление транзакцией в момент захват неиспользуемого шаблона, для устранения гонки.

This commit is contained in:
Mikhail Kuznetsov
2026-07-23 08:36:17 +10:00
parent 5094945c8e
commit 5f384947a6

View File

@@ -99,8 +99,13 @@ namespace PARR.DAL.Repositories
public async Task<Guid?> ReserveUnusedTemplateAsync(Guid newUnitId, HistoryInitiator initiator) public async Task<Guid?> ReserveUnusedTemplateAsync(Guid newUnitId, HistoryInitiator initiator)
{ {
logger.LogDebug("Резервирую неиспользуемый шаблон с проверкой конфигураций роботов для UnitId: {UnitId}", newUnitId); logger.LogDebug("Резервирую неиспользуемый шаблон для UnitId: {UnitId}", newUnitId);
// Явная транзакция гарантирует атомарность UPDATE + подзапроса
await using var transaction = await EntityContext.Database.BeginTransactionAsync();
try
{
var sql = @" var sql = @"
UPDATE ""Templates"" UPDATE ""Templates""
SET ""StatusTypeId"" = @NewStatus, SET ""StatusTypeId"" = @NewStatus,
@@ -114,16 +119,14 @@ namespace PARR.DAL.Repositories
WHERE t.""StatusTypeId"" = @OldStatus WHERE t.""StatusTypeId"" = @OldStatus
AND t.""UnitId"" != @NewUnitId AND t.""UnitId"" != @NewUnitId
AND EXISTS ( AND EXISTS (
SELECT 1 SELECT 1 FROM ""RobotConfigurations"" rc
FROM ""RobotConfigurations"" rc
WHERE rc.""TemplateId"" = t.""Id"" WHERE rc.""TemplateId"" = t.""Id""
AND rc.""RobotCode"" = @RobotCode1 AND rc.""RobotCode"" = @RobotCode1
AND rc.""TaskStatusCode"" = @TaskStatus AND rc.""TaskStatusCode"" = @TaskStatus
AND rc.""RobotStatusCode"" = @RobotStatus AND rc.""RobotStatusCode"" = @RobotStatus
) )
AND EXISTS ( AND EXISTS (
SELECT 1 SELECT 1 FROM ""RobotConfigurations"" rc
FROM ""RobotConfigurations"" rc
WHERE rc.""TemplateId"" = t.""Id"" WHERE rc.""TemplateId"" = t.""Id""
AND rc.""RobotCode"" = @RobotCode2 AND rc.""RobotCode"" = @RobotCode2
AND rc.""TaskStatusCode"" = @TaskStatus AND rc.""TaskStatusCode"" = @TaskStatus
@@ -131,6 +134,7 @@ namespace PARR.DAL.Repositories
) )
ORDER BY t.""DateCreated"" ASC ORDER BY t.""DateCreated"" ASC
LIMIT 1 LIMIT 1
FOR UPDATE SKIP LOCKED
) )
RETURNING ""Id"";"; RETURNING ""Id"";";
@@ -146,19 +150,18 @@ namespace PARR.DAL.Repositories
new NpgsqlParameter("@InitiatorComment", initiator.InitiatorComment ?? (object)DBNull.Value), new NpgsqlParameter("@InitiatorComment", initiator.InitiatorComment ?? (object)DBNull.Value),
new NpgsqlParameter("@OldStatus", (int)TemplateStatusTypeEnum.Unused), new NpgsqlParameter("@OldStatus", (int)TemplateStatusTypeEnum.Unused),
new NpgsqlParameter("@NewUnitId", newUnitId), new NpgsqlParameter("@NewUnitId", newUnitId),
// Параметры для проверки конфигураций роботов
new NpgsqlParameter("@RobotCode1", (int)RobotsEnum.TemplateOrder), new NpgsqlParameter("@RobotCode1", (int)RobotsEnum.TemplateOrder),
new NpgsqlParameter("@RobotCode2", (int)RobotsEnum.ScheduleOrder), new NpgsqlParameter("@RobotCode2", (int)RobotsEnum.ScheduleOrder),
new NpgsqlParameter("@TaskStatus", (int)TaskStatusEnum.Ok), new NpgsqlParameter("@TaskStatus", (int)TaskStatusEnum.Ok),
new NpgsqlParameter("@RobotStatus", (int)RobotStatusEnum.Complete) new NpgsqlParameter("@RobotStatus", (int)RobotStatusEnum.Complete)
}; };
try
{
var result = await EntityContext.Database var result = await EntityContext.Database
.SqlQueryRaw<Guid>(sql, parameters) .SqlQueryRaw<Guid>(sql, parameters)
.ToListAsync(); .ToListAsync();
await transaction.CommitAsync();
var reservedTemplateId = result.FirstOrDefault(); var reservedTemplateId = result.FirstOrDefault();
if (reservedTemplateId != Guid.Empty) if (reservedTemplateId != Guid.Empty)
@@ -167,14 +170,13 @@ namespace PARR.DAL.Repositories
reservedTemplateId, newUnitId); reservedTemplateId, newUnitId);
return reservedTemplateId; return reservedTemplateId;
} }
else
{ logger.LogDebug("Не удалось зарезервировать шаблон для UnitId: {UnitId}", newUnitId);
logger.LogDebug("Не удалось зарезервировать шаблон для UnitId: {UnitId} (не найдено подходящих конфигураций роботов)", newUnitId);
return null; return null;
} }
}
catch (Exception ex) catch (Exception ex)
{ {
await transaction.RollbackAsync();
logger.LogError(ex, "Ошибка при резервировании шаблона для UnitId: {UnitId}", newUnitId); logger.LogError(ex, "Ошибка при резервировании шаблона для UnitId: {UnitId}", newUnitId);
throw; throw;
} }