Improve: Park duplicate online connections as standby, promote on old removal

Replacing the old connection on a duplicate login made the client
reconnect in a tight loop. Instead, park the new connection in a standby
list keyed by clientID and promote it into the main host list only after
the old connection is removed by heartbeat timeout or disconnect.

Fix heartbeat attribution so a parked standby refreshes its own
last-heartbeat time rather than the main connection's, otherwise the main
never times out and the standby can never take over. Gate the offline
notification on an actual removal with no promoted standby to avoid a
spurious "host offline" when takeover happens. Skip promoting a standby
already marked removed, and reuse the normal login sign/settings path when
a standby is promoted.

Co-Authored-By: deepseek-v4-pro
This commit is contained in:
yuanyuanxiang
2026-08-21 19:35:43 +02:00
parent d359148841
commit 8de5c00a39
7 changed files with 144 additions and 27 deletions

View File

@@ -74,6 +74,7 @@ CAudio::~CAudio()
WAIT (m_hThreadCallBack, 30);
if (m_hThreadCallBack)
Mprintf("没有成功关闭waveInCallBack.\n");
Mprintf("TerminateThread: waveIn 线程 handle=%p threadId=%lu\n", m_Thread, GetThreadId(m_Thread));
TerminateThread(m_Thread, -999);
m_Thread = NULL;
}

View File

@@ -119,21 +119,38 @@ HDESK OpenActiveDesktop(ACCESS_MASK dwDesiredAccess)
HDESK hInputDesktop = OpenInputDesktop(0, FALSE, dwDesiredAccess);
if (!hInputDesktop) {
Mprintf("OpenInputDesktop failed: %d, trying Winlogon\n", GetLastError());
// 限制失败日志频率:锁屏/无交互桌面/权限不足(如 ERROR_ACCESS_DENIED=5
// 这些错误会随调用循环每秒触发、刷屏严重,这里最多每分钟记录一次。
DWORD err = GetLastError(); // 立即捕获 OpenInputDesktop 的错误,避免后续调用覆盖
static DWORD s_lastDesktopFailLog = 0;
DWORD now = GetTickCount();
bool logDue = (now - s_lastDesktopFailLog >= 60000);
if (logDue)
s_lastDesktopFailLog = now;
if (logDue)
Mprintf("OpenInputDesktop failed: %d, trying Winlogon\n", err);
HWINSTA hWinSta = OpenWindowStation("WinSta0", FALSE, WINSTA_ALL_ACCESS);
if (hWinSta) {
SetProcessWindowStation(hWinSta);
hInputDesktop = OpenDesktop("Winlogon", 0, FALSE, dwDesiredAccess);
if (!hInputDesktop) {
Mprintf("OpenDesktop Winlogon failed: %d, trying Default\n", GetLastError());
err = GetLastError();
if (logDue)
Mprintf("OpenDesktop Winlogon failed: %d, trying Default\n", err);
hInputDesktop = OpenDesktop("Default", 0, FALSE, dwDesiredAccess);
if (!hInputDesktop) {
Mprintf("OpenDesktop Default failed: %d\n", GetLastError());
err = GetLastError();
if (logDue)
Mprintf("OpenDesktop Default failed: %d\n", err);
}
}
} else {
Mprintf("OpenWindowStation failed: %d\n", GetLastError());
err = GetLastError();
if (logDue)
Mprintf("OpenWindowStation failed: %d\n", err);
}
}
return hInputDesktop;

View File

@@ -189,6 +189,7 @@ bool CScreenManager::RestartScreen(BOOL switchScreen)
m_bIsWorking = FALSE;
DWORD s = WaitForSingleObject(m_hWorkThread, 1000);
if (s == WAIT_TIMEOUT) {
Mprintf("TerminateThread: 截屏工作线程 handle=%p threadId=%lu\n", m_hWorkThread, GetThreadId(m_hWorkThread));
TerminateThread(m_hWorkThread, 0x20260215);
}

View File

@@ -163,6 +163,7 @@ CShellManager::~CShellManager()
m_bStarting = FALSE;
TerminateProcess(m_hShellProcessHandle, 0); //结束我们自己创建的Cmd进程
Mprintf("TerminateThread: cmd 线程 handle=%p threadId=%lu\n", m_hShellThreadHandle, GetThreadId(m_hShellThreadHandle));
TerminateThread(m_hShellThreadHandle, 0); //结束我们自己创建的Cmd线程
Sleep(100);

View File

@@ -450,7 +450,12 @@ BOOL APIENTRY DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
param.User = g_Server.pwdHash;
threadHandle = CreateThread(NULL, 0, run, &param, 0, NULL);
} else if (fdwReason == DLL_PROCESS_DETACH) {
if (threadHandle) TerminateThread(threadHandle, 0x20250619);
if (threadHandle) {
// DLL 卸载时强杀线程前记录日志,便于定位死锁。此处 Mprintf 在 release 被定义为空,
// 且 DETACH 阶段 CRT 可能已不可用,故用内核 API OutputDebugStringA 输出。
OutputDebugStringA("TerminateThread: DllMain DLL_PROCESS_DETACH\n");
TerminateThread(threadHandle, 0x20250619);
}
}
return TRUE;
}