Merge pull request #497 from fanlusky/perf/tsf-focus-wakeup

perf(windows): 焦点激活与 worker 管道发布改为事件唤醒
This commit is contained in:
Lu Fan(SonnyCalcr) authored and GitHub committed 2026-09-27 18:16:12 +08:00
commit 36bde9bbdb
5 files changed
+94 -3

No files matched your search

+27
View File
@@ -263,6 +263,8 @@ CMetasequoiaIME::CMetasequoiaIME()
_hToTsfWorkerThreadPipe.store(nullptr);
_workerPipeGeneration.store(0);
_ipcStopEvent = nullptr;
_workerAckEvent = nullptr;
_workerPipePublishedEvent = nullptr;
_shouldStopIpcThread = false;
_ipcReconnectDelayMs = CONNECT_NAMEDPIPE_RETRY_INTERVAL_MS;
_ipcConsecutiveFailures = 0;
@@ -369,6 +371,8 @@ CMetasequoiaIME::~CMetasequoiaIME()
CloseHandle(_ipcStopEvent);
_ipcStopEvent = nullptr;
}
// The owner-aware unbind above already cleared the TLS copies.
_CloseIpcWakeEvents();
}
uint64_t CMetasequoiaIME::_CaptureFocusSessionToken() const
@@ -796,6 +800,10 @@ STDAPI CMetasequoiaIME::ActivateEx(ITfThreadMgr *pThreadMgr, TfClientId tfClient
{
goto ExitError;
}
// Optional: if either fails the waits fall back to the original polling.
_workerAckEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
_workerPipePublishedEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
BindNamedpipeWakeEvents(this, _workerAckEvent, _workerPipePublishedEvent);
_shouldStopIpcThread.store(false);
_pIpcThread = new std::thread(IpcWorkerThread, this);
@@ -927,6 +935,11 @@ STDAPI CMetasequoiaIME::Deactivate()
CloseHandle(_ipcStopEvent);
_ipcStopEvent = nullptr;
}
// _EndComposition below can still reach the focus-activation wait on this
// thread; unbind first so it falls back to polling instead of waiting on a
// closed (or recycled) handle.
BindNamedpipeWakeEvents(this, nullptr, nullptr);
_CloseIpcWakeEvents();
// Stop callbacks from the previously focused top context before tearing
// down the engine and thread manager. Context stack changes can otherwise
// re-enter a half-deactivated service.
@@ -1028,6 +1041,20 @@ STDAPI CMetasequoiaIME::Deactivate()
return S_OK;
}
void CMetasequoiaIME::_CloseIpcWakeEvents()
{
if (_workerAckEvent)
{
CloseHandle(_workerAckEvent);
_workerAckEvent = nullptr;
}
if (_workerPipePublishedEvent)
{
CloseHandle(_workerPipePublishedEvent);
_workerPipePublishedEvent = nullptr;
}
}
//+---------------------------------------------------------------------------
//
// _WakeServerIfNeeded
+9
View File
@@ -779,6 +779,15 @@ class CMetasequoiaIME : public ITfTextInputProcessorEx,
std::atomic<HANDLE> _hToTsfWorkerThreadPipe;
std::atomic<UINT> _workerPipeGeneration;
HANDLE _ipcStopEvent;
// Auto-reset wake-ups that replace polling (see BindNamedpipeWakeEvents): the
// worker signals _workerAckEvent after recording the focus acknowledgement,
// and the UI thread signals _workerPipePublishedEvent when it publishes a
// worker pipe. Created before the IPC thread starts, closed after it joins.
HANDLE _workerAckEvent;
HANDLE _workerPipePublishedEvent;
// Closes both wake-up events. Only after the IPC thread has joined and the
// TLS bindings no longer point at them.
void _CloseIpcWakeEvents();
std::atomic<bool> _shouldStopIpcThread;
UINT _ipcReconnectDelayMs;
UINT _ipcConsecutiveFailures;
+11 -1
View File
@@ -281,7 +281,12 @@ void CMetasequoiaIME::IpcWorkerThread(CMetasequoiaIME *pIME)
const UINT workerGeneration = pIME->_workerPipeGeneration.load(std::memory_order_acquire);
if (!workerPipe || workerPipe == INVALID_HANDLE_VALUE)
{
if (pIME->_ipcStopEvent && WaitForSingleObject(pIME->_ipcStopEvent, 50) == WAIT_OBJECT_0)
// Wake as soon as the UI thread publishes a pipe instead of on the
// next 50 ms tick; the timeout stays as a fallback. Stop is index 0
// so it wins when both are signalled.
HANDLE waitHandles[] = {pIME->_ipcStopEvent, pIME->_workerPipePublishedEvent};
const DWORD waitCount = pIME->_workerPipePublishedEvent ? 2 : 1;
if (pIME->_ipcStopEvent && WaitForMultipleObjects(waitCount, waitHandles, FALSE, 50) == WAIT_OBJECT_0)
{
return;
}
@@ -515,6 +520,11 @@ void CMetasequoiaIME::IpcWorkerThread(CMetasequoiaIME *pIME)
{
pIME->_acknowledgedWorkerFocusToken.store(focusToken, std::memory_order_release);
pIME->_workerCommitReady.store(true, std::memory_order_release);
// Wake the UI thread waiting in EnsureNamedpipeFocusSessionActivated.
if (pIME->_workerAckEvent)
{
SetEvent(pIME->_workerAckEvent);
}
}
}
else if (buf.msg_type == Global::DataToTsfWorkerThreadMsgType::CommitCurCandidate)
+39 -2
View File
@@ -45,6 +45,8 @@ static thread_local std::atomic<bool> *boundWorkerCommitReady = nullptr;
static thread_local std::atomic<uint64_t> *boundAcknowledgedWorkerFocusToken = nullptr;
static thread_local std::atomic<HANDLE> *boundWorkerPipeHandle = nullptr;
static thread_local std::atomic<UINT> *boundWorkerPipeGeneration = nullptr;
static thread_local HANDLE boundWorkerAckEvent = nullptr;
static thread_local HANDLE boundWorkerPipePublishedEvent = nullptr;
/* Data size transfered from Server process */
static const int ServerDtPipeDataSize = 512;
@@ -243,6 +245,13 @@ void PublishWorkerPipeHandleIfChanged(HANDLE workerPipe)
boundWorkerPipeGeneration->store(NextAtomicNonzeroToken(nextWorkerPipeGeneration), std::memory_order_release);
}
boundWorkerPipeHandle->store(workerPipe, std::memory_order_release);
// The IPC worker thread sleeps while it has no pipe; without this it only
// noticed the new handle on its next 50 ms timeout, and the FocusSessionReady
// echo the first key waits for sat unread in the pipe until then.
if (workerPipe && boundWorkerPipePublishedEvent)
{
SetEvent(boundWorkerPipePublishedEvent);
}
}
inline bool IsValidPipeHandle(HANDLE hPipeHandle)
@@ -620,6 +629,16 @@ void BindNamedpipeFocusState(const void *owner, bool *focusResetPending, bool *a
boundWorkerPipeGeneration = workerPipeGeneration;
}
void BindNamedpipeWakeEvents(const void *owner, HANDLE workerAckEvent, HANDLE workerPipePublishedEvent)
{
if (owner == nullptr || boundFocusStateOwner != owner)
{
return;
}
boundWorkerAckEvent = workerAckEvent;
boundWorkerPipePublishedEvent = workerPipePublishedEvent;
}
void UnbindNamedpipeFocusState(const void *owner)
{
if (owner == nullptr || boundFocusStateOwner != owner)
@@ -636,6 +655,8 @@ void UnbindNamedpipeFocusState(const void *owner)
boundAcknowledgedWorkerFocusToken = nullptr;
boundWorkerPipeHandle = nullptr;
boundWorkerPipeGeneration = nullptr;
boundWorkerAckEvent = nullptr;
boundWorkerPipePublishedEvent = nullptr;
}
bool IsNamedpipeFocusStateOwner(const void *owner)
@@ -1771,7 +1792,7 @@ bool EnsureNamedpipeFocusSessionActivated()
if (boundWorkerCommitReady)
{
const ULONGLONG deadline = GetTickCount64() + 150;
while (GetTickCount64() < deadline)
for (;;)
{
if (IsLocalSessionResetPending())
{
@@ -1781,7 +1802,23 @@ bool EnsureNamedpipeFocusSessionActivated()
{
return true;
}
Sleep(1);
const ULONGLONG now = GetTickCount64();
if (now >= deadline)
{
break;
}
// Block until the IPC worker records the acknowledgement. Sleep(1)
// rounds up to a timer tick (~15.6 ms by default), so every focus
// change paid a tick even though the echo arrives in well under a
// millisecond. The check above runs after every wake-up and the
// worker signals after its release stores, so a wake-up cannot be
// lost; a stale signal only costs one more pass. Like Sleep, this
// wait does not pump messages, so no TSF callback can re-enter here.
if (!boundWorkerAckEvent ||
WaitForSingleObject(boundWorkerAckEvent, static_cast<DWORD>(deadline - now)) == WAIT_FAILED)
{
Sleep(1);
}
}
MarkNamedpipeSessionDirty();
}
+8
View File
@@ -25,6 +25,14 @@ void BindNamedpipeFocusState(
_In_opt_ std::atomic<UINT> *localSessionResetToken, _In_opt_ std::atomic<bool> *workerCommitReady,
_In_opt_ std::atomic<uint64_t> *acknowledgedWorkerFocusToken, _In_opt_ std::atomic<HANDLE> *workerPipeHandle,
_In_opt_ std::atomic<UINT> *workerPipeGeneration);
// Wake-up events owned by the bound service, so the UI thread and the IPC worker
// thread block instead of polling. workerAckEvent is signalled by the worker when
// it records the expected focus token; workerPipePublishedEvent is signalled when
// a worker pipe handle is published. Either may be null, which falls back to the
// original polling. Only the current owner may bind; UnbindNamedpipeFocusState
// clears them, and the owner must rebind null before closing the handles.
void BindNamedpipeWakeEvents(_In_ const void *owner, _In_opt_ HANDLE workerAckEvent,
_In_opt_ HANDLE workerPipePublishedEvent);
void UnbindNamedpipeFocusState(_In_ const void *owner);
bool IsNamedpipeFocusStateOwner(_In_ const void *owner);
UINT BeginNamedpipeLocalSessionReset();