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 { /// /// 自动化设备门面实现。 /// /// 生命周期: /// 1) ReserveAsync 申请资源占用 → 得到 ResourceOwnerToken /// 2) DeviceService 分派命令 → 内部再拿操作租约 /// 3) ReleaseReservationAsync 释放资源 /// /// 两种调用模式: /// - ExecuteAsync/QueryAsync:一次性执行(内部完成 1→2→3) /// - AcquireAsync:作用域租约(1 手动触发;3 在 lease.Dispose 时触发;2 可多次) /// public sealed class AutoDeviceFacade : IAutoDeviceFacade { private static readonly ILog Log = LogManager.GetLogger(typeof(AutoDeviceFacade)); private readonly IResourceManager _resourceManager; private readonly IDeviceService _deviceService; /// /// 默认租约申请超时(秒) /// public int DefaultLeaseTimeoutSeconds { get; set; } = 30; /// /// 构造函数 /// public AutoDeviceFacade(IResourceManager resourceManager, IDeviceService deviceService) { _resourceManager = resourceManager ?? throw new ArgumentNullException(nameof(resourceManager)); _deviceService = deviceService ?? throw new ArgumentNullException(nameof(deviceService)); } // ==================== 一次性执行模式 ==================== /// public async Task 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); } } /// public async Task 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); } } // ==================== 作用域租约模式 ==================== /// public async Task 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; } // ==================== 内部辅助 ==================== /// /// 内部:申请资源占用。 /// 使用 OwnerType.Production(自动化测试视为生产流程), /// 会与其他测试/维护会话互斥。 /// private async Task 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; } /// /// 内部:释放资源占用。 /// 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}"); } } } /// /// 自动化设备作用域租约实现。 /// Dispose 时自动释放底层资源占用。 /// 幂等:多次 Dispose 只释放一次。 /// 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 ExecuteAsync(string command, CancellationToken ct = default) { ThrowIfReleased(); ThrowIfStale(); return _deviceService.ExecuteCommandAsync(DeviceId, command, _owner, ct); } public Task 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: 租约已失效"); } } }