Архивация списков (#13)
Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz> Reviewed-on: http://git.tnsu.kz/gorbenko.pavel/tnsu-approval-route/pulls/13
This commit was merged in pull request #13.
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Tnsu.ApprovalRoute.Infrastructure.Persistence;
|
||||
|
||||
/// <summary>
|
||||
/// Блокировка «по типу заявок идёт создание следующей партиции списка» на строке
|
||||
/// request_types.
|
||||
///
|
||||
/// Почему сырой SQL, а не обычный EF: через change tracker это было бы
|
||||
/// read-modify-write (прочитали флаг → проверили → записали), и две задачи,
|
||||
/// прочитавшие null одновременно, обе решили бы, что блокировка свободна.
|
||||
/// Здесь проверка и захват — ОДИН условный UPDATE, атомарный на уровне строки:
|
||||
/// Postgres берёт row lock на время оператора, поэтому ровно одна из
|
||||
/// конкурирующих задач увидит affected rows = 1, остальные — 0.
|
||||
///
|
||||
/// Никакого pg_advisory_lock: он живёт в рамках сессии, а нам нужно, чтобы
|
||||
/// блокировку было видно в самой строке (админке — что провижининг идёт) и чтобы
|
||||
/// она переживала возврат соединения в пул между шагами задачи.
|
||||
/// </summary>
|
||||
public sealed class ListPartitioningLock(ApprovalRouteDbContext db)
|
||||
{
|
||||
/// <summary>
|
||||
/// Попытаться захватить блокировку. true — захватили (и только тогда можно
|
||||
/// провижинить), false — держит кто-то другой либо типа заявок нет.
|
||||
///
|
||||
/// Блокировка старше staleAfter считается брошенной (процесс упал, не сняв
|
||||
/// флаг) и перехватывается.
|
||||
/// </summary>
|
||||
public async Task<bool> TryAcquireAsync(int requestTypeId, TimeSpan staleAfter, CancellationToken cancellationToken)
|
||||
{
|
||||
// Имена таблицы/колонки — snake_case из UseSnakeCaseNamingConvention
|
||||
// (см. InfrastructureServiceCollectionExtensions).
|
||||
var affected = await db.Database.ExecuteSqlInterpolatedAsync(
|
||||
$"""
|
||||
UPDATE request_types
|
||||
SET list_partitioning_started_at = now()
|
||||
WHERE id = {requestTypeId}
|
||||
AND (list_partitioning_started_at IS NULL
|
||||
OR list_partitioning_started_at < now() - CAST({staleAfter} AS interval))
|
||||
""",
|
||||
cancellationToken);
|
||||
|
||||
return affected == 1;
|
||||
}
|
||||
|
||||
/// <summary>Снять блокировку. Идемпотентно: снятая блокировка снимается повторно без ошибки.</summary>
|
||||
public Task ReleaseAsync(int requestTypeId, CancellationToken cancellationToken) =>
|
||||
db.Database.ExecuteSqlInterpolatedAsync(
|
||||
$"UPDATE request_types SET list_partitioning_started_at = NULL WHERE id = {requestTypeId}",
|
||||
cancellationToken);
|
||||
}
|
||||
Reference in New Issue
Block a user