mirror of
https://github.com/wxWidgets/wxWidgets.git
synced 2026-09-24 07:24:31 +08:00
sckipc: fix the two-request deadlock by pumping while waiting on the main thread
A Request() on the main thread concurrent with a worker-thread Request() on the same connection deadlocked: the worker holds m_cs_awaiting_reply while parked in RunOnMainThread() waiting for the main thread, and the main thread blocked acquiring m_cs_awaiting_reply in SendAndGetReply_MainThread(), so it stopped pumping the event loop the worker needed, and neither could progress. Fix: the main-thread path no longer blocks on m_cs_awaiting_reply. It spins on TryEnter(), and between attempts pumps the active event loop (ProcessPendingEvents() to run the worker's RunOnMainThread() jobs, plus a short DispatchTimeout() to service its reply). The worker can then finish and release the lock while the main thread keeps the loop alive. The pump happens before either lock is taken, so re-entrant OnSocketInput() cannot collide with a lock we hold; the existing read loop after acquisition is unchanged. A CritSectLeaver RAII guard releases the manually-entered section on all return paths. If there is no active event loop (so nothing could advance a worker), it falls back to a plain blocking Enter(). With this, a main-thread Request() concurrent with a worker-thread Request() completes instead of hanging. The IPC::ConcurrentMainAndWorkerRequest test added with the test suite that follows reproduces the original deadlock; with the fix in place, full [ipc] is green on a plain build and under TSan (no data races, no hang).
This commit is contained in:
+45
-1
@@ -36,8 +36,11 @@
|
||||
#include "wx/log.h"
|
||||
#include "wx/event.h"
|
||||
#include "wx/module.h"
|
||||
#include "wx/app.h"
|
||||
#endif
|
||||
|
||||
#include "wx/evtloop.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
@@ -1700,6 +1703,25 @@ bool wxTCPEventHandler::SendAndGetReply(wxIPCMessageBase& send_msg,
|
||||
return SendAndGetReply_WorkerThread(send_msg, expected_code, return_msgptr);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// RAII guard that leaves an already-entered critical section on destruction
|
||||
// (the inverse of wxCRIT_SECT_LOCKER, which enters on construction). Used when
|
||||
// the section was acquired with TryEnter() in a custom wait loop.
|
||||
class CritSectLeaver
|
||||
{
|
||||
public:
|
||||
explicit CritSectLeaver(wxCriticalSection& cs) : m_cs(cs) {}
|
||||
~CritSectLeaver() { m_cs.Leave(); }
|
||||
|
||||
private:
|
||||
wxCriticalSection& m_cs;
|
||||
wxDECLARE_NO_COPY_CLASS(CritSectLeaver);
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// Take over socket processing from OnSocketInput until we find the
|
||||
// return message.
|
||||
bool wxTCPEventHandler::SendAndGetReply_MainThread(wxIPCMessageBase& send_msg,
|
||||
@@ -1707,7 +1729,29 @@ bool wxTCPEventHandler::SendAndGetReply_MainThread(wxIPCMessageBase& send_msg,
|
||||
wxSocketBase* socket,
|
||||
wxIPCMessageBase** return_msgptr)
|
||||
{
|
||||
wxCRIT_SECT_LOCKER(find_message_lock, m_cs_awaiting_reply);
|
||||
// A worker thread may have triggered m_cs_awaiting_reply while parked in
|
||||
// RunOnMainThread(), waiting for *this* (the main) thread to run its
|
||||
// marshalled socket I/O. Blocking on the lock here would stop us pumping the
|
||||
// event loop and deadlock. So acquire it without blocking the loop: spin on
|
||||
// TryEnter(), pumping pending events (the worker's RunOnMainThread() jobs)
|
||||
// and socket I/O (its reply) between attempts so the worker can finish and
|
||||
// release the lock.
|
||||
while ( !m_cs_awaiting_reply.TryEnter() )
|
||||
{
|
||||
wxEventLoopBase* const loop = wxEventLoopBase::GetActive();
|
||||
if ( !loop )
|
||||
{
|
||||
// No event loop to pump, so no worker can be waiting on us; the only
|
||||
// safe thing left is to block until the lock is free.
|
||||
m_cs_awaiting_reply.Enter();
|
||||
break;
|
||||
}
|
||||
|
||||
if ( wxTheApp )
|
||||
wxTheApp->ProcessPendingEvents();
|
||||
loop->DispatchTimeout(1);
|
||||
}
|
||||
CritSectLeaver find_message_lock(m_cs_awaiting_reply);
|
||||
|
||||
wxCRIT_SECT_LOCKER(socket_processing_lock, m_cs_socket_processing);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user