|
|
@@ -17,9 +17,10 @@ namespace ivf_tl_Operate.Debug
|
|
|
{
|
|
|
private readonly string _baseUrl;
|
|
|
private readonly HttpClient _http;
|
|
|
+ private readonly bool _ownsHttp;
|
|
|
private string _sessionId;
|
|
|
private Timer _heartbeatTimer;
|
|
|
- private volatile bool _expiredFired;
|
|
|
+ private int _expiredFired; // 0=未触发 1=已触发(Interlocked 闸,保证 OnSessionExpired 只触发一次)
|
|
|
|
|
|
/// <summary>心跳间隔(ms),默认 2.5s(远小于 control 端 10s TTL,容忍偶发卡顿)。</summary>
|
|
|
public int HeartbeatIntervalMs { get; set; } = 2500;
|
|
|
@@ -32,6 +33,7 @@ namespace ivf_tl_Operate.Debug
|
|
|
public DebugSessionClient(string baseUrl, HttpClient http = null)
|
|
|
{
|
|
|
_baseUrl = baseUrl.TrimEnd('/');
|
|
|
+ _ownsHttp = http == null; // 自建的由本类 Dispose;注入的归调用方
|
|
|
_http = http ?? new HttpClient();
|
|
|
}
|
|
|
|
|
|
@@ -41,23 +43,23 @@ namespace ivf_tl_Operate.Debug
|
|
|
var resp = await _http.PostAsync($"{_baseUrl}{path}", content);
|
|
|
string s = await resp.Content.ReadAsStringAsync();
|
|
|
var r = JsonConvert.DeserializeObject<AcquireResult>(s) ?? new AcquireResult { Ok = false, Error = "空响应" };
|
|
|
- if (r.Code == "SESSION_EXPIRED" && !_expiredFired)
|
|
|
+ // Interlocked 闸:并发请求同收 SESSION_EXPIRED 时,回调只触发一次。
|
|
|
+ if (r.Code == "SESSION_EXPIRED" && Interlocked.CompareExchange(ref _expiredFired, 1, 0) == 0)
|
|
|
{
|
|
|
- _expiredFired = true;
|
|
|
StopHeartbeat();
|
|
|
try { OnSessionExpired?.Invoke(); } catch { }
|
|
|
}
|
|
|
return r;
|
|
|
}
|
|
|
|
|
|
- /// <summary>借用某舱。成功则记录 sessionId 并起心跳。返回体含 cultivating/embryoCount(供��认框)。</summary>
|
|
|
+ /// <summary>借用某舱。成功则记录 sessionId 并起心跳。返回体含 cultivating/embryoCount(供确认框)。</summary>
|
|
|
public async Task<AcquireResult> AcquireAsync(int houseSn)
|
|
|
{
|
|
|
var r = await PostAsync("/debug/acquire", new { houseSn });
|
|
|
if (r.Ok && !string.IsNullOrEmpty(r.SessionId))
|
|
|
{
|
|
|
_sessionId = r.SessionId;
|
|
|
- _expiredFired = false;
|
|
|
+ Interlocked.Exchange(ref _expiredFired, 0);
|
|
|
StartHeartbeat();
|
|
|
}
|
|
|
return r;
|
|
|
@@ -70,6 +72,10 @@ namespace ivf_tl_Operate.Debug
|
|
|
/// <summary>归还会话(幂等):停心跳、清 sessionId、通知 control。多次调用不抛。</summary>
|
|
|
public async Task ReleaseAsync()
|
|
|
{
|
|
|
+ // 主动归还:抢先置闸,挡掉 release 与"在途心跳"的竞态——
|
|
|
+ // 在途心跳若晚于 release 到达 control,会话已删会回 SESSION_EXPIRED,
|
|
|
+ // 不置闸就会在用户正常关调试时误弹"会话已失效"框。
|
|
|
+ Interlocked.Exchange(ref _expiredFired, 1);
|
|
|
StopHeartbeat();
|
|
|
var sid = _sessionId;
|
|
|
_sessionId = null;
|
|
|
@@ -93,6 +99,10 @@ namespace ivf_tl_Operate.Debug
|
|
|
_heartbeatTimer = null;
|
|
|
}
|
|
|
|
|
|
- public void Dispose() { StopHeartbeat(); }
|
|
|
+ public void Dispose()
|
|
|
+ {
|
|
|
+ StopHeartbeat();
|
|
|
+ if (_ownsHttp) { try { _http?.Dispose(); } catch { } } // 只释放自建的;注入的归调用方
|
|
|
+ }
|
|
|
}
|
|
|
}
|