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: 租约已失效");
}
}
}