wenxiangchao
4 天以前 559890c482f5973f06aafdfec9550fee5ba3c7f2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
using System.Collections.Concurrent;
using Aging.Infrastructure.Contracts;
using Aging.ResourceContracts;
using log4net;
 
namespace Aging.Infrastructure.Core
{
    /// <summary>
    /// 基于 DbLeaseStore 的 IResourceManager 分层实现。
    /// 对外签名与 ResourceManager 完全一致(零改动),内部转发到 ILeaseCoordinator 仲裁库。
    /// </summary>
    public sealed class LayeredResourceManager : IResourceManager
    {
        private static readonly ILog Log = LogManager.GetLogger(typeof(LayeredResourceManager));
 
        private readonly ILeaseCoordinator _lease;
 
        /// <summary>
        /// 按 TokenId 缓存已获取的 LeaseGrant 数组,供 ValidateOwner / Release / AcquireOperation 使用。
        /// </summary>
        private readonly ConcurrentDictionary<string, LeaseGrant[]> _grantsByToken = new();
 
        /// <summary>
        /// 默认租约 TTL(秒)
        /// </summary>
        private const int DefaultTtlSeconds = 300; // 5 分钟,足够长用于 MVP
 
        public LayeredResourceManager(ILeaseCoordinator lease)
        {
            _lease = lease ?? throw new ArgumentNullException(nameof(lease));
        }
 
        /// <summary>
        /// 获取底层的 ILeaseCoordinator(供 AutoDeviceLease 心跳续期使用)
        /// </summary>
        public ILeaseCoordinator Coordinator => _lease;
 
        // ======================== ReserveAsync ========================
 
        public async Task<ResourceReservation> ReserveAsync(
            ResourceOwnerToken owner, ResourceKey resource, CancellationToken ct)
        {
            if (owner == null) throw new ArgumentNullException(nameof(owner));
            if (resource == null) throw new ArgumentNullException(nameof(resource));
 
            var req = new AcquireRequest
            {
                OwnerId = owner.OwnerId,
                OwnerType = (OwnerTypeV3)(int)owner.OwnerType,
                ResourceKeys = new[] { resource.ToString() },
                TtlSeconds = DefaultTtlSeconds,
                WaitTimeoutMs = 0,
                Reason = owner.OwnerId
            };
 
            AcquireOutcome outcome;
            try
            {
                outcome = await Task.Run(() => _lease.Acquire(req), ct).ConfigureAwait(false);
            }
            catch (OperationCanceledException)
            {
                throw;
            }
            catch (Exception ex)
            {
                Log.Error($"[LayeredRM] Acquire 异常: {ex.Message}", ex);
                throw new ResourceConflictException($"资源 {resource} 获取异常: {ex.Message}", ex);
            }
 
            switch (outcome.Code)
            {
                case AcquireResultCode.Acquired:
                case AcquireResultCode.Reused:
                    _grantsByToken[owner.TokenId] = outcome.Grants;
                    Log.Info($"[LayeredRM] 资源 {resource} 预约成功,所有者: {owner.OwnerId},lease: {outcome.Grants[0].LeaseId}");
                    return new ResourceReservation(resource, owner)
                    {
                        State = ReservationState.Active,
                        Description = $"LeaseId={outcome.Grants[0].LeaseId}"
                    };
 
                case AcquireResultCode.ConflictHeld:
                case AcquireResultCode.Timeout:
                    var msg = BuildConflictMessage(resource, outcome.Holder);
                    Log.Warn($"[LayeredRM] 资源冲突: {msg}");
                    throw new ResourceConflictException(msg);
 
                case AcquireResultCode.RecoveryBlocked:
                    throw new ResourceConflictException(
                        $"资源 {resource} 处于恢复中(RecoveryRequired),暂不可用");
 
                default:
                    throw new ResourceConflictException($"资源 {resource} 获取失败: {outcome.Code}");
            }
        }
 
        // ======================== ReleaseReservationAsync ========================
 
        public async Task ReleaseReservationAsync(
            ResourceOwnerToken owner, ResourceKey resource, CancellationToken ct)
        {
            if (owner == null) throw new ArgumentNullException(nameof(owner));
            if (resource == null) throw new ArgumentNullException(nameof(resource));
 
            var grant = FindGrant(owner.TokenId, resource);
            if (grant == null)
            {
                Log.Warn($"[LayeredRM] 尝试释放无 grant 的资源: {resource}, owner: {owner.OwnerId}");
                return;
            }
 
            await Task.Run(() => _lease.Release(grant, owner.OwnerId), ct).ConfigureAwait(false);
 
            // 清除缓存
            _grantsByToken.TryRemove(owner.TokenId, out _);
            Log.Info($"[LayeredRM] 资源 {resource} 已释放,所有者: {owner.OwnerId}");
        }
 
        // ======================== ValidateOwner ========================
 
        public bool ValidateOwner(ResourceOwnerToken owner, ResourceKey resource)
        {
            if (owner == null) throw new ArgumentNullException(nameof(owner));
            if (resource == null) throw new ArgumentNullException(nameof(resource));
 
            var grant = FindGrant(owner.TokenId, resource);
            if (grant == null) return false;
 
            // 栅栏校验:Renew 仅在“租约仍 Active 且 generation/epoch 匹配且未过期”时返回 true。
            // 用完整 TTL 续期而非 1s:既校验归属,又作为每次操作的保活,
            // 避免把租约到期时间砍到 1s 导致后续心跳 Renew 因“已过期”而失败→误报租约失效。
            return _lease.Renew(grant, DefaultTtlSeconds);
        }
 
        // ======================== IsReserved ========================
 
        public bool IsReserved(ResourceKey resource, out ResourceReservation? reservation)
        {
            if (resource == null) throw new ArgumentNullException(nameof(resource));
 
            var conflict = _lease.ProbeConflict(resource.ToString());
            if (conflict != null)
            {
                // 构造一个 "占位" reservation 返回给调用方
                var syntheticOwner = new ResourceOwnerToken(
                    (OwnerType)(int)conflict.OwnerType, conflict.OwnerId);
                reservation = new ResourceReservation(resource, syntheticOwner,
                    $"占用中: {conflict.OwnerId} ({conflict.Reason})")
                {
                    State = ReservationState.Active
                };
                return true;
            }
 
            reservation = null;
            return false;
        }
 
        // ======================== AcquireOperationAsync ========================
 
        public async Task<IOperationLease> AcquireOperationAsync(
            ResourceOwnerToken owner, ResourceKey resource, CancellationToken ct)
        {
            if (owner == null) throw new ArgumentNullException(nameof(owner));
            if (resource == null) throw new ArgumentNullException(nameof(resource));
 
            // 栅栏校验:确保 owner 仍持有该资源
            if (!ValidateOwner(owner, resource))
            {
                throw new InvalidOperationException(
                    $"资源 {resource} 的所有者栅栏校验失败,owner: {owner.OwnerId}");
            }
 
            await Task.CompletedTask;
 
            return new OperationLease(
                resource,
                owner,
                onRelease: () => Log.Debug($"[LayeredRM] 操作租约释放: {resource}"),
                leaseDurationSeconds: 30);
        }
 
        // ======================== 内部辅助 ========================
 
        /// <summary>
        /// 根据 TokenId 获取缓存的 LeaseGrant 数组(供 AutoDeviceLease 心跳续期使用)
        /// </summary>
        public bool TryGetGrants(string tokenId, out LeaseGrant[] grants)
        {
            return _grantsByToken.TryGetValue(tokenId, out grants);
        }
 
        /// <summary>
        /// 根据 TokenId 和资源键查找对应的 LeaseGrant
        /// </summary>
        private LeaseGrant? FindGrant(string tokenId, ResourceKey resource)
        {
            if (!_grantsByToken.TryGetValue(tokenId, out var grants))
                return null;
 
            var resourceStr = resource.ToString();
            foreach (var g in grants)
            {
                // DbLeaseStore 对 key 做了 NormKey(trim+lower+空格替换为-),
                // 这里用相同的规范化来比较
                if (string.Equals(DbLeaseStore.NormKey(g.ResourceKey),
                                  DbLeaseStore.NormKey(resourceStr),
                                  StringComparison.OrdinalIgnoreCase))
                    return g;
            }
            return null;
        }
 
        /// <summary>
        /// 构造冲突异常消息
        /// </summary>
        private static string BuildConflictMessage(ResourceKey resource, ConflictInfo? holder)
        {
            if (holder == null)
                return $"资源 {resource} 正被占用";
 
            long nowMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
            long remaining = Math.Max(0, (holder.ExpiresUtcMs - nowMs) / 1000);
 
            return $"资源 {resource} 正被占用:{holder.OwnerId}" +
                   $"({holder.OwnerType},机器 {holder.MachineName} PID {holder.HostProcessId}," +
                   $"剩余租约 {remaining}s)";
        }
    }
}