using System; using System.Collections.Generic; using System.Linq; using log4net; using Plugin.DeviceDebug.Entity; using Aging.Infrastructure.Contracts; namespace Plugin.DeviceDebug.Model { /// /// 调试会话管理器 - 负责状态机转换和权限校验 /// public class DebugSessionManager { private static readonly ILog log = LogManager.GetLogger(typeof(DebugSessionManager)); private readonly DeviceDebugModel _model; private readonly IResourceManager? _resourceManager; // 内存中的活跃会话缓存 private readonly Dictionary _activeSessions = new(); /// /// 维护租约心跳:与 LayeredResourceManager.DefaultTtlSeconds(300s) 对齐, /// 每 120s 续期一次,防止长时间维护会话的 arb.db 租约过期进入 RecoveryRequired 后禁止 I/O。 /// private System.Threading.Timer _renewTimer; private const int MaintenanceTtlSeconds = 300; private const int RenewIntervalMs = 120_000; /// /// 会话→资源令牌缓存:LayeredResourceManager 的租约按 TokenId 索引, /// 而 ResourceOwnerToken.TokenId 每次 new 都是新 GUID,必须复用同一实例才能释放到租约 /// private readonly Dictionary _sessionOwnerTokens = new(); public DebugSessionManager(DeviceDebugModel model, IResourceManager? resourceManager = null) { _model = model; _resourceManager = resourceManager; } /// /// 打开会话(ReadOnly模式) /// public DebugSession OpenSession(string deviceId, string operatorId, string operatorName, string effectiveVersion) { try { // 检查是否已有活跃会话 var existingSession = GetActiveSessionByDevice(deviceId); if (existingSession != null) { // 进程内存中存在 = 本会话真活跃,直接复用 if (_activeSessions.ContainsKey(existingSession.SessionId)) { log.Warn($"设备 {deviceId} 已有活跃会话: {existingSession.SessionId}"); return existingSession; } // 仅存在于数据库且不在本进程内存 = 旧进程退出前的残留会话: // 其内存上下文/维护租约(arb.db 侧靠 TTL 过期+回收)已随进程消亡, // 必须回收为 Closed 再新建,否则 RequestMaintenance 永远“会话不存在”拒绝 _activeSessions[existingSession.SessionId] = existingSession; existingSession.State = DebugSessionState.Closed; existingSession.ClosedAt = DateTime.Now; _model.UpdateSession(existingSession); _activeSessions.Remove(existingSession.SessionId); log.Warn($"已回收设备 {deviceId} 的残留会话 {existingSession.SessionId}(进程重启导致死会话),新建会话替代"); } var session = new DebugSession { DeviceId = deviceId, OperatorId = operatorId, OperatorName = operatorName, EffectiveVersion = effectiveVersion, State = DebugSessionState.ReadOnly }; // 持久化到数据库 _model.CreateSession(session); // 更新内存缓存 _activeSessions[session.SessionId] = session; log.Info($"打开会话: {session.SessionId}, 设备: {deviceId}, 操作员: {operatorName}"); return session; } catch (Exception ex) { log.Error($"打开会话失败: {ex.Message}", ex); throw; } } /// /// 请求进入维护模式(ReadOnly → AcquiringMaintenance) /// 检查:设备是否有生产Reserved /// public bool RequestMaintenance(string sessionId) { try { if (!_activeSessions.TryGetValue(sessionId, out var session)) { // 兜底:会话可能在数据库(如 UI 持有的 sessionID 属于重启前残留)→ 恢复进缓存后继续 session = _model.GetSession(sessionId); if (session == null || session.State == DebugSessionState.Closed) { log.Warn($"会话不存在: {sessionId}"); return false; } _activeSessions[sessionId] = session; log.Warn($"会话 {sessionId} 从数据库恢复进内存缓存后继续维护请求"); } // 验证状态转换 if (session.State != DebugSessionState.ReadOnly) { log.Warn($"会话 {sessionId} 当前状态为 {session.State},无法请求维护模式"); return false; } // 检查设备是否有生产Reserved(与资源预约系统集成) if (_resourceManager != null) { var resourceKey = new ResourceKey(session.DeviceId, "Generic"); // 复用已缓存令牌(残留会话从 DB 恢复后首次进入时同样新建并存入) if (!_sessionOwnerTokens.TryGetValue(sessionId, out var ownerToken)) { ownerToken = new ResourceOwnerToken(OwnerType.Maintenance, session.SessionId); _sessionOwnerTokens[sessionId] = ownerToken; } try { var reservation = _resourceManager.ReserveAsync( ownerToken, resourceKey, CancellationToken.None).GetAwaiter().GetResult(); session.ReservationRef = reservation.ReservationId; } catch (ResourceConflictException) { _sessionOwnerTokens.Remove(sessionId); log.Warn($"设备 {session.DeviceId} 已被生产预约占用,拒绝维护请求"); return false; } } else { // 降级模式:无 ResourceManager 时允许所有请求(向后兼容) log.Warn("ResourceManager 未初始化,跳过资源预约检查"); } // 状态转换 session.State = DebugSessionState.AcquiringMaintenance; // 持久化 _model.UpdateSession(session); log.Info($"会话 {sessionId} 请求维护模式"); return true; } catch (Exception ex) { log.Error($"请求维护模式失败: {ex.Message}", ex); return false; } } /// /// 激活维护模式(AcquiringMaintenance → MaintenanceActive) /// public void ActivateMaintenance(string sessionId) { try { if (!_activeSessions.TryGetValue(sessionId, out var session)) { log.Warn($"会话不存在: {sessionId}"); throw new InvalidOperationException($"会话不存在: {sessionId}"); } // 验证状态转换 if (session.State != DebugSessionState.AcquiringMaintenance) { log.Warn($"会话 {sessionId} 当前状态为 {session.State},无法激活维护模式"); throw new InvalidOperationException($"会话 {sessionId} 当前状态为 {session.State},无法激活维护模式"); } // 状态转换 session.State = DebugSessionState.MaintenanceActive; // 持久化 _model.UpdateSession(session); // 启动维护租约心跳续期(防止 TTL 过期) EnsureRenewHeartbeat(); log.Info($"会话 {sessionId} 激活维护模式"); } catch (Exception ex) { log.Error($"激活维护模式失败: {ex.Message}", ex); throw; } } /// 首次进入维护时启动周期续期定时器(幂等)。 private void EnsureRenewHeartbeat() { if (_renewTimer != null) return; _renewTimer = new System.Threading.Timer(_ => RenewActiveMaintenance(), null, RenewIntervalMs, RenewIntervalMs); log.Info("维护租约心跳已启动"); } /// 周期回调:对所有 MaintenanceActive 会话续期其 arb.db 租约。 private void RenewActiveMaintenance() { var lrm = _resourceManager as Aging.Infrastructure.Core.LayeredResourceManager; if (lrm == null) return; // 非分层实现(无 Coordinator)时降级不续期 try { foreach (var kv in _activeSessions.ToArray()) { var s = kv.Value; if (s.State != DebugSessionState.MaintenanceActive) continue; if (!_sessionOwnerTokens.TryGetValue(s.SessionId, out var token)) continue; if (!lrm.TryGetGrants(token.TokenId, out var grants) || grants == null) continue; foreach (var g in grants) { try { lrm.Coordinator.Renew(g, MaintenanceTtlSeconds); } catch (Exception ex) { log.Warn($"续期设备 {s.DeviceId} 租约失败: {ex.Message}"); } } } } catch (Exception ex) { log.Error($"维护租约心跳异常: {ex.Message}", ex); } } /// 无维护中会话时停止心跳,避免空转。 private void StopRenewHeartbeatIfIdle() { if (_renewTimer == null) return; if (_activeSessions.Values.Any(s => s.State == DebugSessionState.MaintenanceActive)) return; try { _renewTimer.Dispose(); } catch { } _renewTimer = null; log.Info("无维护中会话,维护租约心跳已停止"); } /// /// 关闭会话(MaintenanceActive → Closing → Closed) /// 执行安全状态检查 /// public void CloseSession(string sessionId) { try { if (!_activeSessions.TryGetValue(sessionId, out var session)) { log.Warn($"会话不存在: {sessionId}"); throw new InvalidOperationException($"会话不存在: {sessionId}"); } // 验证状态转换(只有 MaintenanceActive 可以关闭) if (session.State != DebugSessionState.MaintenanceActive) { log.Warn($"会话 {sessionId} 当前状态为 {session.State},无法关闭"); throw new InvalidOperationException($"会话 {sessionId} 当前状态为 {session.State},无法关闭"); } // 状态转换: MaintenanceActive → Closing session.State = DebugSessionState.Closing; _model.UpdateSession(session); log.Debug($"会话 {sessionId} 进入关闭中状态"); // 执行安全状态检查:释放资源预约 if (_resourceManager != null && !string.IsNullOrEmpty(session.ReservationRef)) { try { var resourceKey = new ResourceKey(session.DeviceId, "Generic"); // 必须复用进入维护时的同一令牌实例:LayeredResourceManager 按 TokenId(GUID) 索引租约, // 重新 new 会导致 FindGrant 落空、租约永不释放→过期后 RecoveryRequired 永久阻塞后续维护 if (!_sessionOwnerTokens.TryGetValue(sessionId, out var ownerToken)) { ownerToken = new ResourceOwnerToken(OwnerType.Maintenance, session.SessionId); } _resourceManager.ReleaseReservationAsync( ownerToken, resourceKey, CancellationToken.None).GetAwaiter().GetResult(); // 注意:不删除 _sessionOwnerTokens[sessionId]! // 令牌必须保持稳定(TokenId 不变),否则下次进入维护时 new 出的新 Token // 无法在 LayeredResourceManager._grantsByToken 中找到旧租约 → 释放落空 → 租约永久泄漏 log.Info($"已释放设备 {session.DeviceId} 的资源预约(令牌保留供复用)"); } catch (Exception ex) { log.Error($"释放资源预约失败: {ex.Message}", ex); // 不阻止关闭流程,但记录警告 } } // 状态转换: Closing → Closed session.State = DebugSessionState.Closed; session.ClosedAt = DateTime.Now; _model.UpdateSession(session); // 从缓存中移除 _activeSessions.Remove(sessionId); // 若无维护中会话则停止心跳 StopRenewHeartbeatIfIdle(); log.Info($"会话 {sessionId} 已关闭"); } catch (Exception ex) { log.Error($"关闭会话失败: {ex.Message}", ex); throw; } } /// /// 故障处理(任意 → Faulted) /// public void FaultSession(string sessionId, string errorMessage) { try { if (!_activeSessions.TryGetValue(sessionId, out var session)) { log.Warn($"会话不存在: {sessionId}"); return; } // 记录当前状态 var previousState = session.State; // 状态转换 session.State = DebugSessionState.Faulted; // 持久化 _model.UpdateSession(session); log.Error($"会话 {sessionId} 发生故障,从 {previousState} 转为 Faulted: {errorMessage}"); } catch (Exception ex) { log.Error($"标记故障状态失败: {ex.Message}", ex); } } /// /// 标记需要恢复(任意 → RecoveryRequired) /// public void MarkRecoveryRequired(string sessionId) { try { if (!_activeSessions.TryGetValue(sessionId, out var session)) { log.Warn($"会话不存在: {sessionId}"); return; } // 记录当前状态 var previousState = session.State; // 状态转换 session.State = DebugSessionState.RecoveryRequired; // 持久化 _model.UpdateSession(session); log.Warn($"会话 {sessionId} 标记为需要恢复,从 {previousState} 转为 RecoveryRequired"); } catch (Exception ex) { log.Error($"标记恢复状态失败: {ex.Message}", ex); } } /// /// 验证命令是否允许执行(只有 MaintenanceActive 状态允许写操作) /// public bool ValidateCommand(string sessionId, DebugCommand command) { try { if (!_activeSessions.TryGetValue(sessionId, out var session)) { log.Warn($"会话不存在: {sessionId}"); return false; } // 只有 MaintenanceActive 状态允许写操作 bool allowed = session.State == DebugSessionState.MaintenanceActive; if (!allowed) { log.Warn($"会话 {sessionId} 状态为 {session.State},拒绝执行命令: {command.CommandType}"); } // 增加资源预约验证 if (allowed && _resourceManager != null) { var resourceKey = new ResourceKey(session.DeviceId, "Generic"); allowed = _resourceManager.ValidateOwner( new ResourceOwnerToken(OwnerType.Maintenance, session.SessionId), resourceKey); if (!allowed) { log.Warn($"会话 {sessionId} 资源预约验证失败,拒绝执行命令: {command.CommandType}"); } } return allowed; } catch (Exception ex) { log.Error($"验证命令失败: {ex.Message}", ex); return false; } } /// /// 获取会话状态 /// public DebugSessionState? GetSessionState(string sessionId) { try { if (_activeSessions.TryGetValue(sessionId, out var session)) { return session.State; } // 从数据库查询 var dbSession = _model.GetSession(sessionId); return dbSession?.State; } catch (Exception ex) { log.Error($"获取会话状态失败: {ex.Message}", ex); return null; } } /// /// 获取设备当前会话(如果有) /// public DebugSession? GetActiveSessionByDevice(string deviceId) { try { // 先从内存缓存查找 var cachedSession = _activeSessions.Values .FirstOrDefault(s => s.DeviceId == deviceId && s.State != DebugSessionState.Closed); if (cachedSession != null) { return cachedSession; } // 从数据库查询 var activeSessions = _model.GetActiveSessionsByDevice(deviceId); return activeSessions.FirstOrDefault(); } catch (Exception ex) { log.Error($"获取设备活跃会话失败: {ex.Message}", ex); return null; } } } }