diff --git a/windows/src/IME/MetasequoiaIME.cpp b/windows/src/IME/MetasequoiaIME.cpp index 4e4b6f0b..a065a2ca 100644 --- a/windows/src/IME/MetasequoiaIME.cpp +++ b/windows/src/IME/MetasequoiaIME.cpp @@ -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 diff --git a/windows/src/IME/MetasequoiaIME.h b/windows/src/IME/MetasequoiaIME.h index 5db6a39d..e779d7ca 100644 --- a/windows/src/IME/MetasequoiaIME.h +++ b/windows/src/IME/MetasequoiaIME.h @@ -779,6 +779,15 @@ class CMetasequoiaIME : public ITfTextInputProcessorEx, std::atomic _hToTsfWorkerThreadPipe; std::atomic _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 _shouldStopIpcThread; UINT _ipcReconnectDelayMs; UINT _ipcConsecutiveFailures; diff --git a/windows/src/IME/MetasequoiaIME_IpcWorker.cpp b/windows/src/IME/MetasequoiaIME_IpcWorker.cpp index f8cd1d96..5ff13830 100644 --- a/windows/src/IME/MetasequoiaIME_IpcWorker.cpp +++ b/windows/src/IME/MetasequoiaIME_IpcWorker.cpp @@ -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) diff --git a/windows/src/IPC/Ipc.cpp b/windows/src/IPC/Ipc.cpp index 9a527f4a..bb13096f 100644 --- a/windows/src/IPC/Ipc.cpp +++ b/windows/src/IPC/Ipc.cpp @@ -45,6 +45,8 @@ static thread_local std::atomic *boundWorkerCommitReady = nullptr; static thread_local std::atomic *boundAcknowledgedWorkerFocusToken = nullptr; static thread_local std::atomic *boundWorkerPipeHandle = nullptr; static thread_local std::atomic *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(deadline - now)) == WAIT_FAILED) + { + Sleep(1); + } } MarkNamedpipeSessionDirty(); } diff --git a/windows/src/IPC/Ipc.h b/windows/src/IPC/Ipc.h index 399fc026..f097db3a 100644 --- a/windows/src/IPC/Ipc.h +++ b/windows/src/IPC/Ipc.h @@ -25,6 +25,14 @@ void BindNamedpipeFocusState( _In_opt_ std::atomic *localSessionResetToken, _In_opt_ std::atomic *workerCommitReady, _In_opt_ std::atomic *acknowledgedWorkerFocusToken, _In_opt_ std::atomic *workerPipeHandle, _In_opt_ std::atomic *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();