|
|
@@ -0,0 +1,65 @@
|
|
|
+using System;
|
|
|
+using System.Collections.Concurrent;
|
|
|
+using IvfTl.Hardware;
|
|
|
+namespace IvfTl.ControlHost.Debug
|
|
|
+{
|
|
|
+ /// <summary>
|
|
|
+ /// control 端调试会话后端:会话表 + 借用/归还 + 心跳续约 + 超时自动回收 + 命令分发。
|
|
|
+ /// 安全地基(spec §5):绝不指望 operate 主动还,SweepExpired 超时回收兜底。
|
|
|
+ /// </summary>
|
|
|
+ public sealed class DebugSessionManager
|
|
|
+ {
|
|
|
+ private readonly Func<int, IHouseGate> _gateOf;
|
|
|
+ private readonly Func<DateTime> _clock;
|
|
|
+ private readonly int _ttlMs;
|
|
|
+ private readonly Action<string> _log;
|
|
|
+ private readonly ConcurrentDictionary<string, DebugSession> _sessions = new ConcurrentDictionary<string, DebugSession>();
|
|
|
+ public DebugSessionManager(Func<int, IHouseGate> gateOf, Func<DateTime> clock, int ttlMs, Action<string> log)
|
|
|
+ {
|
|
|
+ _gateOf = gateOf; _clock = clock; _ttlMs = ttlMs; _log = log ?? (_ => { });
|
|
|
+ }
|
|
|
+ public DebugCommandResult Acquire(int houseSn)
|
|
|
+ {
|
|
|
+ var gate = _gateOf(houseSn);
|
|
|
+ if (gate == null) return DebugCommandResult.Fail("NO_HANDLE", $"舱{houseSn}无闸门");
|
|
|
+ var lease = gate.Acquire(HardwareUser.OperateDebug);
|
|
|
+ if (lease == null) return DebugCommandResult.Fail("BUSY", $"舱{houseSn}被占用,借用超时");
|
|
|
+ string sid = Guid.NewGuid().ToString("N");
|
|
|
+ _sessions[sid] = new DebugSession(sid, houseSn, lease, _clock());
|
|
|
+ _log($"[debug] acquire 舱{houseSn} sid={sid}");
|
|
|
+ return DebugCommandResult.Okay(sid);
|
|
|
+ }
|
|
|
+ public DebugCommandResult Heartbeat(string sid)
|
|
|
+ {
|
|
|
+ if (sid != null && _sessions.TryGetValue(sid, out var s)) { s.LastSeen = _clock(); return DebugCommandResult.Okay(); }
|
|
|
+ return DebugCommandResult.Fail("SESSION_EXPIRED", "会话不存在或已过期");
|
|
|
+ }
|
|
|
+ public DebugCommandResult Release(string sid)
|
|
|
+ {
|
|
|
+ if (sid != null && _sessions.TryRemove(sid, out var s))
|
|
|
+ {
|
|
|
+ try { s.Lease.Dispose(); } catch { }
|
|
|
+ _log($"[debug] release sid={sid} 舱{s.HouseSn}");
|
|
|
+ }
|
|
|
+ return DebugCommandResult.Okay();
|
|
|
+ }
|
|
|
+ /// <summary>超时回收:LastSeen + ttl < now 的会话自动归还(spec §5.1)。</summary>
|
|
|
+ public int SweepExpired()
|
|
|
+ {
|
|
|
+ int n = 0; var now = _clock();
|
|
|
+ foreach (var kv in _sessions)
|
|
|
+ {
|
|
|
+ if ((now - kv.Value.LastSeen).TotalMilliseconds > _ttlMs)
|
|
|
+ {
|
|
|
+ if (_sessions.TryRemove(kv.Key, out var s))
|
|
|
+ {
|
|
|
+ try { s.Lease.Dispose(); } catch { }
|
|
|
+ _log($"[debug] 会话超时自动回收 sid={kv.Key} 舱{s.HouseSn}");
|
|
|
+ n++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return n;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|