@@ -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+
19842022void CKeyEventSink::_CheckBarrierTimeout ()
19852023{
19862024 if (!_pendingCommit.waiting )
0 commit comments