using System.Collections.Concurrent; using GmRelay.Shared.Platform; namespace GmRelay.Shared.Features.Sessions.CreateSession; public interface IScheduleMessageUpdateLock { ValueTask AcquireAsync(PlatformMessageRef scheduleMessage, CancellationToken ct); } public sealed class ScheduleMessageUpdateLock : IScheduleMessageUpdateLock { private readonly ConcurrentDictionary locks = new(StringComparer.Ordinal); public async ValueTask AcquireAsync(PlatformMessageRef scheduleMessage, CancellationToken ct) { var key = CreateKey(scheduleMessage); var semaphore = locks.GetOrAdd(key, _ => new SemaphoreSlim(1, 1)); await semaphore.WaitAsync(ct); return new Releaser(semaphore); } private static string CreateKey(PlatformMessageRef scheduleMessage) => string.Join( '\u001F', scheduleMessage.Platform.ToString(), scheduleMessage.ExternalGroupId, scheduleMessage.ExternalThreadId ?? string.Empty, scheduleMessage.ExternalMessageId); private sealed class Releaser(SemaphoreSlim semaphore) : IAsyncDisposable { public ValueTask DisposeAsync() { semaphore.Release(); return ValueTask.CompletedTask; } } }