using System.Threading; using System.Threading.Tasks; using FrameworkDAL.CustomCode.GOP; using GB5Shared.DTO.Framework.Login; using Microsoft.Extensions.Logging; namespace FrameworkBLL.GOP.Worker { // ============================================================ // GopExecutionLockService — optimistic lock management. // // AcquireLock: attempts an INSERT into TGOPEXECUTIONLOCK via the // DAL. The DAL uses optimistic concurrency (unique constraint on // ExecutionId). If another worker holds the lock, the DAL // returns false — no exception. // // ReleaseLock: DELETE WHERE ExecutionId=@id AND LockedBy=@workerId // so a worker can only release its own lock (not another's). // Always called in a finally block by GopWorkerService. // ============================================================ public class GopExecutionLockService { private readonly IGopQueueDAL _QueueDal; private readonly ILogger _Logger; public GopExecutionLockService( IGopQueueDAL queueDal, ILogger logger) { _QueueDal = queueDal; _Logger = logger; } /// /// Attempts to acquire a lock for the given execution. /// Returns true if the lock was successfully acquired. /// Returns false if another worker already holds the lock (no exception). /// public async Task TryAcquireLockAsync( int clientId, int executionId, string workerId, int lockDurationSeconds, LoginDTO loginDTO, CancellationToken ct = default) { try { bool acquired = await _QueueDal.AcquireLock( clientId, executionId, workerId, lockDurationSeconds, loginDTO); if (!acquired) _Logger.LogDebug( "GopLock: execution {ExecutionId} — lock already held by another worker", executionId); return acquired; } catch { // If AcquireLock throws (e.g. duplicate key), treat as "not acquired". _Logger.LogWarning( "GopLock: exception acquiring lock for execution {ExecutionId}", executionId); return false; } } /// /// Releases the lock for the given execution. /// Safe to call even if the lock has already expired or was released. /// MUST be called in a finally block — never skip. /// public async Task ReleaseLockAsync( int clientId, int executionId, LoginDTO loginDTO, CancellationToken ct = default) { try { await _QueueDal.ReleaseLock(clientId, executionId, loginDTO); } catch { // Log but do not rethrow — release failure must not mask the real exception. _Logger.LogWarning( "GopLock: exception releasing lock for execution {ExecutionId} — " + "lock will expire via TTL", executionId); } } } }