wenxiangchao
4 天以前 559890c482f5973f06aafdfec9550fee5ba3c7f2
Plugin.DeviceDebug/Model/DebugSessionManager.cs
@@ -19,6 +19,20 @@
        // 内存中的活跃会话缓存
        private readonly Dictionary<string, DebugSession> _activeSessions = new();
        /// <summary>
        /// 维护租约心跳:与 LayeredResourceManager.DefaultTtlSeconds(300s) 对齐,
        /// 每 120s 续期一次,防止长时间维护会话的 arb.db 租约过期进入 RecoveryRequired 后禁止 I/O。
        /// </summary>
        private System.Threading.Timer _renewTimer;
        private const int MaintenanceTtlSeconds = 300;
        private const int RenewIntervalMs = 120_000;
        /// <summary>
        /// 会话→资源令牌缓存:LayeredResourceManager 的租约按 TokenId 索引,
        /// 而 ResourceOwnerToken.TokenId 每次 new 都是新 GUID,必须复用同一实例才能释放到租约
        /// </summary>
        private readonly Dictionary<string, ResourceOwnerToken> _sessionOwnerTokens = new();
        public DebugSessionManager(DeviceDebugModel model, IResourceManager? resourceManager = null)
        {
            _model = model;
@@ -36,8 +50,22 @@
                var existingSession = GetActiveSessionByDevice(deviceId);
                if (existingSession != null)
                {
                    log.Warn($"设备 {deviceId} 已有活跃会话: {existingSession.SessionId}");
                    return existingSession;
                    // 进程内存中存在 = 本会话真活跃,直接复用
                    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
@@ -75,8 +103,15 @@
            {
                if (!_activeSessions.TryGetValue(sessionId, out var session))
                {
                    log.Warn($"会话不存在: {sessionId}");
                    return false;
                    // 兜底:会话可能在数据库(如 UI 持有的 sessionID 属于重启前残留)→ 恢复进缓存后继续
                    session = _model.GetSession(sessionId);
                    if (session == null || session.State == DebugSessionState.Closed)
                    {
                        log.Warn($"会话不存在: {sessionId}");
                        return false;
                    }
                    _activeSessions[sessionId] = session;
                    log.Warn($"会话 {sessionId} 从数据库恢复进内存缓存后继续维护请求");
                }
                // 验证状态转换
@@ -90,7 +125,12 @@
                if (_resourceManager != null)
                {
                    var resourceKey = new ResourceKey(session.DeviceId, "Generic");
                    var ownerToken = new ResourceOwnerToken(OwnerType.Maintenance, session.SessionId);
                    // 复用已缓存令牌(残留会话从 DB 恢复后首次进入时同样新建并存入)
                    if (!_sessionOwnerTokens.TryGetValue(sessionId, out var ownerToken))
                    {
                        ownerToken = new ResourceOwnerToken(OwnerType.Maintenance, session.SessionId);
                        _sessionOwnerTokens[sessionId] = ownerToken;
                    }
                    try
                    {
@@ -100,6 +140,7 @@
                    }
                    catch (ResourceConflictException)
                    {
                        _sessionOwnerTokens.Remove(sessionId);
                        log.Warn($"设备 {session.DeviceId} 已被生产预约占用,拒绝维护请求");
                        return false;
                    }
@@ -152,6 +193,9 @@
                // 持久化
                _model.UpdateSession(session);
                // 启动维护租约心跳续期(防止 TTL 过期)
                EnsureRenewHeartbeat();
                log.Info($"会话 {sessionId} 激活维护模式");
            }
            catch (Exception ex)
@@ -159,6 +203,48 @@
                log.Error($"激活维护模式失败: {ex.Message}", ex);
                throw;
            }
        }
        /// <summary>首次进入维护时启动周期续期定时器(幂等)。</summary>
        private void EnsureRenewHeartbeat()
        {
            if (_renewTimer != null) return;
            _renewTimer = new System.Threading.Timer(_ => RenewActiveMaintenance(),
                null, RenewIntervalMs, RenewIntervalMs);
            log.Info("维护租约心跳已启动");
        }
        /// <summary>周期回调:对所有 MaintenanceActive 会话续期其 arb.db 租约。</summary>
        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); }
        }
        /// <summary>无维护中会话时停止心跳,避免空转。</summary>
        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("无维护中会话,维护租约心跳已停止");
        }
        /// <summary>
@@ -193,11 +279,17 @@
                    try
                    {
                        var resourceKey = new ResourceKey(session.DeviceId, "Generic");
                        var ownerToken = new ResourceOwnerToken(OwnerType.Maintenance, session.SessionId);
                        // 必须复用进入维护时的同一令牌实例: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.Remove(sessionId);
                        log.Info($"已释放设备 {session.DeviceId} 的资源预约");
                    }
                    catch (Exception ex)
@@ -215,6 +307,9 @@
                // 从缓存中移除
                _activeSessions.Remove(sessionId);
                // 若无维护中会话则停止心跳
                StopRenewHeartbeatIfIdle();
                log.Info($"会话 {sessionId} 已关闭");
            }
            catch (Exception ex)