wenxiangchao
3 天以前 b544d5cccd76735e034c8055918a50927b437474
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
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
using System;
using System.Threading;
using System.Threading.Tasks;
using Aging.Infrastructure.Contracts;
using Aging.Infrastructure.Core;
using Aging.ResourceContracts;
using log4net;
 
namespace Aging.Infrastructure.Services
{
    /// <summary>
    /// 自动化设备门面实现。
    /// 
    /// 生命周期:
    ///   1) ReserveAsync 申请资源占用 → 得到 ResourceOwnerToken
    ///   2) DeviceService 分派命令 → 内部再拿操作租约
    ///   3) ReleaseReservationAsync 释放资源
    /// 
    /// 两种调用模式:
    ///   - ExecuteAsync/QueryAsync:一次性执行(内部完成 1→2→3)
    ///   - AcquireAsync:作用域租约(1 手动触发;3 在 lease.Dispose 时触发;2 可多次)
    /// </summary>
    public sealed class AutoDeviceFacade : IAutoDeviceFacade
    {
        private static readonly ILog Log = LogManager.GetLogger(typeof(AutoDeviceFacade));
 
        private readonly IResourceManager _resourceManager;
        private readonly IDeviceService _deviceService;
 
        /// <summary>
        /// 默认租约申请超时(秒)
        /// </summary>
        public int DefaultLeaseTimeoutSeconds { get; set; } = 30;
 
        /// <summary>
        /// 构造函数
        /// </summary>
        public AutoDeviceFacade(IResourceManager resourceManager, IDeviceService deviceService)
        {
            _resourceManager = resourceManager ?? throw new ArgumentNullException(nameof(resourceManager));
            _deviceService = deviceService ?? throw new ArgumentNullException(nameof(deviceService));
        }
 
        // ==================== 一次性执行模式 ====================
 
        /// <inheritdoc/>
        public async Task<DeviceCommandResult> ExecuteAsync(
            string deviceId, string command, CancellationToken ct = default)
        {
            if (string.IsNullOrWhiteSpace(deviceId))
                throw new ArgumentException("设备 ID 不能为空", nameof(deviceId));
            if (string.IsNullOrWhiteSpace(command))
                throw new ArgumentException("命令不能为空", nameof(command));
 
            // 1. 申请资源占用
            var owner = await ReserveCoreAsync(deviceId, "OneShot", ct).ConfigureAwait(false);
            try
            {
                // 2. 通过 DeviceService 分派命令
                return await _deviceService.ExecuteCommandAsync(deviceId, command, owner, ct)
                    .ConfigureAwait(false);
            }
            finally
            {
                // 3. 无论成功失败都释放资源
                await ReleaseCoreAsync(deviceId, owner).ConfigureAwait(false);
            }
        }
 
        /// <inheritdoc/>
        public async Task<DeviceCommandResult> QueryAsync(
            string deviceId, string query, CancellationToken ct = default)
        {
            if (string.IsNullOrWhiteSpace(deviceId))
                throw new ArgumentException("设备 ID 不能为空", nameof(deviceId));
 
            var owner = await ReserveCoreAsync(deviceId, "OneShotQuery", ct).ConfigureAwait(false);
            try
            {
                return await _deviceService.ExecuteQueryAsync(deviceId, query, owner, ct)
                    .ConfigureAwait(false);
            }
            finally
            {
                await ReleaseCoreAsync(deviceId, owner).ConfigureAwait(false);
            }
        }
 
        // ==================== 作用域租约模式 ====================
 
        /// <inheritdoc/>
        public async Task<IAutoDeviceLease> AcquireAsync(
            string deviceId, string stepName, CancellationToken ct = default)
        {
            if (string.IsNullOrWhiteSpace(deviceId))
                throw new ArgumentException("设备 ID 不能为空", nameof(deviceId));
 
            var owner = await ReserveCoreAsync(deviceId, stepName ?? "Step", ct).ConfigureAwait(false);
            var sessionId = Guid.NewGuid().ToString();
 
            Log.Info($"[AutoFacade] 设备 {deviceId} 已获得作用域租约: Session={sessionId.Substring(0, 8)}, Step={stepName}");
 
            var lease = new AutoDeviceLease(this, _deviceService, deviceId, sessionId, stepName ?? "Step", owner);
 
            // 如果底层是 LayeredResourceManager,启动心跳续期
            if (_resourceManager is LayeredResourceManager lrm)
            {
                if (lrm.TryGetGrants(owner.TokenId, out var grants))
                {
                    lease.StartHeartbeat(lrm.Coordinator, grants);
                }
            }
 
            return lease;
        }
 
        // ==================== 内部辅助 ====================
 
        /// <summary>
        /// 内部:申请资源占用。
        /// 使用 OwnerType.Production(自动化测试视为生产流程),
        /// 会与其他测试/维护会话互斥。
        /// </summary>
        private async Task<ResourceOwnerToken> ReserveCoreAsync(
            string deviceId, string purpose, CancellationToken ct)
        {
            var owner = new ResourceOwnerToken(OwnerType.Production, $"auto_{Guid.NewGuid():N}");
            var resourceKey = new ResourceKey(deviceId, "Generic");
 
            // 使用超时包装
            using (var cts = CancellationTokenSource.CreateLinkedTokenSource(ct))
            {
                cts.CancelAfter(TimeSpan.FromSeconds(DefaultLeaseTimeoutSeconds));
                try
                {
                    await _resourceManager.ReserveAsync(owner, resourceKey, cts.Token).ConfigureAwait(false);
                    Log.Debug($"[AutoFacade] 设备 {deviceId} 已预约 (purpose={purpose}, owner={owner.OwnerId})");
                }
                catch (OperationCanceledException)
                {
                    throw new TimeoutException($"设备 {deviceId} 租约申请超时({DefaultLeaseTimeoutSeconds}s),可能被生产或维护占用");
                }
                catch (ResourceConflictException ex)
                {
                    throw new InvalidOperationException(
                        $"设备 {deviceId} 已被其他所有者占用: {ex.Message}", ex);
                }
            }
 
            return owner;
        }
 
        /// <summary>
        /// 内部:释放资源占用。
        /// </summary>
        internal async Task ReleaseCoreAsync(string deviceId, ResourceOwnerToken owner)
        {
            try
            {
                var resourceKey = new ResourceKey(deviceId, "Generic");
                await _resourceManager.ReleaseReservationAsync(owner, resourceKey, CancellationToken.None)
                    .ConfigureAwait(false);
                Log.Debug($"[AutoFacade] 设备 {deviceId} 已释放预约 (owner={owner.OwnerId})");
            }
            catch (Exception ex)
            {
                // 释放失败不阻塞主流程,仅记录
                Log.Warn($"[AutoFacade] 释放设备 {deviceId} 预约异常: {ex.Message}");
            }
        }
    }
 
    /// <summary>
    /// 自动化设备作用域租约实现。
    /// Dispose 时自动释放底层资源占用。
    /// 幂等:多次 Dispose 只释放一次。
    /// </summary>
    internal sealed class AutoDeviceLease : IAutoDeviceLease
    {
        private readonly AutoDeviceFacade _facade;
        private readonly IDeviceService _deviceService;
        private readonly ResourceOwnerToken _owner;
        private bool _disposed;
 
        // V3.0 心跳续期字段
        private LeaseGrant[] _grants = new LeaseGrant[0];
        private Timer? _beacon;
        private volatile int _renewFailures;
        private ILeaseCoordinator? _coordinator;
        public event EventHandler? LeaseLost;
        public bool HandoverRequested => false;
 
        public string DeviceId { get; }
        public string SessionId { get; }
        public string StepName { get; }
        public DateTime AcquiredAt { get; }
        public bool IsReleased => _disposed;
 
        internal AutoDeviceLease(
            AutoDeviceFacade facade,
            IDeviceService deviceService,
            string deviceId,
            string sessionId,
            string stepName,
            ResourceOwnerToken owner)
        {
            _facade = facade;
            _deviceService = deviceService;
            DeviceId = deviceId;
            SessionId = sessionId;
            StepName = stepName;
            AcquiredAt = DateTime.Now;
            _owner = owner;
        }
 
        internal void StartHeartbeat(ILeaseCoordinator coordinator, LeaseGrant[] grants)
        {
            _coordinator = coordinator;
            _grants = grants;
            _beacon = new Timer(_ =>
            {
                if (_disposed) return;
                try
                {
                    bool allOk = true;
                    foreach (var g in _grants)
                    {
                        if (!_coordinator.Renew(g, 30)) { allOk = false; break; }
                    }
                    if (allOk)
                        Interlocked.Exchange(ref _renewFailures, 0);
                    else
                        Interlocked.Increment(ref _renewFailures);
                    if (Volatile.Read(ref _renewFailures) >= 2) LeaseLost?.Invoke(this, EventArgs.Empty);
                }
                catch
                {
                    if (Interlocked.Increment(ref _renewFailures) >= 2) LeaseLost?.Invoke(this, EventArgs.Empty);
                }
            }, null, 8000, 8000);
        }
 
        public Task<DeviceCommandResult> ExecuteAsync(string command, CancellationToken ct = default)
        {
            ThrowIfReleased();
            ThrowIfStale();
            return _deviceService.ExecuteCommandAsync(DeviceId, command, _owner, ct);
        }
 
        public Task<DeviceCommandResult> QueryAsync(string query, CancellationToken ct = default)
        {
            ThrowIfReleased();
            ThrowIfStale();
            return _deviceService.ExecuteQueryAsync(DeviceId, query, _owner, ct);
        }
 
        public Task ReleaseAsync(CancellationToken ct = default)
        {
            // Dispose() 内部已处理 _beacon?.Dispose() 和 _disposed 标记
            Dispose();
            return Task.CompletedTask;
        }
 
        public void Dispose()
        {
            if (_disposed) return;
            _beacon?.Dispose();
            _disposed = true;
            // 同步释放(.NET 4.7.2 无 IAsyncDisposable),阻塞等待
            _facade.ReleaseCoreAsync(DeviceId, _owner).GetAwaiter().GetResult();
            GC.SuppressFinalize(this);
        }
 
        private void ThrowIfReleased()
        {
            if (_disposed)
                throw new ObjectDisposedException(nameof(AutoDeviceLease),
                    $"设备 {DeviceId} 的租约已释放 (Session={SessionId.Substring(0, 8)})");
        }
 
        private void ThrowIfStale()
        {
            if (Volatile.Read(ref _renewFailures) >= 2)
                throw new InvalidOperationException("LEASE_STALE: 租约已失效");
        }
    }
}