| | |
| | | // 内存中的活跃会话缓存 |
| | | 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; |
| | |
| | | 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 |
| | |
| | | { |
| | | 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} 从数据库恢复进内存缓存后继续维护请求"); |
| | | } |
| | | |
| | | // 验证状态转换 |
| | |
| | | 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 |
| | | { |
| | |
| | | } |
| | | catch (ResourceConflictException) |
| | | { |
| | | _sessionOwnerTokens.Remove(sessionId); |
| | | log.Warn($"设备 {session.DeviceId} 已被生产预约占用,拒绝维护请求"); |
| | | return false; |
| | | } |
| | |
| | | // 持久化 |
| | | _model.UpdateSession(session); |
| | | |
| | | // 启动维护租约心跳续期(防止 TTL 过期) |
| | | EnsureRenewHeartbeat(); |
| | | |
| | | log.Info($"会话 {sessionId} 激活维护模式"); |
| | | } |
| | | catch (Exception ex) |
| | |
| | | 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> |
| | |
| | | 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) |
| | |
| | | // 从缓存中移除 |
| | | _activeSessions.Remove(sessionId); |
| | | |
| | | // 若无维护中会话则停止心跳 |
| | | StopRenewHeartbeatIfIdle(); |
| | | |
| | | log.Info($"会话 {sessionId} 已关闭"); |
| | | } |
| | | catch (Exception ex) |