Skip to content

Commit e21a998

Browse files
committed
fix(tsf): resync 自愈加时限与失败上限, 防止 IPC 持续失败导致按键永久被吃
_needsCompositionResync 原本只能由"下一次按键再走 IPC 且成功"清除, 当 Go 服务长时间不可用时, ENTER/ESC/Ctrl+Alt 等键会持续被吃且每次重试都失败, 形成永久卡死。新增 3 秒自愈窗口 + 连续失败 3 次降级 passthrough, 最坏 阻塞窗口收敛到 ~5s, 服务恢复后任一成功响应立即清零计数。
1 parent 4f855c7 commit e21a998

2 files changed

Lines changed: 52 additions & 6 deletions

File tree

wind_tsf/include/KeyEventSink.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ class CKeyEventSink : public ITfKeyEventSink,
9191
// 用于 Excel/WPS cell-select(按数字直通) → cell-edit(按标点) 这种焦点切换
9292
// 场景的数字后智能标点判断。残留由按键事件路径(_SendKeyToService 非智能
9393
// 标点目标键清零)和光标 Y 跨行检测兜底,不应在 IME 会话状态重置时一起清。
94-
void ResetComposingState() { _isComposing = FALSE; _hasCandidates = FALSE; _needsCompositionResync = FALSE; _skipKeyCount = 0; _pendingPairAction = {}; _englishPairEngine.Clear(); }
94+
void ResetComposingState() { _isComposing = FALSE; _hasCandidates = FALSE; _needsCompositionResync = FALSE; _resyncDeadline = 0; _resyncFailStreak = 0; _skipKeyCount = 0; _pendingPairAction = {}; _englishPairEngine.Clear(); }
9595

9696
// Flush pending English pass-through stats before focus/mode teardown.
9797
void FlushEnglishStats();
@@ -130,6 +130,14 @@ class CKeyEventSink : public ITfKeyEventSink,
130130
// 下一次按键前提下视作"有会话",让 ENTER/ESC 也能发给 Go 走重握手;
131131
// 任何一次成功 ReceiveResponse 之后清旗,状态由响应处理路径自然重建。
132132
BOOL _needsCompositionResync;
133+
// resync 自愈窗口:deadline 到期或连续失败超限后自动放弃,避免 Go/IPC 长时间不可用
134+
// 时把 ENTER/ESC/Ctrl+Alt 等键永久吃掉。失败 streak 在响应成功后清零。
135+
DWORD _resyncDeadline; // GetTickCount() 时间戳,0 表示无 deadline
136+
int _resyncFailStreak; // 连续 IPC 失败次数,超过 RESYNC_MAX_RETRIES 强制降级 passthrough
137+
static constexpr DWORD RESYNC_WINDOW_MS = 3000;
138+
static constexpr int RESYNC_MAX_RETRIES = 3;
139+
BOOL _IsResyncActive(); // 读旗+过期检查;过期会自动清旗
140+
133141
WCHAR _lastPassthroughDigit; // Last digit key that passed through (for smart punct fallback in apps where TSF can't read text)
134142
uint32_t _pendingKeyUpKey; // Key code of pending KeyUp toggle key
135143
uint32_t _pendingKeyUpModifiers; // Modifiers when KeyDown was pressed

wind_tsf/src/KeyEventSink.cpp

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ CKeyEventSink::CKeyEventSink(CTextService* pTextService)
8989
, _isComposing(FALSE)
9090
, _hasCandidates(FALSE)
9191
, _needsCompositionResync(FALSE)
92+
, _resyncDeadline(0)
93+
, _resyncFailStreak(0)
9294
, _lastPassthroughDigit(0)
9395
, _pendingKeyUpKey(0)
9496
, _pendingKeyUpModifiers(0)
@@ -269,7 +271,7 @@ STDAPI CKeyEventSink::OnTestKeyDown(ITfContext* pContext, WPARAM wParam, LPARAM
269271
BOOL chineseMode = _pTextService->IsChineseMode();
270272
// resync 期 (上次 IPC 失败后) 视作有会话, 让 ENTER/ESC/Backspace 等 session 热键
271273
// 也走 Go 重握手, 由 Go 权威响应清旗 + 重建状态。
272-
BOOL hasSession = _pTextService->HasActiveComposition() || _hasCandidates || _needsCompositionResync;
274+
BOOL hasSession = _pTextService->HasActiveComposition() || _hasCandidates || _IsResyncActive();
273275
if (chineseMode && hasSession)
274276
{
275277
WIND_LOG_DEBUG_FMT(L"KeyDown session hotkey matched: vk=0x%02X, hash=0x%08X\n",
@@ -389,7 +391,7 @@ STDAPI CKeyEventSink::OnTestKeyDown(ITfContext* pContext, WPARAM wParam, LPARAM
389391
// Also check _hasCandidates for cases where InlinePreedit is disabled
390392
// (Go sends UpdateComposition with empty text, _hasCandidates is TRUE but HasActiveComposition is FALSE)
391393
// _needsCompositionResync: 上次 IPC 失败后强行视作有会话, 让 ENTER/ESC 也能发给 Go 重握手。
392-
BOOL hasInputSession = hasComposition || _hasCandidates || _needsCompositionResync;
394+
BOOL hasInputSession = hasComposition || _hasCandidates || _IsResyncActive();
393395

394396
// English auto-pair: intercept bracket keys in English mode
395397
if (!isChineseMode && _englishPairEngine.IsEnabled())
@@ -847,7 +849,7 @@ STDAPI CKeyEventSink::OnKeyDown(ITfContext* pContext, WPARAM wParam, LPARAM lPar
847849
BOOL hasComposition = _pTextService->HasActiveComposition();
848850
// Also check _hasCandidates for cases where InlinePreedit is disabled.
849851
// _needsCompositionResync: 上次 IPC 失败后强行视作有会话, 让 ENTER/ESC 也能发给 Go 重握手。
850-
BOOL hasInputSession = hasComposition || _hasCandidates || _needsCompositionResync;
852+
BOOL hasInputSession = hasComposition || _hasCandidates || _IsResyncActive();
851853

852854
// Track whether this is a Ctrl/Alt combo that needs cleanup-then-passthrough
853855
BOOL isCtrlAltCleanup = FALSE;
@@ -1531,12 +1533,30 @@ BOOL CKeyEventSink::_HandleServiceResponse()
15311533
_isComposing = FALSE;
15321534
_hasCandidates = FALSE;
15331535
_pTextService->NotifyCandidatesVisibilityChanged(FALSE);
1534-
_needsCompositionResync = TRUE;
1536+
1537+
// resync 自愈:累计连续失败,到上限就放弃自愈、走 passthrough,
1538+
// 避免 Go 服务长时间挂掉时 ENTER/ESC/Ctrl+Alt 被永久吃。
1539+
// 任一次响应成功 (下方 _resyncFailStreak=0) 即清零计数。
1540+
_resyncFailStreak++;
1541+
if (_resyncFailStreak >= RESYNC_MAX_RETRIES)
1542+
{
1543+
WIND_LOG_WARN_FMT(L"Resync fail streak=%d reached limit, dropping to passthrough mode",
1544+
_resyncFailStreak);
1545+
_needsCompositionResync = FALSE;
1546+
_resyncDeadline = 0;
1547+
}
1548+
else
1549+
{
1550+
_needsCompositionResync = TRUE;
1551+
_resyncDeadline = GetTickCount() + RESYNC_WINDOW_MS;
1552+
}
15351553
return TRUE; // Default to eating the key on error
15361554
}
15371555

1538-
// 响应成功 → 状态由下方 switch 各分支按权威重建, 清 resync 旗。
1556+
// 响应成功 → 状态由下方 switch 各分支按权威重建, 清 resync 旗 + 失败计数
15391557
_needsCompositionResync = FALSE;
1558+
_resyncDeadline = 0;
1559+
_resyncFailStreak = 0;
15401560

15411561
QueryPerformanceCounter(&midTime);
15421562
int ipcMs = (int)((midTime.QuadPart - startTime.QuadPart) * 1000 / freq.QuadPart);
@@ -1981,6 +2001,24 @@ void CKeyEventSink::_HandleCommitResult(uint16_t barrierSeq, const std::wstring&
19812001
}
19822002
}
19832003

2004+
// 读 resync 旗 + 过期检查。deadline 到期立即清旗,保证只读处不需要关心时间窗口。
2005+
// 注意:_resyncFailStreak 在此不清零——streak 仅由"响应成功"清零,否则失败计数被
2006+
// 时间衰减抹掉就失去了"连续失败 → 降级"的语义。
2007+
BOOL CKeyEventSink::_IsResyncActive()
2008+
{
2009+
if (!_needsCompositionResync)
2010+
return FALSE;
2011+
if (GetTickCount() >= _resyncDeadline)
2012+
{
2013+
WIND_LOG_DEBUG_FMT(L"Resync window expired (streak=%d), auto-clearing flag",
2014+
_resyncFailStreak);
2015+
_needsCompositionResync = FALSE;
2016+
_resyncDeadline = 0;
2017+
return FALSE;
2018+
}
2019+
return TRUE;
2020+
}
2021+
19842022
void CKeyEventSink::_CheckBarrierTimeout()
19852023
{
19862024
if (!_pendingCommit.waiting)

0 commit comments

Comments
 (0)