Merge pull request #497 from fanlusky/perf/tsf-focus-wakeup
perf(windows): 焦点激活与 worker 管道发布改为事件唤醒
This commit is contained in:
5 files changed
+94
-3
No files matched your search
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in new issue
Block a user