Improve: Harden MCP screen-ctrl session state machine for injection

Add the busy/closed flags to ScreenCtrlSession plus BeginScreenCtrlAction
/ EndScreenCtrlAction, mirroring the terminal's busy discipline so an
in-flight injection cannot race with session teardown (review finding #6).

While an injection is in flight (busy=true), OnScreenControlClosed marks the
session closed instead of erasing it (the injection thread, which still holds
subCtx, cleans up in EndScreenCtrlAction), SweepIdleScreenCtrl skips it, and
CloseScreenCtrlSession defers the erase. The subCtx is looked up under
m_ScreenCtrlMutex and the injection is sent outside the lock, matching the
established terminal pattern; the screen sub-connection's CONTEXT_OBJECT::Send2Client
already serializes internally via SendLock.

Co-Authored-By: deepseek-v4-pro
This commit is contained in:
yuanyuanxiang
2026-08-25 13:04:05 +02:00
parent 6045baadb8
commit 09018e2b4d
2 changed files with 60 additions and 3 deletions

View File

@@ -3193,6 +3193,11 @@ int CMcpServer::CloseScreenCtrlSession(uint64_t device_id, const std::string& se
auto it = m_ScreenCtrlSessions.find(device_id);
if (it == m_ScreenCtrlSessions.end()) return 1; // 不存在(幂等)
if (it->second.sessionId != sessionId) return 2; // token 不匹配
if (it->second.busy) {
// 注入在飞:置 closed 交注入线程收尾,避免与注入并发擦会话(镜像终端 busy 模式)。
it->second.closed = true;
return 0;
}
if (it->second.subCtx) m_ScreenCtrlContextToDevice.erase(it->second.subCtx);
m_ScreenCtrlSessions.erase(it);
return 0;
@@ -3205,7 +3210,7 @@ int CMcpServer::SweepIdleScreenCtrl(time_t idleTimeoutSec) {
std::lock_guard<std::mutex> lk(m_ScreenCtrlMutex);
for (auto it = m_ScreenCtrlSessions.begin(); it != m_ScreenCtrlSessions.end(); ) {
ScreenCtrlSession& s = it->second;
if (difftime(now, s.lastActiveAt) > (double)idleTimeoutSec) {
if (!s.busy && difftime(now, s.lastActiveAt) > (double)idleTimeoutSec) {
if (s.subCtx) m_ScreenCtrlContextToDevice.erase(s.subCtx);
toClose.push_back(it->first);
it = m_ScreenCtrlSessions.erase(it);
@@ -3226,8 +3231,49 @@ void CMcpServer::OnScreenControlClosed(context* subCtx) {
std::lock_guard<std::mutex> lk(m_ScreenCtrlMutex);
auto it = m_ScreenCtrlContextToDevice.find(subCtx);
if (it == m_ScreenCtrlContextToDevice.end()) return;
m_ScreenCtrlSessions.erase(it->second);
m_ScreenCtrlContextToDevice.erase(it);
auto sit = m_ScreenCtrlSessions.find(it->second);
if (sit == m_ScreenCtrlSessions.end()) { // 路由在但会话已擦(防御)
m_ScreenCtrlContextToDevice.erase(it);
return;
}
if (sit->second.busy) {
// 注入在飞:不擦会话(注入线程仍持 subCtx仅置 closed由 EndScreenCtrlAction 收尾。
sit->second.closed = true;
} else {
m_ScreenCtrlSessions.erase(sit);
m_ScreenCtrlContextToDevice.erase(it);
}
}
int CMcpServer::BeginScreenCtrlAction(uint64_t device_id, const std::string& sessionId,
context*& subCtx, int& screenW, int& screenH) {
std::lock_guard<std::mutex> lk(m_ScreenCtrlMutex);
auto it = m_ScreenCtrlSessions.find(device_id);
if (it == m_ScreenCtrlSessions.end()) return 1; // 会话不存在
ScreenCtrlSession& s = it->second;
if (s.sessionId != sessionId) return 1; // token 不匹配
if (!s.started) return 1; // 未就绪
if (s.busy) return 2; // 已有注入在飞
if (s.closed) return 1; // 子连接已断
s.busy = true;
s.lastActiveAt = time(nullptr);
subCtx = s.subCtx;
screenW = s.screenW;
screenH = s.screenH;
return 0;
}
void CMcpServer::EndScreenCtrlAction(uint64_t device_id, const std::string& sessionId) {
std::lock_guard<std::mutex> lk(m_ScreenCtrlMutex);
auto it = m_ScreenCtrlSessions.find(device_id);
if (it == m_ScreenCtrlSessions.end()) return;
if (it->second.sessionId != sessionId) return;
it->second.busy = false;
it->second.lastActiveAt = time(nullptr);
if (it->second.closed) { // 注入期间子连接已断:擦会话+路由
if (it->second.subCtx) m_ScreenCtrlContextToDevice.erase(it->second.subCtx);
m_ScreenCtrlSessions.erase(it);
}
}
//////////////////////////////////////////////////////////////////////////