fix(templateMatcher): Добавлено управление транзакцией в момент захват неиспользуемого шаблона, для устранения гонки.
This commit is contained in:
@@ -99,66 +99,69 @@ 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);
|
||||||
|
|
||||||
var sql = @"
|
// Явная транзакция гарантирует атомарность UPDATE + подзапроса
|
||||||
UPDATE ""Templates""
|
await using var transaction = await EntityContext.Database.BeginTransactionAsync();
|
||||||
SET ""StatusTypeId"" = @NewStatus,
|
|
||||||
""DateModified"" = @DateModified,
|
|
||||||
""InitiatorIp"" = @InitiatorIp,
|
|
||||||
""InitiatorParrComponentId"" = @InitiatorComponent,
|
|
||||||
""InitiatorComment"" = @InitiatorComment
|
|
||||||
WHERE ""Id"" = (
|
|
||||||
SELECT t.""Id""
|
|
||||||
FROM ""Templates"" t
|
|
||||||
WHERE t.""StatusTypeId"" = @OldStatus
|
|
||||||
AND t.""UnitId"" != @NewUnitId
|
|
||||||
AND EXISTS (
|
|
||||||
SELECT 1
|
|
||||||
FROM ""RobotConfigurations"" rc
|
|
||||||
WHERE rc.""TemplateId"" = t.""Id""
|
|
||||||
AND rc.""RobotCode"" = @RobotCode1
|
|
||||||
AND rc.""TaskStatusCode"" = @TaskStatus
|
|
||||||
AND rc.""RobotStatusCode"" = @RobotStatus
|
|
||||||
)
|
|
||||||
AND EXISTS (
|
|
||||||
SELECT 1
|
|
||||||
FROM ""RobotConfigurations"" rc
|
|
||||||
WHERE rc.""TemplateId"" = t.""Id""
|
|
||||||
AND rc.""RobotCode"" = @RobotCode2
|
|
||||||
AND rc.""TaskStatusCode"" = @TaskStatus
|
|
||||||
AND rc.""RobotStatusCode"" = @RobotStatus
|
|
||||||
)
|
|
||||||
ORDER BY t.""DateCreated"" ASC
|
|
||||||
LIMIT 1
|
|
||||||
)
|
|
||||||
RETURNING ""Id"";";
|
|
||||||
|
|
||||||
var parameters = new[]
|
|
||||||
{
|
|
||||||
new NpgsqlParameter("@NewStatus", (int)TemplateStatusTypeEnum.Updating),
|
|
||||||
new NpgsqlParameter("@DateModified", DateTimeOffset.UtcNow),
|
|
||||||
new NpgsqlParameter("@InitiatorIp", initiator.InitiatorIp ?? (object)DBNull.Value),
|
|
||||||
new NpgsqlParameter("@InitiatorComponent",
|
|
||||||
initiator.InitiatorParrComponentId.HasValue
|
|
||||||
? (object)(int)initiator.InitiatorParrComponentId.Value
|
|
||||||
: DBNull.Value),
|
|
||||||
new NpgsqlParameter("@InitiatorComment", initiator.InitiatorComment ?? (object)DBNull.Value),
|
|
||||||
new NpgsqlParameter("@OldStatus", (int)TemplateStatusTypeEnum.Unused),
|
|
||||||
new NpgsqlParameter("@NewUnitId", newUnitId),
|
|
||||||
// Параметры для проверки конфигураций роботов
|
|
||||||
new NpgsqlParameter("@RobotCode1", (int)RobotsEnum.TemplateOrder),
|
|
||||||
new NpgsqlParameter("@RobotCode2", (int)RobotsEnum.ScheduleOrder),
|
|
||||||
new NpgsqlParameter("@TaskStatus", (int)TaskStatusEnum.Ok),
|
|
||||||
new NpgsqlParameter("@RobotStatus", (int)RobotStatusEnum.Complete)
|
|
||||||
};
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
var sql = @"
|
||||||
|
UPDATE ""Templates""
|
||||||
|
SET ""StatusTypeId"" = @NewStatus,
|
||||||
|
""DateModified"" = @DateModified,
|
||||||
|
""InitiatorIp"" = @InitiatorIp,
|
||||||
|
""InitiatorParrComponentId"" = @InitiatorComponent,
|
||||||
|
""InitiatorComment"" = @InitiatorComment
|
||||||
|
WHERE ""Id"" = (
|
||||||
|
SELECT t.""Id""
|
||||||
|
FROM ""Templates"" t
|
||||||
|
WHERE t.""StatusTypeId"" = @OldStatus
|
||||||
|
AND t.""UnitId"" != @NewUnitId
|
||||||
|
AND EXISTS (
|
||||||
|
SELECT 1 FROM ""RobotConfigurations"" rc
|
||||||
|
WHERE rc.""TemplateId"" = t.""Id""
|
||||||
|
AND rc.""RobotCode"" = @RobotCode1
|
||||||
|
AND rc.""TaskStatusCode"" = @TaskStatus
|
||||||
|
AND rc.""RobotStatusCode"" = @RobotStatus
|
||||||
|
)
|
||||||
|
AND EXISTS (
|
||||||
|
SELECT 1 FROM ""RobotConfigurations"" rc
|
||||||
|
WHERE rc.""TemplateId"" = t.""Id""
|
||||||
|
AND rc.""RobotCode"" = @RobotCode2
|
||||||
|
AND rc.""TaskStatusCode"" = @TaskStatus
|
||||||
|
AND rc.""RobotStatusCode"" = @RobotStatus
|
||||||
|
)
|
||||||
|
ORDER BY t.""DateCreated"" ASC
|
||||||
|
LIMIT 1
|
||||||
|
FOR UPDATE SKIP LOCKED
|
||||||
|
)
|
||||||
|
RETURNING ""Id"";";
|
||||||
|
|
||||||
|
var parameters = new[]
|
||||||
|
{
|
||||||
|
new NpgsqlParameter("@NewStatus", (int)TemplateStatusTypeEnum.Updating),
|
||||||
|
new NpgsqlParameter("@DateModified", DateTimeOffset.UtcNow),
|
||||||
|
new NpgsqlParameter("@InitiatorIp", initiator.InitiatorIp ?? (object)DBNull.Value),
|
||||||
|
new NpgsqlParameter("@InitiatorComponent",
|
||||||
|
initiator.InitiatorParrComponentId.HasValue
|
||||||
|
? (object)(int)initiator.InitiatorParrComponentId.Value
|
||||||
|
: DBNull.Value),
|
||||||
|
new NpgsqlParameter("@InitiatorComment", initiator.InitiatorComment ?? (object)DBNull.Value),
|
||||||
|
new NpgsqlParameter("@OldStatus", (int)TemplateStatusTypeEnum.Unused),
|
||||||
|
new NpgsqlParameter("@NewUnitId", newUnitId),
|
||||||
|
new NpgsqlParameter("@RobotCode1", (int)RobotsEnum.TemplateOrder),
|
||||||
|
new NpgsqlParameter("@RobotCode2", (int)RobotsEnum.ScheduleOrder),
|
||||||
|
new NpgsqlParameter("@TaskStatus", (int)TaskStatusEnum.Ok),
|
||||||
|
new NpgsqlParameter("@RobotStatus", (int)RobotStatusEnum.Complete)
|
||||||
|
};
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user