// Copyright (c) 2025 GeWuYou // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. using System.Collections.Concurrent; using GFramework.Core.Abstractions.Concurrency; namespace GFramework.Core.Concurrency; /// /// 工业级异步键锁管理器,支持自动清理、统计和调试 /// public sealed class AsyncKeyLockManager : IAsyncKeyLockManager { private readonly Timer _cleanupTimer; private readonly ConcurrentDictionary _locks = new(); private readonly long _lockTimeoutTicks; private volatile bool _disposed; // 统计计数器 private int _totalAcquired; private int _totalCleaned; private int _totalReleased; /// /// 初始化锁管理器 /// /// 清理间隔,默认 60 秒 /// 锁超时时间,默认 300 秒 public AsyncKeyLockManager(TimeSpan? cleanupInterval = null, TimeSpan? lockTimeout = null) { var cleanupIntervalValue = cleanupInterval ?? TimeSpan.FromSeconds(60); var lockTimeoutValue = lockTimeout ?? TimeSpan.FromSeconds(300); _lockTimeoutTicks = (long)(lockTimeoutValue.TotalMilliseconds * TimeSpan.TicksPerMillisecond / 10000); _cleanupTimer = new Timer(CleanupUnusedLocks, null, cleanupIntervalValue, cleanupIntervalValue); } /// /// 异步获取指定键的锁 /// public async ValueTask AcquireLockAsync(string key, CancellationToken cancellationToken = default) { ObjectDisposedException.ThrowIf(_disposed, this); var entry = _locks.GetOrAdd(key, _ => new LockEntry()); Interlocked.Increment(ref entry.ReferenceCount); entry.LastAccessTicks = System.Environment.TickCount64; // 再次检查 disposed(防止在 GetOrAdd 后 Dispose) if (_disposed) { Interlocked.Decrement(ref entry.ReferenceCount); throw new ObjectDisposedException(nameof(AsyncKeyLockManager)); } await entry.Semaphore.WaitAsync(cancellationToken); Interlocked.Increment(ref _totalAcquired); return new AsyncLockHandle(this, key, entry, System.Environment.TickCount64); } /// /// 同步获取指定键的锁 /// public IAsyncLockHandle AcquireLock(string key) { return AcquireLockAsync(key).AsTask().GetAwaiter().GetResult(); } /// /// 获取统计信息 /// public LockStatistics GetStatistics() { return new LockStatistics { ActiveLockCount = _locks.Count, TotalAcquired = _totalAcquired, TotalReleased = _totalReleased, TotalCleaned = _totalCleaned }; } /// /// 获取活跃锁信息 /// public IReadOnlyDictionary GetActiveLocks() { return _locks.ToDictionary( kvp => kvp.Key, kvp => new LockInfo { Key = kvp.Key, ReferenceCount = kvp.Value.ReferenceCount, LastAccessTicks = kvp.Value.LastAccessTicks, WaitingCount = kvp.Value.Semaphore.CurrentCount == 0 ? 1 : 0 }); } /// /// 释放资源 /// public void Dispose() { if (_disposed) return; _disposed = true; _cleanupTimer.Dispose(); // 统一释放所有 semaphore foreach (var entry in _locks.Values) { entry.Dispose(); } _locks.Clear(); } /// /// 释放指定键的锁 /// internal void ReleaseLock(string key, LockEntry entry) { entry.Semaphore.Release(); Interlocked.Decrement(ref entry.ReferenceCount); Interlocked.Increment(ref _totalReleased); entry.LastAccessTicks = System.Environment.TickCount64; } /// /// 清理未使用的锁(不 Dispose semaphore,避免 race condition) /// private void CleanupUnusedLocks(object? state) { if (_disposed) return; var now = System.Environment.TickCount64; foreach (var (key, entry) in _locks) { // 只检查引用计数和超时,不 Dispose if (entry.ReferenceCount == 0 && now - entry.LastAccessTicks > _lockTimeoutTicks && _locks.TryRemove(key, out _)) { Interlocked.Increment(ref _totalCleaned); } } } /// /// 锁条目,包含信号量和引用计数 /// internal sealed class LockEntry : IDisposable { public readonly SemaphoreSlim Semaphore = new(1, 1); public long LastAccessTicks = System.Environment.TickCount64; public int ReferenceCount; public void Dispose() { Semaphore.Dispose(); } } }