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); auto it = m_ScreenCtrlSessions.find(device_id);
if (it == m_ScreenCtrlSessions.end()) return 1; // 不存在(幂等) if (it == m_ScreenCtrlSessions.end()) return 1; // 不存在(幂等)
if (it->second.sessionId != sessionId) return 2; // token 不匹配 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); if (it->second.subCtx) m_ScreenCtrlContextToDevice.erase(it->second.subCtx);
m_ScreenCtrlSessions.erase(it); m_ScreenCtrlSessions.erase(it);
return 0; return 0;
@@ -3205,7 +3210,7 @@ int CMcpServer::SweepIdleScreenCtrl(time_t idleTimeoutSec) {
std::lock_guard<std::mutex> lk(m_ScreenCtrlMutex); std::lock_guard<std::mutex> lk(m_ScreenCtrlMutex);
for (auto it = m_ScreenCtrlSessions.begin(); it != m_ScreenCtrlSessions.end(); ) { for (auto it = m_ScreenCtrlSessions.begin(); it != m_ScreenCtrlSessions.end(); ) {
ScreenCtrlSession& s = it->second; 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); if (s.subCtx) m_ScreenCtrlContextToDevice.erase(s.subCtx);
toClose.push_back(it->first); toClose.push_back(it->first);
it = m_ScreenCtrlSessions.erase(it); it = m_ScreenCtrlSessions.erase(it);
@@ -3226,8 +3231,49 @@ void CMcpServer::OnScreenControlClosed(context* subCtx) {
std::lock_guard<std::mutex> lk(m_ScreenCtrlMutex); std::lock_guard<std::mutex> lk(m_ScreenCtrlMutex);
auto it = m_ScreenCtrlContextToDevice.find(subCtx); auto it = m_ScreenCtrlContextToDevice.find(subCtx);
if (it == m_ScreenCtrlContextToDevice.end()) return; if (it == m_ScreenCtrlContextToDevice.end()) return;
m_ScreenCtrlSessions.erase(it->second); auto sit = m_ScreenCtrlSessions.find(it->second);
m_ScreenCtrlContextToDevice.erase(it); 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);
}
} }
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////

View File

@@ -175,6 +175,15 @@ public:
bool MarkScreenCtrlReady(uint64_t device_id, const std::string& sessionId, bool MarkScreenCtrlReady(uint64_t device_id, const std::string& sessionId,
context* subCtx, int screenW, int screenH); context* subCtx, int screenW, int screenH);
// 注入前登记(校验 sessionId/started/!busy/!closed。输出 subCtx/screenW/H。
// 注入期间 busy=true保证 close/sweep/断线不与注入并发擦会话(镜像终端 busy 模式,
// 评审发现 #6。返回0=ok1=不存在/不匹配/未就绪/已断(-320022=忙(-32003
int BeginScreenCtrlAction(uint64_t device_id, const std::string& sessionId,
context*& subCtx, int& screenW, int& screenH);
// 注入后复位 busy若注入期间屏幕子连接已断closed擦会话+路由(防泄漏)。
void EndScreenCtrlAction(uint64_t device_id, const std::string& sessionId);
// 关闭控制会话(校验 sessionId擦路由 + 会话)。屏幕子连接的关闭由调用方锁外执行 // 关闭控制会话(校验 sessionId擦路由 + 会话)。屏幕子连接的关闭由调用方锁外执行
// CloseWebRemoteDesktopByClientID。返回 0=已关1=不存在幂等2=sessionId 不匹配。 // CloseWebRemoteDesktopByClientID。返回 0=已关1=不存在幂等2=sessionId 不匹配。
int CloseScreenCtrlSession(uint64_t device_id, const std::string& sessionId); int CloseScreenCtrlSession(uint64_t device_id, const std::string& sessionId);
@@ -253,6 +262,8 @@ private:
std::string sessionId; // 会话 token每次 open 独立随机) std::string sessionId; // 会话 token每次 open 独立随机)
context* subCtx = nullptr; // 屏幕子连接上下文(就绪后填;用于注入) context* subCtx = nullptr; // 屏幕子连接上下文(就绪后填;用于注入)
bool started = false; // false=子连接建立中true=已就绪 bool started = false; // false=子连接建立中true=已就绪
bool busy = false; // 一条注入在飞BeginScreenCtrlAction→End 之间)
bool closed = false; // 注入期间屏幕子连接已断(由注入线程收尾)
time_t lastActiveAt = 0; // idle 回收用(秒) time_t lastActiveAt = 0; // idle 回收用(秒)
int screenW = 0; // 物理捕获分辨率(来自 TOKEN_BITMAPINFO int screenW = 0; // 物理捕获分辨率(来自 TOKEN_BITMAPINFO
int screenH = 0; int screenH = 0;