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