From a42d4b1b0d934c6c63c492f951d275c15afa78d4 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 17 Aug 2026 10:58:02 -0700 Subject: [PATCH 01/12] Add a minipal wait abstraction for the debugger Introduce typed RAII event and process wait handles, migrate debugger waits off PAL multi-wait APIs, and add cross-platform wait tests. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/coreclr/debug/di/dbgtransportmanager.cpp | 124 +- src/coreclr/debug/di/dbgtransportmanager.h | 23 +- src/coreclr/debug/di/dbgtransportpipeline.cpp | 49 +- src/coreclr/debug/di/eventchannel.h | 6 +- src/coreclr/debug/di/localeventchannel.cpp | 24 +- src/coreclr/debug/di/nativepipeline.h | 9 +- src/coreclr/debug/di/process.cpp | 226 ++-- src/coreclr/debug/di/remoteeventchannel.cpp | 13 +- src/coreclr/debug/di/rspriv.h | 30 +- src/coreclr/debug/di/shimremotedatatarget.cpp | 6 +- src/coreclr/debug/di/windowspipeline.cpp | 43 +- src/coreclr/debug/ee/debugger.cpp | 35 +- src/coreclr/debug/ee/debugger.h | 12 +- src/coreclr/debug/ee/rcthread.cpp | 148 ++- src/coreclr/debug/inc/dbgtransportsession.h | 15 +- .../debug/shared/dbgtransportsession.cpp | 142 +- src/coreclr/dlls/mscordbi/CMakeLists.txt | 1 + src/coreclr/inc/cordebug.idl | 7 +- src/coreclr/minipal/CMakeLists.txt | 4 + src/coreclr/minipal/Unix/CMakeLists.txt | 10 + src/coreclr/minipal/Unix/wait.cpp | 1156 +++++++++++++++++ src/coreclr/minipal/Windows/CMakeLists.txt | 1 + src/coreclr/minipal/Windows/wait.cpp | 124 ++ src/coreclr/minipal/minipal-wait.h | 99 ++ src/coreclr/minipal/tests/CMakeLists.txt | 13 + src/coreclr/minipal/tests/wait.cpp | 486 +++++++ 26 files changed, 2401 insertions(+), 405 deletions(-) create mode 100644 src/coreclr/minipal/Unix/wait.cpp create mode 100644 src/coreclr/minipal/Windows/wait.cpp create mode 100644 src/coreclr/minipal/minipal-wait.h create mode 100644 src/coreclr/minipal/tests/CMakeLists.txt create mode 100644 src/coreclr/minipal/tests/wait.cpp diff --git a/src/coreclr/debug/di/dbgtransportmanager.cpp b/src/coreclr/debug/di/dbgtransportmanager.cpp index de3ee71b093944..e24a267af05355 100644 --- a/src/coreclr/debug/di/dbgtransportmanager.cpp +++ b/src/coreclr/debug/di/dbgtransportmanager.cpp @@ -10,61 +10,10 @@ #ifdef HOST_UNIX #include #include -#include -#include -#endif +#endif // HOST_UNIX DbgTransportTarget g_DbgTransportTarget{}; -#ifdef HOST_UNIX -// Polling interval for the per-process exit poller thread. -static const useconds_t s_processExitPollIntervalUsec = 250 * 1000; - -// Polls the target PID for exit. Uses waitpid(WNOHANG) for child processes -// (immune to PID reuse) and falls back to kill(pid, 0) for non-children -// (best-effort, racy under PID reuse). Signals m_hProcessExited on exit. -/* static */ -void *DbgTransportTarget::ProcessExitPollerThread(void *arg) -{ - ProcessEntry *entry = static_cast(arg); - - while (!entry->m_fStopPoller) - { - bool exited = false; - - int status; - pid_t r; - do - { - r = waitpid(entry->m_dwPID, &status, WNOHANG); - } while (r == -1 && errno == EINTR); - - if (r == (pid_t)entry->m_dwPID) - { - exited = true; - } - else if (r == -1 && errno == ECHILD) - { - // Not our child; fall back to kill(pid, 0). - if (kill(entry->m_dwPID, 0) != 0 && errno == ESRCH) - { - exited = true; - } - } - - if (exited) - { - SetEvent(entry->m_hProcessExited); - break; - } - - usleep(s_processExitPollIntervalUsec); - } - - return NULL; -} -#endif // HOST_UNIX - DbgTransportTarget::DbgTransportTarget() : m_pProcessList{} , m_sLock{} @@ -104,7 +53,7 @@ void DbgTransportTarget::Shutdown() // on for process termination. HRESULT DbgTransportTarget::GetTransportForProcess(const ProcessDescriptor *pProcessDescriptor, DbgTransportSession **ppTransport, - HANDLE *phProcessHandle) + minipal_process_wait **ppProcessHandle) { RSLockHolder lock(&m_sLock); HRESULT hr = S_OK; @@ -127,8 +76,8 @@ HRESULT DbgTransportTarget::GetTransportForProcess(const ProcessDescriptor *pPr // Probe the process to make sure it exists, then create a waitable handle that becomes - // signaled on process exit. On HOST_WINDOWS the process handle itself is waitable; on - // HOST_UNIX we create a manual-reset event and start a thread to poll for exit. + // signaled on process exit. On Windows the process handle itself remains available for + // native process operations. #ifdef HOST_UNIX if (kill(dwPID, 0) != 0) { @@ -136,43 +85,42 @@ HRESULT DbgTransportTarget::GetTransportForProcess(const ProcessDescriptor *pPr return (errno == ESRCH) ? E_INVALIDARG : E_FAIL; } - HANDLE hProcessExited = CreateEvent(NULL, TRUE, FALSE, NULL); - if (hProcessExited == NULL) + minipal_process_wait *pProcessExited = new (nothrow) minipal_process_wait(dwPID); + if ((pProcessExited == NULL) || !pProcessExited->IsValid()) { + delete pProcessExited; transport->Shutdown(); - return HRESULT_FROM_GetLastError(); + return E_FAIL; } #else // HOST_UNIX - HANDLE hProcessExited = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID); - if (hProcessExited == NULL) + HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID); + if (hProcess == NULL) { transport->Shutdown(); return HRESULT_FROM_GetLastError(); } -#endif // HOST_UNIX - newEntry->m_dwPID = dwPID; - newEntry->m_hProcessExited = hProcessExited; -#ifdef HOST_UNIX - newEntry->m_fStopPoller = false; - newEntry->m_fPollerStarted = false; - - if (pthread_create(&newEntry->m_pollerThread, NULL, &ProcessExitPollerThread, newEntry.GetValue()) != 0) + minipal_process_wait *pProcessExited = new (nothrow) minipal_process_wait(hProcess); + bool allocationFailed = pProcessExited == nullptr; + DWORD error = GetLastError(); + CloseHandle(hProcess); + if (allocationFailed || !pProcessExited->IsValid()) { + delete pProcessExited; transport->Shutdown(); - CloseHandle(hProcessExited); - newEntry->m_hProcessExited = NULL; - return E_FAIL; + return allocationFailed ? E_OUTOFMEMORY : HRESULT_FROM_WIN32(error); } - newEntry->m_fPollerStarted = true; #endif // HOST_UNIX + newEntry->m_dwPID = dwPID; + newEntry->m_hProcessExited = pProcessExited; + // Initialize it (this immediately starts the remote connection process). - hr = transport->Init(*pProcessDescriptor, hProcessExited); + hr = transport->Init(*pProcessDescriptor, *pProcessExited); if (FAILED(hr)) { transport->Shutdown(); - // ProcessEntry destructor stops the poller thread and closes the event handle. + // ProcessEntry destructor releases the process waitable. return hr; } @@ -190,18 +138,15 @@ HRESULT DbgTransportTarget::GetTransportForProcess(const ProcessDescriptor *pPr entry->m_cProcessRef++; _ASSERTE(entry->m_cProcessRef > 0); _ASSERTE(entry->m_transport != NULL); - _ASSERTE((intptr_t)entry->m_hProcessExited > 0); + _ASSERTE(entry->m_hProcessExited->IsValid()); *ppTransport = entry->m_transport; - if (!DuplicateHandle(GetCurrentProcess(), - entry->m_hProcessExited, - GetCurrentProcess(), - phProcessHandle, - 0, // ignored since we are going to pass DUPLICATE_SAME_ACCESS - FALSE, - DUPLICATE_SAME_ACCESS)) + *ppProcessHandle = new (nothrow) minipal_process_wait(*entry->m_hProcessExited); + if ((*ppProcessHandle == nullptr) || !(*ppProcessHandle)->IsValid()) { - return HRESULT_FROM_GetLastError(); + delete *ppProcessHandle; + *ppProcessHandle = nullptr; + return E_FAIL; } return hr; @@ -227,7 +172,7 @@ void DbgTransportTarget::ReleaseTransport(DbgTransportSession *pTransport) _ASSERTE(entry->m_cProcessRef > 0); _ASSERTE(entry->m_transport != NULL); - _ASSERTE((intptr_t)entry->m_hProcessExited > 0); + _ASSERTE(entry->m_hProcessExited->IsValid()); if (entry->m_transport == pTransport) { @@ -267,18 +212,9 @@ void DbgTransportTarget::KillProcess(DWORD dwPID) DbgTransportTarget::ProcessEntry::~ProcessEntry() { -#ifdef HOST_UNIX - if (m_fPollerStarted) - { - m_fStopPoller = true; - pthread_join(m_pollerThread, NULL); - m_fPollerStarted = false; - } -#endif - if (m_hProcessExited != NULL) { - CloseHandle(m_hProcessExited); + delete m_hProcessExited; m_hProcessExited = NULL; } diff --git a/src/coreclr/debug/di/dbgtransportmanager.h b/src/coreclr/debug/di/dbgtransportmanager.h index 76e150ccbfb7ef..3c2b38f050fbe1 100644 --- a/src/coreclr/debug/di/dbgtransportmanager.h +++ b/src/coreclr/debug/di/dbgtransportmanager.h @@ -7,9 +7,7 @@ #ifdef FEATURE_DBGIPC_TRANSPORT_DI -#ifdef HOST_UNIX -#include -#endif +#include "minipal-wait.h" // TODO: Ideally we'd like to remove this class and don't do any process related book keeping in DBI. @@ -39,7 +37,10 @@ class DbgTransportTarget // Given a PID attempt to find or create a DbgTransportSession instance to manage a connection to a // runtime in that process. Returns E_UNEXPECTED if the process can't be found. Also returns a handle that // can be waited on for process termination. - HRESULT GetTransportForProcess(const ProcessDescriptor *pProcessDescriptor, DbgTransportSession **ppTransport, HANDLE *phProcessHandle); + HRESULT GetTransportForProcess( + const ProcessDescriptor *pProcessDescriptor, + DbgTransportSession **ppTransport, + minipal_process_wait **ppProcessHandle); // Give back a previously acquired transport (if nobody else is using the transport it will close down the // connection at this point). @@ -56,25 +57,13 @@ class DbgTransportTarget { ProcessEntry *m_pNext; // Next entry in the list DWORD m_dwPID; // Process ID for this entry - HANDLE m_hProcessExited; // Waitable handle that becomes signaled when the - // process exits. On HOST_WINDOWS this is the process - // handle itself; on HOST_UNIX it is a manual-reset - // event signaled by the poller thread below. + minipal_process_wait *m_hProcessExited; // Waitable handle that becomes signaled when the process exits DbgTransportSession *m_transport; // Debugger's connection to the process DWORD m_cProcessRef; // Ref count -#ifdef HOST_UNIX - pthread_t m_pollerThread; // Thread that polls m_dwPID for exit - bool m_fPollerStarted; // True once m_pollerThread has been created - Volatile m_fStopPoller; // Set to true to ask the poller thread to exit -#endif // HOST_UNIX ~ProcessEntry(); }; -#ifdef HOST_UNIX - static void *ProcessExitPollerThread(void *arg); -#endif - ProcessEntry *m_pProcessList; // Head of list of currently alive processes (unsorted) RSLock m_sLock; // Lock protecting read and write access to the target list diff --git a/src/coreclr/debug/di/dbgtransportpipeline.cpp b/src/coreclr/debug/di/dbgtransportpipeline.cpp index b42f7c4534e080..bdc4907b718924 100644 --- a/src/coreclr/debug/di/dbgtransportpipeline.cpp +++ b/src/coreclr/debug/di/dbgtransportpipeline.cpp @@ -91,7 +91,7 @@ class DbgTransportPipeline : ); // Return a handle which will be signaled when the debuggee process terminates. - virtual HANDLE GetProcessHandle(); + virtual minipal_process_wait *GetProcessHandle(); // Terminate the debuggee process. virtual BOOL TerminateProcess(UINT32 exitCode); @@ -115,7 +115,7 @@ class DbgTransportPipeline : { if (m_hProcess != NULL) { - CloseHandle(m_hProcess); + delete m_hProcess; } m_hProcess = NULL; @@ -134,8 +134,8 @@ class DbgTransportPipeline : BOOL m_fRunning; DWORD m_dwProcessId; - // This is actually a handle to an event. This is only valid for waiting on process termination. - HANDLE m_hProcess; + // This waitable is only valid for waiting on process termination. + minipal_process_wait *m_hProcess; DbgTransportTarget * m_pProxy; DbgTransportSession * m_pTransport; @@ -229,14 +229,14 @@ BOOL DbgTransportPipeline::WaitForDebugEvent(DEBUG_EVENT * pEvent, DWORD dwTimeo // We need to wait for a debug event from the transport and the process termination event. // On Windows, process termination is communicated via a debug event as well, but that's not true for // the Mac debugging transport. - DWORD cWaitSet = 2; - HANDLE rghWaitSet[2]; - rghWaitSet[0] = m_pTransport->GetDebugEventReadyEvent(); - rghWaitSet[1] = m_hProcess; + const minipal_wait_handle *waitSet[] = { + m_pTransport->GetDebugEventReadyEvent(), + m_hProcess + }; - DWORD dwRet = ::WaitForMultipleObjectsEx(cWaitSet, rghWaitSet, FALSE, dwTimeout, FALSE); + int32_t waitResult = minipal_wait_handle::Wait(waitSet, ARRAY_SIZE(waitSet), dwTimeout); - if (dwRet == WAIT_OBJECT_0) + if (waitResult == 0) { // The Mac debugging transport actually transmits IPC events and not debug events. // We need to convert the IPC event to a debug event and pass it back to the caller. @@ -256,7 +256,7 @@ BOOL DbgTransportPipeline::WaitForDebugEvent(DEBUG_EVENT * pEvent, DWORD dwTimeo return TRUE; } - else if (dwRet == (WAIT_OBJECT_0 + 1)) + else if (waitResult == 1) { // The process has been terminated. @@ -295,24 +295,23 @@ BOOL DbgTransportPipeline::ContinueDebugEvent( } // Return a handle which will be signaled when the debuggee process terminates. -HANDLE DbgTransportPipeline::GetProcessHandle() +minipal_process_wait *DbgTransportPipeline::GetProcessHandle() { - HANDLE hProcessTerminated; - - if (!DuplicateHandle(GetCurrentProcess(), - m_hProcess, - GetCurrentProcess(), - &hProcessTerminated, - 0, // ignored since we are going to pass DUPLICATE_SAME_ACCESS - FALSE, - DUPLICATE_SAME_ACCESS)) + // The handle returned here is only valid for waiting on process termination. + // See code:INativeEventPipeline::GetProcessHandle. + if (m_hProcess == nullptr) { - return NULL; + return nullptr; } - // The handle returned here is only valid for waiting on process termination. - // See code:INativeEventPipeline::GetProcessHandle. - return hProcessTerminated; + minipal_process_wait *processHandle = new (nothrow) minipal_process_wait(*m_hProcess); + if ((processHandle != nullptr) && !processHandle->IsValid()) + { + delete processHandle; + processHandle = nullptr; + } + + return processHandle; } // Terminate the debuggee process. diff --git a/src/coreclr/debug/di/eventchannel.h b/src/coreclr/debug/di/eventchannel.h index 0376a85010275a..2c572dbe45f4e3 100644 --- a/src/coreclr/debug/di/eventchannel.h +++ b/src/coreclr/debug/di/eventchannel.h @@ -12,6 +12,8 @@ #ifndef _EVENT_CHANNEL_H_ #define _EVENT_CHANNEL_H_ +#include "minipal-wait.h" + //--------------------------------------------------------------------------------------- // // This is the abstract base class for the old-style "IPC" event channel. (Despite the name, these events are @@ -154,13 +156,13 @@ class IEventChannel // first to see if it is necessary to wait for an acknowledgement. // // Return Value: - // a handle to a Win32 event which will be signaled when the LS acknowledges the receipt of the IPC event + // a waitable event which will be signaled when the LS acknowledges the receipt of the IPC event // // Assumptions: // NeedToWaitForAck() returns true after sending an IPC event to the LS // - virtual HANDLE GetRightSideEventAckHandle() = 0; + virtual minipal_event *GetRightSideEventAckHandle() = 0; // // After sending an event to the LS and determining that we need to wait for the LS's acknowledgement, diff --git a/src/coreclr/debug/di/localeventchannel.cpp b/src/coreclr/debug/di/localeventchannel.cpp index a7eb5fd9a5c405..9f0f6c7a7f15a0 100644 --- a/src/coreclr/debug/di/localeventchannel.cpp +++ b/src/coreclr/debug/di/localeventchannel.cpp @@ -51,7 +51,7 @@ class LocalEventChannel : public IEventChannel virtual BOOL NeedToWaitForAck(DebuggerIPCEvent * pEvent); // Get a handle to wait on after sending an IPC event to the LS. The caller should call NeedToWaitForAck() - virtual HANDLE GetRightSideEventAckHandle(); + virtual minipal_event *GetRightSideEventAckHandle(); // Clean up the state if the wait for an acknowledgement is unsuccessful. virtual void ClearEventForLeftSide(); @@ -100,6 +100,7 @@ class LocalEventChannel : public IEventChannel // used by the LS to signal that the event is read HANDLE m_rightSideEventRead; + minipal_event *m_rightSideEventReadWaitHandle; // handle of the debuggee process HANDLE m_hTargetProc; @@ -159,6 +160,7 @@ LocalEventChannel::LocalEventChannel(CORDB_ADDRESS pLeftSideDCB, m_rightSideEventAvailable = NULL; m_rightSideEventRead = NULL; + m_rightSideEventReadWaitHandle = nullptr; m_pMutableDataTarget.Assign(pMutableDataTarget); } @@ -190,6 +192,19 @@ HRESULT LocalEventChannel::Init(HANDLE hTargetProc) IfFailRet(DuplicateHandleToLocalProcess(&m_rightSideEventRead, &m_pDCBBuffer->m_rightSideEventRead)); + m_rightSideEventReadWaitHandle = new (nothrow) minipal_event(m_rightSideEventRead); + if (m_rightSideEventReadWaitHandle == nullptr) + { + return E_OUTOFMEMORY; + } + if (!m_rightSideEventReadWaitHandle->IsValid()) + { + HRESULT hr = HRESULT_FROM_GetLastError(); + delete m_rightSideEventReadWaitHandle; + m_rightSideEventReadWaitHandle = nullptr; + return hr; + } + return S_OK; } @@ -234,6 +249,9 @@ void LocalEventChannel::Delete() m_rightSideEventRead = NULL; } + delete m_rightSideEventReadWaitHandle; + m_rightSideEventReadWaitHandle = nullptr; + if (m_pDCBBuffer != NULL) { delete m_pDCBBuffer; @@ -294,9 +312,9 @@ BOOL LocalEventChannel::NeedToWaitForAck(DebuggerIPCEvent * pEvent) // Get a handle to wait on after sending an IPC event to the LS. The caller should call NeedToWaitForAck() // // virtual -HANDLE LocalEventChannel::GetRightSideEventAckHandle() +minipal_event *LocalEventChannel::GetRightSideEventAckHandle() { - return m_rightSideEventRead; + return m_rightSideEventReadWaitHandle; } // Clean up the state if the wait for an acknowledgement is unsuccessful. diff --git a/src/coreclr/debug/di/nativepipeline.h b/src/coreclr/debug/di/nativepipeline.h index 442ab32138b5ff..8c5709bf51f19f 100644 --- a/src/coreclr/debug/di/nativepipeline.h +++ b/src/coreclr/debug/di/nativepipeline.h @@ -109,12 +109,12 @@ class INativeEventPipeline // handle for the debuggee process (see below) // // Notes: - // Handles are a Windows-specific concept. For Mac debugging, the handle returned by this function is - // only valid for waiting on process termination. This is ok for now because the only cases where a - // real process handle is needed are related to interop-debugging, which isn't supported on the Mac. + // Handles are a Windows-specific concept. On Unix, the returned value is an internal opaque minipal + // waitable that is only valid for debugger-internal process-termination waits. It must not be exposed + // through ICorDebugProcess::GetHandle. // - virtual HANDLE GetProcessHandle() = 0; + virtual minipal_process_wait *GetProcessHandle() = 0; // // Terminate the debuggee process. @@ -175,4 +175,3 @@ BOOL IsExceptionEvent(const DEBUG_EVENT * pEvent, BOOL * pfFirstChance, const EX INativeEventPipeline * NewPipelineForThisPlatform(); #endif // _NATIVE_PIPELINE_H - diff --git a/src/coreclr/debug/di/process.cpp b/src/coreclr/debug/di/process.cpp index b7464de024d85d..9ea3e1ca230eb3 100644 --- a/src/coreclr/debug/di/process.cpp +++ b/src/coreclr/debug/di/process.cpp @@ -960,8 +960,9 @@ CordbProcess::CordbProcess(ULONG64 clrInstanceId, CordbHashTable m_steppers; // Closed in ~CordbProcess + // Deleted in ~CordbProcess + minipal_event *m_leftSideEventAvailable; // Closed in CloseIPCEventHandles called from ~CordbProcess - HANDLE m_leftSideEventAvailable; HANDLE m_leftSideEventRead; // Closed in ~CordbProcess @@ -988,6 +989,11 @@ CordbProcess::~CordbProcess() // We shouldn't still be in Cordb's list of processes. Unfortunately, our root Cordb object // may have already been deleted b/c we're at the mercy of ref-counting, so we can't check. + // RCET wait sets hold non-owning pointers to this wrapper while retaining the CordbProcess. + // Keep it alive until the final process reference is released. + delete m_leftSideEventAvailable; + m_leftSideEventAvailable = NULL; + m_processMutex.Destroy(); m_StopGoLock.Destroy(); @@ -1067,14 +1073,15 @@ HRESULT ShimProcess::DebugActiveProcess( // being 'managed attached' if(!pShim->m_fIsInteropDebugging) { - DWORD dwHandles = 2; - HANDLE arrHandles[2]; - - arrHandles[0] = pShim->m_terminatingEvent; - arrHandles[1] = pShim->m_markAttachPendingEvent; + minipal_event terminatingEvent(pShim->m_terminatingEvent); + minipal_event markAttachPendingEvent(pShim->m_markAttachPendingEvent); + const minipal_wait_handle *waitSet[] = { &terminatingEvent, &markAttachPendingEvent }; // Wait for the completion of marking pending attach bit or debugger detaching - WaitForMultipleObjectsEx(dwHandles, arrHandles, FALSE, INFINITE, FALSE); + minipal_wait_handle::Wait( + waitSet, + ARRAY_SIZE(waitSet), + MINIPAL_WAIT_INFINITE); } #endif //!FEATURE_DBGIPC_TRANSPORT_DI } @@ -1269,13 +1276,6 @@ void CordbProcess::CloseIPCHandles() { INTERNAL_API_ENTRY(this); - // Close off Right Side's handles. - if (m_leftSideEventAvailable != NULL) - { - CloseHandle(m_leftSideEventAvailable); - m_leftSideEventAvailable = NULL; - } - if (m_leftSideEventRead != NULL) { CloseHandle(m_leftSideEventRead); @@ -1284,12 +1284,7 @@ void CordbProcess::CloseIPCHandles() if (m_handle != NULL) { - // @dbgtodo - We should probably add asserts to all calls to CloseHandles(), but this has been - // a particularly problematic spot in the past for Mac debugging. - BOOL fSuccess = CloseHandle(m_handle); - (void)fSuccess; //prevent "unused variable" error from GCC - _ASSERTE(fSuccess); - + delete m_handle; m_handle = NULL; } @@ -1625,10 +1620,12 @@ HRESULT CordbProcess::Init() // signal existing RS infrastructure. Eventually get rid of LSEA, LSER completely. // - m_leftSideEventAvailable = CreateEvent(NULL, FALSE, FALSE, NULL); - if (m_leftSideEventAvailable == NULL) + m_leftSideEventAvailable = new (nothrow) minipal_event(false, false); + if ((m_leftSideEventAvailable == nullptr) || !m_leftSideEventAvailable->IsValid()) { - ThrowLastError(); + delete m_leftSideEventAvailable; + m_leftSideEventAvailable = nullptr; + ThrowOutOfMemory(); } m_leftSideEventRead = CreateEvent(NULL, FALSE, FALSE, NULL); @@ -1657,7 +1654,7 @@ HRESULT CordbProcess::Init() // This is not needed in the V3 pipeline because we don't assume we have a live, local, process. m_handle = GetShim()->GetNativePipeline()->GetProcessHandle(); - if (m_handle == NULL) + if ((m_handle == nullptr) || !m_handle->IsValid()) { ThrowLastError(); } @@ -1780,7 +1777,7 @@ void CordbProcess::Terminating(BOOL fDetach) // But don't set RSER unless we actually read the event. We don't block on RSER // since that wait also checks the leftside's process handle. SetEvent(m_leftSideEventRead); - SetEvent(m_leftSideEventAvailable); + m_leftSideEventAvailable->Set(); SetEvent(m_stopWaitEvent); if (m_pShim != NULL) @@ -3016,9 +3013,10 @@ void CordbProcess::DetachShim() IfFailThrow(hr); #ifdef OUT_OF_PROCESS_SETTHREADCONTEXT - const HANDLE rghWaitSet[] = { - m_detachSetThreadContextNeededEvent, // Signaled on every debug event after the first SendCanDetach request - UnsafeGetProcessHandle() // Signaled when the process exits + minipal_event detachSetThreadContextNeededEvent(m_detachSetThreadContextNeededEvent); + const minipal_wait_handle *waitSet[] = { + &detachSetThreadContextNeededEvent, // Signaled on every debug event after the first SendCanDetach request + UnsafeGetProcessWaitHandle() // Signaled when the process exits }; bool fDetachComplete = !m_fOutOfProcessSetThreadContextEventReceived; @@ -3029,14 +3027,17 @@ void CordbProcess::DetachShim() } while (!fDetachComplete) { - DWORD dwResult = WaitForMultipleObjectsEx(_countof(rghWaitSet), rghWaitSet, FALSE, DETACH_WAIT_TIMEOUT_MS, FALSE); - if (dwResult == WAIT_OBJECT_0) + int32_t waitResult = minipal_wait_handle::Wait( + waitSet, + ARRAY_SIZE(waitSet), + DETACH_WAIT_TIMEOUT_MS); + if (waitResult == 0) { // We have been signaled via TryDetach() to determine if it is safe to detach // so call CanDetach and then detach if it returns S_OK fDetachComplete = (this->m_pShim->GetWin32EventThread()->SendCanDetach() == S_OK); } - else if (dwResult == WAIT_OBJECT_0 + 1 /*UnsafeGetProcessHandle()*/) + else if (waitResult == 1) { // The process has exited while waiting for the detach to complete m_detached = true; @@ -3048,7 +3049,7 @@ void CordbProcess::DetachShim() // We timed out waiting for debug events, indicating the process is idle. // Simply detach as if it had succeeded. - _ASSERTE(dwResult == WAIT_TIMEOUT); + _ASSERTE(waitResult == MINIPAL_WAIT_TIMEOUT); CONSISTENCY_CHECK_MSGF(false, ("Timeout while waiting for detach to complete")); fDetachComplete = true; @@ -3282,6 +3283,12 @@ HRESULT CordbProcess::GetHandle(HANDLE *phProcessHandle) FAIL_IF_NEUTERED(this); // Once we neuter the process, we close our OS handle to it. VALIDATE_POINTER_TO_OBJECT(phProcessHandle, HANDLE *); +#ifdef HOST_UNIX + // COMPAT: Unix callers historically received an opaque PAL process handle and only rely on this + // API succeeding, so return the process ID as an opaque sentinel now that DBI uses minipal internally. + *phProcessHandle = reinterpret_cast(static_cast(GetProcessDescriptor()->m_Pid)); + return S_OK; +#else if (m_pShim == NULL) { _ASSERTE(!"CordbProcess::GetHandle() should be not be called on the new architecture"); @@ -3290,9 +3297,10 @@ HRESULT CordbProcess::GetHandle(HANDLE *phProcessHandle) } else { - *phProcessHandle = m_handle; + *phProcessHandle = UnsafeGetProcessHandle(); return S_OK; } +#endif } HRESULT CordbProcess::IsRunning(BOOL *pbRunning) @@ -7936,7 +7944,7 @@ HRESULT CordbProcess::StartSyncFromWin32Stop(BOOL * pfAsyncBreakSent) bool CordbProcess::CheckIfLSExited() { // Check by waiting on the handle with no timeout. - if (WaitForSingleObject(m_handle, 0) == WAIT_OBJECT_0) + if (minipal_wait_handle::Wait(*m_handle, 0) == 0) { Lock(); m_terminated = true; @@ -8748,7 +8756,7 @@ CordbRCEventThread::CordbRCEventThread(Cordb* cordb) CordbRCEventThread::~CordbRCEventThread() { if (m_threadControlEvent != NULL) - CloseHandle(m_threadControlEvent); + delete m_threadControlEvent; if (m_thread != NULL) CloseHandle(m_thread); @@ -8764,10 +8772,14 @@ HRESULT CordbRCEventThread::Init() if (m_cordb == NULL) return E_INVALIDARG; - m_threadControlEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + m_threadControlEvent = new (nothrow) minipal_event(false, false); - if (m_threadControlEvent == NULL) - return HRESULT_FROM_GetLastError(); + if ((m_threadControlEvent == nullptr) || !m_threadControlEvent->IsValid()) + { + delete m_threadControlEvent; + m_threadControlEvent = nullptr; + return E_OUTOFMEMORY; + } return S_OK; } @@ -8791,7 +8803,7 @@ void CordbProcess::DuplicateHandleToLocalProcess(HANDLE * pLocalHandle, RemoteHA // On Launch, we don't have them yet, but on attach we do. if (*pLocalHandle == NULL) { - BOOL fSuccess = pRemoteHandle->DuplicateToLocalProcess(m_handle, pLocalHandle); + BOOL fSuccess = pRemoteHandle->DuplicateToLocalProcess(UnsafeGetProcessHandle(), pLocalHandle); if (!fSuccess) { ThrowLastError(); @@ -8851,7 +8863,7 @@ void CordbProcess::FinishInitializeIPCChannelWorker() LOG((LF_CORDB, LL_EVERYTHING, "Size of CdbP is %zu\n", sizeof(CordbProcess))); - m_pEventChannel->Init(m_handle); + IfFailThrow(m_pEventChannel->Init(UnsafeGetProcessHandle())); #if defined(FEATURE_INTEROP_DEBUGGING) DuplicateHandleToLocalProcess(&m_leftSideUnmanagedWaitEvent, &GetDCB()->m_leftSideUnmanagedWaitEvent); @@ -9327,13 +9339,11 @@ HRESULT CordbRCEventThread::SendIPCEvent(CordbProcess* process, } else { - // Get a handle to the target process - this call always succeeds - HANDLE hLSProcess = NULL; - process->GetHandle(&hLSProcess); + minipal_process_wait *pLSProcess = process->UnsafeGetProcessWaitHandle(); // We take locks to ensure that the CordbProcess object is still alive, // even if the OS process exited. - _ASSERTE(hLSProcess != NULL); + _ASSERTE(pLSProcess != nullptr); // Check if Sending the IPC event failed if (FAILED(hr)) @@ -9343,8 +9353,8 @@ HRESULT CordbRCEventThread::SendIPCEvent(CordbProcess* process, // There is a race here - we can't rely on any check above SendEventToLeftSide // to tell us whether the process has exited yet. // Check for that case and return an accurate hresult. - DWORD ret = WaitForSingleObject(hLSProcess, 0); - if (ret == WAIT_OBJECT_0) + int32_t waitResult = minipal_wait_handle::Wait(*pLSProcess, 0); + if (waitResult == 0) { return CORDBG_E_PROCESS_TERMINATED; } @@ -9362,7 +9372,7 @@ HRESULT CordbRCEventThread::SendIPCEvent(CordbProcess* process, { STRESS_LOG0(LF_CORDB, LL_INFO1000,"CRCET::SIPCE: waiting for left side to read event. (on RSER)\n"); - DWORD ret; + int32_t waitResult; // Wait for either a reply (common case) or the left side to go away. // We can't detach while waiting for a reply (because detach needs to send events). @@ -9371,7 +9381,7 @@ HRESULT CordbRCEventThread::SendIPCEvent(CordbProcess* process, // and so ExitProcess may have been called, but it doesn't matter. enum { - ID_RSER = WAIT_OBJECT_0, + ID_RSER = 0, ID_LSPROCESS, ID_HELPERTHREAD, }; @@ -9381,29 +9391,46 @@ HRESULT CordbRCEventThread::SendIPCEvent(CordbProcess* process, // follow up with an exit. // This includes when we've dispatch Native events, and it includes the AsyncBreak sent to get us from a // win32 frozen state to a synchronized state). + minipal_process_wait *pHelperThreadWait = nullptr; +#ifdef HOST_WINDOWS HANDLE hHelperThread = NULL; if (process->IsStopped()) { hHelperThread = process->GetHelperThreadHandle(); } + if (hHelperThread != NULL) + { + pHelperThreadWait = new (nothrow) minipal_process_wait(hHelperThread); + if ((pHelperThreadWait == nullptr) || !pHelperThreadWait->IsValid()) + { + delete pHelperThreadWait; + return E_OUTOFMEMORY; + } + } +#endif - // Note that in case of a tie (multiple handles signaled), WaitForMultipleObjects gives - // priority to the handle earlier in the array. - HANDLE waitSet[] = { process->GetEventChannel()->GetRightSideEventAckHandle(), hLSProcess, hHelperThread}; + // In a tie, wait-any gives priority to the handle earlier in the array. + const minipal_wait_handle *waitSet[] = { + process->GetEventChannel()->GetRightSideEventAckHandle(), + pLSProcess, + pHelperThreadWait + }; DWORD cWaitSet = ARRAY_SIZE(waitSet); - if (hHelperThread == NULL) + if (pHelperThreadWait == nullptr) { cWaitSet--; } do { - ret = WaitForMultipleObjectsEx(cWaitSet, waitSet, FALSE, CordbGetWaitTimeout(), FALSE); + waitResult = minipal_wait_handle::Wait(waitSet, cWaitSet, CordbGetWaitTimeout()); // If we timeout because we're waiting for an uncontinued OOB event, we need to just keep waiting. - } while ((ret == WAIT_TIMEOUT) && process->IsWaitingForOOBEvent()); + } while ((waitResult == MINIPAL_WAIT_TIMEOUT) && process->IsWaitingForOOBEvent()); + + delete pHelperThreadWait; - switch(ret) + switch(waitResult) { case ID_RSER: // Normal reply from LS. @@ -9456,7 +9483,7 @@ HRESULT CordbRCEventThread::SendIPCEvent(CordbProcess* process, // If we timed out/failed, check the left side to see if it is in the unrecoverable error mode. If it is, // return the HR from the left side that caused the error. Otherwise, return that we timed out and that // we don't really know why. - HRESULT realHR = (ret == WAIT_FAILED) ? HRESULT_FROM_GetLastError() : ErrWrapper(CORDBG_E_TIMEOUT); + HRESULT realHR = waitResult == MINIPAL_WAIT_FAILED ? E_FAIL : ErrWrapper(CORDBG_E_TIMEOUT); hr = process->CheckForUnrecoverableError(); @@ -9663,7 +9690,7 @@ void CordbRCEventThread::ProcessStateChanged() m_cordb->LockProcessList(); STRESS_LOG0(LF_CORDB, LL_INFO100000, "CRCET::ProcessStateChanged\n"); m_processStateChanged = TRUE; - SetEvent(m_threadControlEvent); + m_threadControlEvent->Set(); m_cordb->UnlockProcessList(); } @@ -9683,13 +9710,13 @@ void CordbRCEventThread::ProcessStateChanged() //--------------------------------------------------------------------------------------- void CordbRCEventThread::ThreadProc() { - HANDLE waitSet[MAXIMUM_WAIT_OBJECTS]; + const minipal_wait_handle *waitSet[MAXIMUM_WAIT_OBJECTS]; CordbProcess * rgProcessSet[MAXIMUM_WAIT_OBJECTS]; unsigned int waitCount; #ifdef _DEBUG memset(&rgProcessSet, 0, MAXIMUM_WAIT_OBJECTS * sizeof(CordbProcess *)); - memset(&waitSet, 0, MAXIMUM_WAIT_OBJECTS * sizeof(HANDLE)); + memset(&waitSet, 0, sizeof(waitSet)); #endif @@ -9700,18 +9727,17 @@ void CordbRCEventThread::ThreadProc() while (m_run) { - DWORD dwStatus = WaitForMultipleObjectsEx(waitCount, waitSet, FALSE, 2000, FALSE); + int32_t waitResult = minipal_wait_handle::Wait(waitSet, waitCount, 2000); - if (dwStatus == WAIT_FAILED) + if (waitResult == MINIPAL_WAIT_FAILED) { - STRESS_LOG1(LF_CORDB, LL_INFO10000, "CordbRCEventThread::ThreadProc WaitFor" - "MultipleObjects failed: 0x%x\n", GetLastError()); + STRESS_LOG0(LF_CORDB, LL_INFO10000, "CordbRCEventThread::ThreadProc wait failed\n"); } #ifdef _DEBUG - else if ((dwStatus >= WAIT_OBJECT_0) && (dwStatus < WAIT_OBJECT_0 + waitCount) && m_run) + else if ((waitResult >= 0) && (static_cast(waitResult) < waitCount) && m_run) { // Got an event. Figure out which process it came from. - unsigned int procNumber = dwStatus - WAIT_OBJECT_0; + unsigned int procNumber = static_cast(waitResult); if (procNumber != 0) { @@ -9965,7 +9991,7 @@ void CordbRCEventThread::QueueAsyncWorkItem(RCETWorkItem * pItem) m_WorkerStack.Push(pItem); // Ping the RCET so that it drains the queue. - SetEvent(m_threadControlEvent); + m_threadControlEvent->Set(); } // Execute & delete all workitems in the queue. @@ -10020,26 +10046,27 @@ HRESULT CordbRCEventThread::WaitForIPCEventFromProcess(CordbProcess * pProcess, CORDBRequireProcessStateOKAndSync(pProcess, pAppDomain); - DWORD dwStatus; + int32_t waitResult; HRESULT hr = S_OK; do { - dwStatus = SafeWaitForSingleObject(pProcess, - pProcess->m_leftSideEventAvailable, - CordbGetWaitTimeout()); + _ASSERTE(!pProcess->ThreadHoldsProcessLock()); + waitResult = minipal_wait_handle::Wait( + *pProcess->m_leftSideEventAvailable, + CordbGetWaitTimeout()); if (pProcess->m_terminated) { return CORDBG_E_PROCESS_TERMINATED; } // If we timeout because we're waiting for an uncontinued OOB event, we need to just keep waiting. - } while ((dwStatus == WAIT_TIMEOUT) && pProcess->IsWaitingForOOBEvent()); + } while ((waitResult == MINIPAL_WAIT_TIMEOUT) && pProcess->IsWaitingForOOBEvent()); - if (dwStatus == WAIT_OBJECT_0) + if (waitResult == 0) { pProcess->CopyRCEventFromIPCBlock(pEvent); @@ -10060,7 +10087,7 @@ HRESULT CordbRCEventThread::WaitForIPCEventFromProcess(CordbProcess * pProcess, return hr; } - else if (dwStatus == WAIT_TIMEOUT) + else if (waitResult == MINIPAL_WAIT_TIMEOUT) { // // If we timed out, check the left side to see if it is in the @@ -10082,9 +10109,9 @@ HRESULT CordbRCEventThread::WaitForIPCEventFromProcess(CordbProcess * pProcess, } else { - _ASSERTE(dwStatus == WAIT_FAILED); + _ASSERTE(waitResult == MINIPAL_WAIT_FAILED); - hr = HRESULT_FROM_GetLastError(); + hr = E_FAIL; CORDBSetUnrecoverableError(pProcess, hr, 0); @@ -10131,7 +10158,7 @@ HRESULT CordbRCEventThread::Stop() m_run = FALSE; - SetEvent(m_threadControlEvent); + m_threadControlEvent->Set(); DWORD ret = WaitForSingleObject(m_thread, INFINITE); @@ -10200,7 +10227,7 @@ CordbWin32EventThread::~CordbWin32EventThread() CloseHandle(m_thread); if (m_threadControlEvent != NULL) - CloseHandle(m_threadControlEvent); + delete m_threadControlEvent; if (m_actionTakenEvent != NULL) CloseHandle(m_actionTakenEvent); @@ -10225,9 +10252,13 @@ HRESULT CordbWin32EventThread::Init() m_sendToWin32EventThreadMutex.Init("Win32-Send lock", RSLock::cLockFlat, RSLock::LL_WIN32_SEND_LOCK); - m_threadControlEvent = CreateEvent(NULL, FALSE, FALSE, NULL); - if (m_threadControlEvent == NULL) - return HRESULT_FROM_GetLastError(); + m_threadControlEvent = new (nothrow) minipal_event(false, false); + if ((m_threadControlEvent == nullptr) || !m_threadControlEvent->IsValid()) + { + delete m_threadControlEvent; + m_threadControlEvent = nullptr; + return E_OUTOFMEMORY; + } m_actionTakenEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if (m_actionTakenEvent == NULL) @@ -10337,7 +10368,7 @@ void CordbProcess::FilterClrNotification( // Save the IPC event and wake up the thread which is waiting for it from the LS. GetEventChannel()->SaveEventFromLeftSide(pManagedEvent); - SetEvent(this->m_leftSideEventAvailable); + this->m_leftSideEventAvailable->Set(); // Some other thread called code:CordbRCEventThread::WaitForIPCEventFromProcess, and // that will respond here and set the event. @@ -11163,7 +11194,7 @@ void CordbWin32EventThread::Win32EventLoop() // Have to wait on 2 sources: - // WaitForMultipleObjects - ping for messages (create, attach, Continue, detach) and also + // Control wait set - ping for messages (create, attach, Continue, detach) and also // process exits in the managed-only case. // Native Debug Events - This is a huge perf hit so we want to avoid it whenever we can. // Only wait on these if we're interop debugging and if the process is not frozen. @@ -11172,9 +11203,9 @@ void CordbWin32EventThread::Win32EventLoop() unsigned int cWaitCount = 1; - HANDLE rghWaitSet[2]; + const minipal_wait_handle *waitSet[2]; - rghWaitSet[0] = m_threadControlEvent; + waitSet[0] = m_threadControlEvent; DWORD dwWaitTimeout = INFINITE; DEBUG_EVENT event = {}; @@ -11237,16 +11268,17 @@ void CordbWin32EventThread::Win32EventLoop() // that will ensure that we only wait for an Exit event once. if ((m_pProcess != NULL) && fDidNotJustGetExitProcessEvent) { - rghWaitSet[1] = m_pProcess->UnsafeGetProcessHandle(); + waitSet[1] = m_pProcess->UnsafeGetProcessWaitHandle(); cWaitCount = 2; } // See if any process that we aren't attached to as the Win32 debugger have exited. (Note: this is a // polling action if we are also waiting for Win32 debugger events. We're also looking at the thread // control event here, too, to see if we're supposed to do something, like attach. - DWORD dwStatus = WaitForMultipleObjectsEx(cWaitCount, rghWaitSet, FALSE, dwWaitTimeout, FALSE); + int32_t waitResult = minipal_wait_handle::Wait(waitSet, cWaitCount, dwWaitTimeout); - _ASSERTE((dwStatus == WAIT_TIMEOUT) || (dwStatus < cWaitCount)); + _ASSERTE((waitResult == MINIPAL_WAIT_TIMEOUT) || + (waitResult >= 0 && static_cast(waitResult) < cWaitCount)); if (!m_run) { @@ -11255,15 +11287,15 @@ void CordbWin32EventThread::Win32EventLoop() } LOG((LF_CORDB, LL_INFO100000, "W32ET::W32EL - got event , ret=%d, has w32 dbg event=%d\n", - dwStatus, fEventAvailable)); + waitResult, fEventAvailable)); // If we haven't timed out, or if it wasn't the thread control event // that was set, then a process has // exited... - if ((dwStatus != WAIT_TIMEOUT) && (dwStatus != WAIT_OBJECT_0)) + if ((waitResult != MINIPAL_WAIT_TIMEOUT) && (waitResult != 0)) { // Grab the process that exited. - _ASSERTE((dwStatus - WAIT_OBJECT_0) == 1); + _ASSERTE(waitResult == 1); ExitProcess(false); // not detach fEventAvailable = false; } @@ -11517,7 +11549,7 @@ CordbUnmanagedThread * CordbProcess::GetUnmanagedThreadFromEvent(const DEBUG_EVE // Kill the process. // RS will pump events until we LS process exits. - TerminateProcess(this->m_handle, hr); + TerminateProcess(UnsafeGetProcessHandle(), hr); return pUnmanagedThread; } @@ -12147,7 +12179,7 @@ Reaction CordbProcess::TriageExcep1stChanceAndInit(CordbUnmanagedThread * pUnman this->m_helperThreadDead = true; // This only works on Windows, not on Mac. We don't support interop-debugging on Mac anyway. - SetEvent(m_pEventChannel->GetRightSideEventAckHandle()); + m_pEventChannel->GetRightSideEventAckHandle()->Set(); // Note: we remember that this was a second chance event from one of the special stack overflow // cases with CUES_ExceptionUnclearable. This tells us to force the process to terminate when we @@ -13439,7 +13471,7 @@ HRESULT CordbWin32EventThread::SendDebugActiveProcessEvent( // threads from making requests at the same time. m_action = W32ETA_ATTACH_PROCESS; - BOOL succ = SetEvent(m_threadControlEvent); + BOOL succ = m_threadControlEvent->Set(); if (succ) { @@ -13678,7 +13710,7 @@ HRESULT CordbWin32EventThread::SendDetachProcessEvent(CordbProcess *pProcess) // requests at the same time. m_action = W32ETA_DETACH; - BOOL succ = SetEvent(m_threadControlEvent); + BOOL succ = m_threadControlEvent->Set(); if (succ) { @@ -13725,7 +13757,7 @@ HRESULT CordbWin32EventThread::SendUnmanagedContinue(CordbProcess *pProcess, // threads from making requests at the same time. m_action = W32ETA_CONTINUE; - BOOL succ = SetEvent(m_threadControlEvent); + BOOL succ = m_threadControlEvent->Set(); if (succ) { @@ -14190,7 +14222,7 @@ HRESULT CordbWin32EventThread::SendCanDetach() m_action = W32ETA_CAN_DETACH; - BOOL succ = SetEvent(m_threadControlEvent); + BOOL succ = m_threadControlEvent->Set(); if (succ) { @@ -14269,7 +14301,7 @@ HRESULT CordbWin32EventThread::Stop() m_action = W32ETA_NONE; m_run = FALSE; - SetEvent(m_threadControlEvent); + m_threadControlEvent->Set(); UnlockSendToWin32EventThreadMutex(); DWORD ret = WaitForSingleObject(m_thread, INFINITE); diff --git a/src/coreclr/debug/di/remoteeventchannel.cpp b/src/coreclr/debug/di/remoteeventchannel.cpp index 47a104720e5c1d..a72ccb946fd997 100644 --- a/src/coreclr/debug/di/remoteeventchannel.cpp +++ b/src/coreclr/debug/di/remoteeventchannel.cpp @@ -52,7 +52,7 @@ class RemoteEventChannel : public IEventChannel virtual BOOL NeedToWaitForAck(DebuggerIPCEvent * pEvent); // Get a handle to wait on after sending an IPC event to the LS. The caller should call NeedToWaitForAck() - virtual HANDLE GetRightSideEventAckHandle(); + virtual minipal_event *GetRightSideEventAckHandle(); // Clean up the state if the wait for an acknowledgement is unsuccessful. virtual void ClearEventForLeftSide(); @@ -95,7 +95,7 @@ HRESULT NewEventChannelForThisPlatform(CORDB_ADDRESS pLeftSideDCB, { // @dbgtodo Mac - Consider moving all of the transport logic to one place. // Perhaps add a new function on DbgTransportManager. - HandleHolder hDummy; + minipal_process_wait *processWaitHandle = nullptr; HRESULT hr = E_FAIL; RemoteEventChannel * pEventChannel = NULL; @@ -104,7 +104,7 @@ HRESULT NewEventChannelForThisPlatform(CORDB_ADDRESS pLeftSideDCB, DbgTransportTarget * pProxy = &g_DbgTransportTarget; DbgTransportSession * pTransport = NULL; - hr = pProxy->GetTransportForProcess(pProcessDescriptor, &pTransport, &hDummy); + hr = pProxy->GetTransportForProcess(pProcessDescriptor, &pTransport, &processWaitHandle); if (FAILED(hr)) { goto Label_Exit; @@ -134,6 +134,11 @@ HRESULT NewEventChannelForThisPlatform(CORDB_ADDRESS pLeftSideDCB, *ppEventChannel = pEventChannel; Label_Exit: + if (processWaitHandle != nullptr) + { + delete processWaitHandle; + } + if (FAILED(hr)) { if (pEventChannel != NULL) @@ -266,7 +271,7 @@ BOOL RemoteEventChannel::NeedToWaitForAck(DebuggerIPCEvent * pEvent) // Get a handle to wait on after sending an IPC event to the LS. The caller should call NeedToWaitForAck() // // virtual -HANDLE RemoteEventChannel::GetRightSideEventAckHandle() +minipal_event *RemoteEventChannel::GetRightSideEventAckHandle() { // Delegate to the transport which does the real work. return m_pTransport->GetIPCEventReadyEvent(); diff --git a/src/coreclr/debug/di/rspriv.h b/src/coreclr/debug/di/rspriv.h index fae880461662cd..7f33deb190095e 100644 --- a/src/coreclr/debug/di/rspriv.h +++ b/src/coreclr/debug/di/rspriv.h @@ -15,6 +15,7 @@ #include #include +#include "minipal-wait.h" #include @@ -3692,20 +3693,25 @@ class CordbProcess : RSSmartPtr m_cordb; private: - // OS process handle to live process. - // @dbgtodo - , Move this into the Shim. This should only be needed in the live-process - // case. Get rid of this since it breaks the data-target abstraction. - // For Mac debugging, this handle is of course not the real process handle. This is just a handle to - // wait on for process termination. - HANDLE m_handle; + // Process-exit waitable. On Windows this is an OS process handle. On Unix it is an opaque + // minipal object that is valid only with the minipal wait APIs and must not be exposed to clients. + minipal_process_wait *m_handle; // Process descriptor - holds PID and App group ID for Mac debugging ProcessDescriptor m_processDescriptor; public: - // Wrapper to get the OS process handle. This is unsafe because it breaks the data-target abstraction. - // The only things that need this should be calls to DuplicateHandle, and some shimming work. - HANDLE UnsafeGetProcessHandle() + // Windows-only callers may also use the returned value as the native process handle. + HANDLE UnsafeGetProcessHandle() + { +#ifdef HOST_WINDOWS + return m_handle == nullptr ? NULL : m_handle->GetRawHandle(); +#else + return NULL; +#endif + } + + minipal_process_wait *UnsafeGetProcessWaitHandle() { return m_handle; } @@ -3868,7 +3874,7 @@ class CordbProcess : DebuggerIPCRuntimeOffsets m_runtimeOffsets; - HANDLE m_leftSideEventAvailable; + minipal_event *m_leftSideEventAvailable; HANDLE m_leftSideEventRead; #if defined(FEATURE_INTEROP_DEBUGGING) HANDLE m_leftSideUnmanagedWaitEvent; @@ -10121,7 +10127,7 @@ class CordbWin32EventThread HANDLE m_thread; DWORD m_threadId; - HANDLE m_threadControlEvent; + minipal_event *m_threadControlEvent; HANDLE m_actionTakenEvent; BOOL m_run; @@ -10295,7 +10301,7 @@ class CordbRCEventThread HANDLE m_thread; DWORD m_threadId; BOOL m_run; - HANDLE m_threadControlEvent; + minipal_event *m_threadControlEvent; BOOL m_processStateChanged; }; diff --git a/src/coreclr/debug/di/shimremotedatatarget.cpp b/src/coreclr/debug/di/shimremotedatatarget.cpp index de3413e1c7fc1b..126ac61731dc41 100644 --- a/src/coreclr/debug/di/shimremotedatatarget.cpp +++ b/src/coreclr/debug/di/shimremotedatatarget.cpp @@ -190,14 +190,14 @@ HRESULT BuildPlatformSpecificDataTarget(MachineInfo machineInfo, const ProcessDescriptor * pProcessDescriptor, ShimDataTarget ** ppDataTarget) { - HandleHolder hDummy; + minipal_process_wait *processWaitHandle = nullptr; HRESULT hr = E_FAIL; ShimRemoteDataTarget * pRemoteDataTarget = NULL; DbgTransportTarget * pProxy = &g_DbgTransportTarget; DbgTransportSession * pTransport = NULL; - hr = pProxy->GetTransportForProcess(pProcessDescriptor, &pTransport, &hDummy); + hr = pProxy->GetTransportForProcess(pProcessDescriptor, &pTransport, &processWaitHandle); if (FAILED(hr)) { goto Label_Exit; @@ -221,6 +221,8 @@ HRESULT BuildPlatformSpecificDataTarget(MachineInfo machineInfo, pRemoteDataTarget->AddRef(); // must addref out-parameters Label_Exit: + delete processWaitHandle; + if (FAILED(hr)) { if (pRemoteDataTarget != NULL) diff --git a/src/coreclr/debug/di/windowspipeline.cpp b/src/coreclr/debug/di/windowspipeline.cpp index 9ed41ee7e1da76..39c7ff2bf45816 100644 --- a/src/coreclr/debug/di/windowspipeline.cpp +++ b/src/coreclr/debug/di/windowspipeline.cpp @@ -71,7 +71,7 @@ class WindowsNativePipeline : ); // Return a handle for the debuggee process. - virtual HANDLE GetProcessHandle(); + virtual minipal_process_wait *GetProcessHandle(); // Terminate the debuggee process. virtual BOOL TerminateProcess(UINT32 exitCode); @@ -156,19 +156,40 @@ BOOL WindowsNativePipeline::ContinueDebugEvent( } // Return a handle for the debuggee process. -HANDLE WindowsNativePipeline::GetProcessHandle() +minipal_process_wait *WindowsNativePipeline::GetProcessHandle() { _ASSERTE(m_dwProcessId != 0); - return ::OpenProcess(PROCESS_DUP_HANDLE | - PROCESS_QUERY_INFORMATION | - PROCESS_TERMINATE | - PROCESS_VM_OPERATION | - PROCESS_VM_READ | - PROCESS_VM_WRITE | - SYNCHRONIZE, - FALSE, - m_dwProcessId); + HANDLE processHandle = ::OpenProcess(PROCESS_DUP_HANDLE | + PROCESS_QUERY_INFORMATION | + PROCESS_TERMINATE | + PROCESS_VM_OPERATION | + PROCESS_VM_READ | + PROCESS_VM_WRITE | + SYNCHRONIZE, + FALSE, + m_dwProcessId); + if (processHandle == NULL) + { + return nullptr; + } + + minipal_process_wait *waitHandle = new (nothrow) minipal_process_wait(processHandle); + DWORD error = GetLastError(); + CloseHandle(processHandle); + + if (waitHandle == nullptr) + { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + } + else if (!waitHandle->IsValid()) + { + delete waitHandle; + waitHandle = nullptr; + SetLastError(error); + } + + return waitHandle; } // Terminate the debuggee process. diff --git a/src/coreclr/debug/ee/debugger.cpp b/src/coreclr/debug/ee/debugger.cpp index b4fbf49209acb5..8dd92201776b5b 100644 --- a/src/coreclr/debug/ee/debugger.cpp +++ b/src/coreclr/debug/ee/debugger.cpp @@ -2078,8 +2078,23 @@ void HelperThreadFavor::Init() CONTRACTL_END; // Create events for managing favors. - m_FavorReadEvent = CreateWin32EventOrThrow(NULL, kAutoResetEvent, FALSE); - m_FavorAvailableEvent = CreateWin32EventOrThrow(NULL, kAutoResetEvent, FALSE); + m_FavorReadEvent = new (nothrow) minipal_event(false, false); + if ((m_FavorReadEvent == nullptr) || !m_FavorReadEvent->IsValid()) + { + delete m_FavorReadEvent; + m_FavorReadEvent = nullptr; + ThrowOutOfMemory(); + } + + m_FavorAvailableEvent = new (nothrow) minipal_event(false, false); + if ((m_FavorAvailableEvent == nullptr) || !m_FavorAvailableEvent->IsValid()) + { + delete m_FavorAvailableEvent; + m_FavorAvailableEvent = nullptr; + delete m_FavorReadEvent; + m_FavorReadEvent = nullptr; + ThrowOutOfMemory(); + } } @@ -6809,10 +6824,9 @@ HRESULT Debugger::LaunchJitDebuggerAndNativeAttach(Thread * pThread, EXCEPTION_P } LOG((LF_CORDB, LL_INFO10000, "D::LJDANA: waiting on m_exUnmanagedAttachEvent and debugger's process handle\n")); - DWORD dwHandles = 2; - HANDLE arrHandles[2]; - arrHandles[0] = GetUnmanagedAttachEvent(); - arrHandles[1] = processInfo.hProcess; + minipal_event unmanagedAttachEvent(GetUnmanagedAttachEvent()); + minipal_process_wait debuggerProcess(processInfo.hProcess); + const minipal_wait_handle *waitSet[] = { &unmanagedAttachEvent, &debuggerProcess }; // Let the helper thread do the attach logic for us and wait for the // attach event. Must release the lock before blocking on a wait. @@ -6820,21 +6834,24 @@ HRESULT Debugger::LaunchJitDebuggerAndNativeAttach(Thread * pThread, EXCEPTION_P // Wait for one or the other to be set. Multiple threads could be waiting here. // The events are manual events, so when they go high, all threads will be released. - DWORD res = WaitForMultipleObjectsEx(dwHandles, arrHandles, FALSE, INFINITE, FALSE); + int32_t waitResult = minipal_wait_handle::Wait( + waitSet, + ARRAY_SIZE(waitSet), + MINIPAL_WAIT_INFINITE); // We no long need to keep handles to the debugger process. CloseHandle(processInfo.hProcess); CloseHandle(processInfo.hThread); // Indicate to the caller that the attach was aborted - if (res == WAIT_OBJECT_0 + 1) + if (waitResult == 1) { LOG((LF_CORDB, LL_INFO10000, "D::LJDANA: Debugger process is unexpectedly terminated!\n")); return E_FAIL; } // Otherwise, attach was successful (Note, only native attach is done so far) - _ASSERTE((res == WAIT_OBJECT_0) && "WaitForMultipleObjectsEx failed!"); + _ASSERTE((waitResult == 0) && "Debugger attach wait failed!"); LOG( (LF_CORDB, LL_INFO10000, "D::LJDANA: Leaving\n") ); return S_OK; #endif // TARGET_UNIX diff --git a/src/coreclr/debug/ee/debugger.h b/src/coreclr/debug/ee/debugger.h index d5341b4f68fee7..e7d9d4b413cf14 100644 --- a/src/coreclr/debug/ee/debugger.h +++ b/src/coreclr/debug/ee/debugger.h @@ -36,6 +36,7 @@ #include "eedbginterface.h" #include "dbginterface.h" #include "corhost.h" +#include "minipal-wait.h" #include "corjit.h" @@ -648,10 +649,10 @@ class HelperThreadFavor // that blew its stack FAVORCALLBACK m_fpFavor; void *m_pFavorData; - HANDLE m_FavorReadEvent; + minipal_event *m_FavorReadEvent; Crst m_FavorLock; - HANDLE m_FavorAvailableEvent; + minipal_event *m_FavorAvailableEvent; }; @@ -865,8 +866,8 @@ class DebuggerRCThread } Crst * GetFavorLock() { return &m_favorData.m_FavorLock; } - HANDLE GetFavorReadEvent() { return m_favorData.m_FavorReadEvent; } - HANDLE GetFavorAvailableEvent() { return m_favorData.m_FavorAvailableEvent; } + minipal_event *GetFavorReadEvent() { return m_favorData.m_FavorReadEvent; } + minipal_event *GetFavorAvailableEvent() { return m_favorData.m_FavorAvailableEvent; } HelperThreadFavor m_favorData; @@ -893,7 +894,8 @@ class DebuggerRCThread HANDLE m_thread; bool m_run; - HANDLE m_threadControlEvent; + minipal_event *m_threadControlEvent; + minipal_event *m_helperThreadExitedEvent; HANDLE m_helperThreadCanGoEvent; bool m_rgfInitRuntimeOffsets[IPC_TARGET_COUNT]; bool m_fDetachRightSide; diff --git a/src/coreclr/debug/ee/rcthread.cpp b/src/coreclr/debug/ee/rcthread.cpp index e60996b61960db..28989f3946d420 100644 --- a/src/coreclr/debug/ee/rcthread.cpp +++ b/src/coreclr/debug/ee/rcthread.cpp @@ -32,6 +32,7 @@ DebuggerRCThread::DebuggerRCThread(Debugger * pDebugger) m_thread(NULL), m_run(true), m_threadControlEvent(NULL), + m_helperThreadExitedEvent(NULL), m_helperThreadCanGoEvent(NULL), m_fDetachRightSide(false) { @@ -227,7 +228,7 @@ void DebuggerRCThread::WatchForStragglers(void) LOG((LF_CORDB,LL_INFO100000, "DRCT::WFS:setting event to watch " "for stragglers\n")); - SetEvent(m_threadControlEvent); + m_threadControlEvent->Set(); } //--------------------------------------------------------------------------------------- @@ -274,7 +275,23 @@ HRESULT DebuggerRCThread::Init(void) // Create the thread control event. - m_threadControlEvent = CreateWin32EventOrThrow(NULL, kAutoResetEvent, FALSE); + m_threadControlEvent = new (nothrow) minipal_event(false, false); + if ((m_threadControlEvent == nullptr) || !m_threadControlEvent->IsValid()) + { + delete m_threadControlEvent; + m_threadControlEvent = nullptr; + ThrowOutOfMemory(); + } + + m_helperThreadExitedEvent = new (nothrow) minipal_event(true, false); + if ((m_helperThreadExitedEvent == nullptr) || !m_helperThreadExitedEvent->IsValid()) + { + delete m_helperThreadExitedEvent; + m_helperThreadExitedEvent = nullptr; + delete m_threadControlEvent; + m_threadControlEvent = nullptr; + ThrowOutOfMemory(); + } // Create the helper thread can go event. m_helperThreadCanGoEvent = CreateWin32EventOrThrow(NULL, kManualResetEvent, TRUE); @@ -564,6 +581,16 @@ void DebuggerRCThread::ThreadProc(void) } CONTRACTL_END; + struct HelperThreadExitSignal + { + minipal_event *Event; + + ~HelperThreadExitSignal() + { + Event->Set(); + } + }; + STRESS_LOG_RESERVE_MEM (0); // This message actually serves a purpose (which is why it is always run) // The Stress log is run during hijacking, when other threads can be suspended @@ -651,7 +678,11 @@ void DebuggerRCThread::ThreadProc(void) // Mark that we're the true helper thread. Now that we've marked // this, no other threads will ever become the temporary helper // thread. + bool resetResult = m_helperThreadExitedEvent->Reset(); + _ASSERTE(resetResult); + (void)resetResult; m_pDCB->m_helperThreadId = GetCurrentThreadId(); + HelperThreadExitSignal helperThreadExitSignal = { m_helperThreadExitedEvent }; LOG((LF_CORDB, LL_INFO1000, "DRCT::TP: helper thread id is 0x%x helperThreadId\n", m_pDCB->m_helperThreadId)); @@ -827,7 +858,11 @@ void DebuggerRCThread::MainLoop() // threads doing helper duty. CantStopHolder cantStopHolder; - HANDLE rghWaitSet[DRCT_COUNT_FINAL]; + const minipal_wait_handle *waitSet[DRCT_COUNT_FINAL]; +#if !defined(FEATURE_DBGIPC_TRANSPORT_VM) + minipal_event rightSideEventAvailable(m_pDCB->m_rightSideEventAvailable.ImportToLocalProcess()); + minipal_process_wait *debuggerProcess = nullptr; +#endif #ifdef _DEBUG DWORD dwSyncSpinCount = 0; @@ -836,12 +871,12 @@ void DebuggerRCThread::MainLoop() // We start out just listening on RSEA and the thread control event... unsigned int cWaitCount = DRCT_COUNT_INITIAL; DWORD dwWaitTimeout = INFINITE; - rghWaitSet[DRCT_CONTROL_EVENT] = m_threadControlEvent; - rghWaitSet[DRCT_FAVORAVAIL] = GetFavorAvailableEvent(); + waitSet[DRCT_CONTROL_EVENT] = m_threadControlEvent; + waitSet[DRCT_FAVORAVAIL] = GetFavorAvailableEvent(); #if !defined(FEATURE_DBGIPC_TRANSPORT_VM) - rghWaitSet[DRCT_RSEA] = m_pDCB->m_rightSideEventAvailable; + waitSet[DRCT_RSEA] = &rightSideEventAvailable; #else - rghWaitSet[DRCT_RSEA] = g_pDbgTransport->GetIPCEventReadyEvent(); + waitSet[DRCT_RSEA] = g_pDbgTransport->GetIPCEventReadyEvent(); #endif // !FEATURE_DBGIPC_TRANSPORT_VM CONTRACT_VIOLATION(ThrowsViolation);// HndCreateHandle throws, and this loop is not backstopped by any EH @@ -860,7 +895,14 @@ void DebuggerRCThread::MainLoop() m_pDCB->m_rightSideProcessHandle.ImportToLocalProcess() != NULL) { _ASSERTE((cWaitCount + 1) == DRCT_COUNT_FINAL); - rghWaitSet[DRCT_DEBUGGER_EVENT] = m_pDCB->m_rightSideProcessHandle; + debuggerProcess = new (nothrow) minipal_process_wait( + m_pDCB->m_rightSideProcessHandle.ImportToLocalProcess()); + if ((debuggerProcess == nullptr) || !debuggerProcess->IsValid()) + { + delete debuggerProcess; + EEPOLICY_HANDLE_FATAL_ERROR(COR_E_OUTOFMEMORY); + } + waitSet[DRCT_DEBUGGER_EVENT] = debuggerProcess; cWaitCount = DRCT_COUNT_FINAL; } #endif // !FEATURE_DBGIPC_TRANSPORT_VM @@ -874,13 +916,15 @@ void DebuggerRCThread::MainLoop() _ASSERTE(cWaitCount == DRCT_COUNT_FINAL); _ASSERTE((cWaitCount - 1) == DRCT_COUNT_INITIAL); - rghWaitSet[DRCT_DEBUGGER_EVENT] = NULL; + delete debuggerProcess; + debuggerProcess = nullptr; + waitSet[DRCT_DEBUGGER_EVENT] = nullptr; cWaitCount = DRCT_COUNT_INITIAL; #endif // !FEATURE_DBGIPC_TRANSPORT_VM } // Wait for an event from the Right Side. - DWORD dwWaitResult = WaitForMultipleObjectsEx(cWaitCount, rghWaitSet, FALSE, dwWaitTimeout, FALSE); + int32_t waitResult = minipal_wait_handle::Wait(waitSet, cWaitCount, dwWaitTimeout); if (!m_run) { @@ -888,7 +932,7 @@ void DebuggerRCThread::MainLoop() } - if (dwWaitResult == WAIT_OBJECT_0 + DRCT_DEBUGGER_EVENT) + if (waitResult == DRCT_DEBUGGER_EVENT) { // If the handle of the right side process is signaled, then we've lost our controlling debugger. We // terminate this process immediately in such a case. @@ -897,7 +941,7 @@ void DebuggerRCThread::MainLoop() EEPOLICY_HANDLE_FATAL_ERROR(0); _ASSERTE(!"Should never reach this point."); } - else if (dwWaitResult == WAIT_OBJECT_0 + DRCT_FAVORAVAIL) + else if (waitResult == DRCT_FAVORAVAIL) { // execute the callback set by DoFavor() FAVORCALLBACK fpCallback = GetFavorFnPtr(); @@ -907,10 +951,10 @@ void DebuggerRCThread::MainLoop() if (fpCallback) { (*fpCallback)(GetFavorData()); - SetEvent(GetFavorReadEvent()); + GetFavorReadEvent()->Set(); } } - else if (dwWaitResult == WAIT_OBJECT_0 + DRCT_RSEA) + else if (waitResult == DRCT_RSEA) { bool fWasContinue = HandleRSEA(); @@ -940,7 +984,7 @@ void DebuggerRCThread::MainLoop() } } - else if (dwWaitResult == WAIT_OBJECT_0 + DRCT_CONTROL_EVENT) + else if (waitResult == DRCT_CONTROL_EVENT) { LOG((LF_CORDB, LL_INFO1000, "DRCT::ML:: straggler event set.\n")); @@ -966,7 +1010,7 @@ void DebuggerRCThread::MainLoop() // dbgLockHolder goes out of scope - implicit Release // tsl goes out of scope - implicit Release } - else if (dwWaitResult == WAIT_TIMEOUT) + else if (waitResult == MINIPAL_WAIT_TIMEOUT) { LWaitTimedOut: @@ -1036,6 +1080,10 @@ void DebuggerRCThread::MainLoop() } } +#if !defined(FEATURE_DBGIPC_TRANSPORT_VM) + delete debuggerProcess; +#endif + STRESS_LOG0(LF_CORDB, LL_INFO1000, "DRCT::ML:: Exiting.\n"); } @@ -1076,7 +1124,10 @@ void DebuggerRCThread::TemporaryHelperThreadMainLoop() // threads doing helper duty. CantStopHolder cantStopHolder; - HANDLE rghWaitSet[DRCT_COUNT_FINAL]; + const minipal_wait_handle *waitSet[DRCT_COUNT_FINAL]; +#if !defined(FEATURE_DBGIPC_TRANSPORT_VM) + minipal_event rightSideEventAvailable(m_pDCB->m_rightSideEventAvailable.ImportToLocalProcess()); +#endif #ifdef _DEBUG DWORD dwSyncSpinCount = 0; @@ -1085,12 +1136,12 @@ void DebuggerRCThread::TemporaryHelperThreadMainLoop() // We start out just listening on RSEA and the thread control event... unsigned int cWaitCount = DRCT_COUNT_INITIAL; DWORD dwWaitTimeout = INFINITE; - rghWaitSet[DRCT_CONTROL_EVENT] = m_threadControlEvent; - rghWaitSet[DRCT_FAVORAVAIL] = GetFavorAvailableEvent(); + waitSet[DRCT_CONTROL_EVENT] = m_threadControlEvent; + waitSet[DRCT_FAVORAVAIL] = GetFavorAvailableEvent(); #if !defined(FEATURE_DBGIPC_TRANSPORT_VM) - rghWaitSet[DRCT_RSEA] = m_pDCB->m_rightSideEventAvailable; + waitSet[DRCT_RSEA] = &rightSideEventAvailable; #else //FEATURE_DBGIPC_TRANSPORT_VM - rghWaitSet[DRCT_RSEA] = g_pDbgTransport->GetIPCEventReadyEvent(); + waitSet[DRCT_RSEA] = g_pDbgTransport->GetIPCEventReadyEvent(); #endif // !FEATURE_DBGIPC_TRANSPORT_VM CONTRACT_VIOLATION(ThrowsViolation);// HndCreateHandle throws, and this loop is not backstopped by any EH @@ -1100,7 +1151,7 @@ void DebuggerRCThread::TemporaryHelperThreadMainLoop() LOG((LF_CORDB, LL_INFO1000, "DRCT::ML: waiting for event.\n")); // Wait for an event from the Right Side. - DWORD dwWaitResult = WaitForMultipleObjectsEx(cWaitCount, rghWaitSet, FALSE, dwWaitTimeout, FALSE); + int32_t waitResult = minipal_wait_handle::Wait(waitSet, cWaitCount, dwWaitTimeout); if (!m_run) { @@ -1108,7 +1159,7 @@ void DebuggerRCThread::TemporaryHelperThreadMainLoop() } - if (dwWaitResult == WAIT_OBJECT_0 + DRCT_DEBUGGER_EVENT) + if (waitResult == DRCT_DEBUGGER_EVENT) { // If the handle of the right side process is signaled, then we've lost our controlling debugger. We // terminate this process immediately in such a case. @@ -1117,14 +1168,14 @@ void DebuggerRCThread::TemporaryHelperThreadMainLoop() TerminateProcess(GetCurrentProcess(), 0); _ASSERTE(!"Should never reach this point."); } - else if (dwWaitResult == WAIT_OBJECT_0 + DRCT_FAVORAVAIL) + else if (waitResult == DRCT_FAVORAVAIL) { // execute the callback set by DoFavor() (*GetFavorFnPtr())(GetFavorData()); - SetEvent(GetFavorReadEvent()); + GetFavorReadEvent()->Set(); } - else if (dwWaitResult == WAIT_OBJECT_0 + DRCT_RSEA) + else if (waitResult == DRCT_RSEA) { // @todo: // We are only interested in dealing with Continue event here... @@ -1147,7 +1198,7 @@ void DebuggerRCThread::TemporaryHelperThreadMainLoop() goto LExit; } } - else if (dwWaitResult == WAIT_OBJECT_0 + DRCT_CONTROL_EVENT) + else if (waitResult == DRCT_CONTROL_EVENT) { LOG((LF_CORDB, LL_INFO1000, "DRCT::THTML:: straggler event set.\n")); @@ -1163,7 +1214,7 @@ void DebuggerRCThread::TemporaryHelperThreadMainLoop() // goto LWaitTimedOut; } - else if (dwWaitResult == WAIT_TIMEOUT) + else if (waitResult == MINIPAL_WAIT_TIMEOUT) { LWaitTimedOut: @@ -1385,7 +1436,7 @@ HRESULT DebuggerRCThread::AsyncStop(void) // We need to get the helper thread out of its wait loop. So ping the thread-control event. // (Don't ping RSEA since that event should be used only for IPC communication). // Don't bother waiting for it to exit. - SetEvent(this->m_threadControlEvent); + this->m_threadControlEvent->Set(); return hr; } @@ -1640,7 +1691,7 @@ void DebuggerRCThread::DoFavor(FAVORCALLBACK fp, void * pData) // Our main message loop operating on the Helper thread will // pickup that event, call the fp, and set the Read event - SetEvent(GetFavorAvailableEvent()); + GetFavorAvailableEvent()->Set(); LOG((LF_CORDB, LL_INFO10000, "DRCT::DF - Waiting on FavorReadEvent for favor %p\n", (void*)fp)); @@ -1660,7 +1711,19 @@ void DebuggerRCThread::DoFavor(FAVORCALLBACK fp, void * pData) // or totally executed the favor but not yet signaled the FavorReadEvent. We don't // know what it did, so we don't know what we can do; so we're in an unstable state. - const HANDLE waitset [] = { GetFavorReadEvent(), m_thread }; + const minipal_wait_handle *waitSet[] = { + GetFavorReadEvent(), +#ifdef HOST_WINDOWS + // Preserve detection of abnormal helper termination through the native thread handle. + nullptr +#else + m_helperThreadExitedEvent +#endif + }; +#ifdef HOST_WINDOWS + minipal_process_wait helperThread(m_thread); + waitSet[1] = &helperThread; +#endif // the favor worker thread will require a transition to cooperative mode in order to complete its work and we will // wait for the favor to complete before terminating the process. if there is a GC in progress the favor thread @@ -1668,19 +1731,15 @@ void DebuggerRCThread::DoFavor(FAVORCALLBACK fp, void * pData) // preemptive mode before waiting for the favor to complete (see Dev11 72349). GCX_PREEMP(); - DWORD ret = WaitForMultipleObjectsEx( - ARRAY_SIZE(waitset), - waitset, - FALSE, - INFINITE, - FALSE - ); + int32_t waitResult = minipal_wait_handle::Wait( + waitSet, + ARRAY_SIZE(waitSet), + MINIPAL_WAIT_INFINITE); - DWORD wn = (ret - WAIT_OBJECT_0); - if (wn == 0) // m_FavorEventRead + if (waitResult == 0) { // Favor was executed, nothing to do here. - LOG((LF_CORDB, LL_INFO10000, "DRCT::DF - favor %p finished, ret = %d\n", (void*)fp, ret)); + LOG((LF_CORDB, LL_INFO10000, "DRCT::DF - favor %p finished, ret = %d\n", (void*)fp, waitResult)); } else { @@ -1689,15 +1748,15 @@ void DebuggerRCThread::DoFavor(FAVORCALLBACK fp, void * pData) // Since we have no timeout, we shouldn't be able to get an error on the wait, // but just in case ... - _ASSERTE(ret != WAIT_FAILED); - _ASSERTE((wn == 1) && !"DoFavor - unexpected return from WFMO"); + _ASSERTE(waitResult != MINIPAL_WAIT_FAILED); + _ASSERTE((waitResult == 1) && !"DoFavor - unexpected wait result"); // Thread exited without doing favor, so execute it on our thread. // If we're here because of a stack overflow, this may push us over the edge, // but there's nothing else we can really do (*fp)(pData); - ResetEvent(GetFavorAvailableEvent()); + GetFavorAvailableEvent()->Reset(); } // m_fpFavor & m_pFavorData are meaningless now. We could set them @@ -1783,4 +1842,3 @@ void DebuggerRCThread::EarlyHelperThreadDeath(void) // dbgLockHolder goes out of scope - implicit Release } } - diff --git a/src/coreclr/debug/inc/dbgtransportsession.h b/src/coreclr/debug/inc/dbgtransportsession.h index 74bb8ab2f7b09b..5df30d9e1e3349 100644 --- a/src/coreclr/debug/inc/dbgtransportsession.h +++ b/src/coreclr/debug/inc/dbgtransportsession.h @@ -12,6 +12,7 @@ #include #include +#include "minipal-wait.h" #if defined(FEATURE_DBGIPC_TRANSPORT_VM) || defined(FEATURE_DBGIPC_TRANSPORT_DI) @@ -358,7 +359,7 @@ class DbgTransportSession // requires the addresses of a couple of runtime data structures to service certain debugger requests that // may be delivered once the session is established. #ifdef RIGHT_SIDE_COMPILE - HRESULT Init(const ProcessDescriptor& pd, HANDLE hProcessExited); + HRESULT Init(const ProcessDescriptor& pd, const minipal_process_wait& processExited); #else HRESULT Init(DebuggerIPCControlBlock * pDCB); #endif // RIGHT_SIDE_COMPILE @@ -424,8 +425,8 @@ class DbgTransportSession // Retrieves the auto-reset handle which is signalled by the session each time a new event is received // from the other side. - HANDLE GetIPCEventReadyEvent(); - HANDLE GetDebugEventReadyEvent(); + minipal_event *GetIPCEventReadyEvent(); + minipal_event *GetDebugEventReadyEvent(); // Copies the last event received from the other side into the provided buffer. This should only be called // (once) after the event returned from GetIPCEventReadyEvent()/GetDebugEventReadyEvent() has been signalled. @@ -560,7 +561,7 @@ class DbgTransportSession MessageHeader m_sHeader; // Inline message header PBYTE m_pbDataBlock; // Pointer to optional message data block (or NULL) DWORD m_cbDataBlock; // Count of bytes in above block if it's non-NULL - HANDLE m_hReplyEvent; // Optional event to signal if this message is replied to (or NULL) + minipal_event *m_hReplyEvent; // Optional event to signal if this message is replied to (or NULL) PBYTE m_pbReplyBlock; // Optional buffer to place data block from reply into (or NULL) DWORD m_cbReplyBlock; // Size in bytes of the above buffer if it is non-NULL Message *m_pOrigMessage; // Used when we need to find the original message from a copy @@ -642,7 +643,7 @@ class DbgTransportSession #ifdef RIGHT_SIDE_COMPILE // Manual reset event that is signalled whenever the session state is SS_Open or SS_Closed (after waiting // on this event the caller should check to see which state it was). - HANDLE m_hSessionOpenEvent; + minipal_event *m_hSessionOpenEvent; #endif // RIGHT_SIDE_COMPILE // Thread responsible for initial Connect()/Accept() on a low level transport connection and @@ -656,7 +657,7 @@ class DbgTransportSession // On the RS the transport thread needs to know the IP address and port number to Connect() to. ProcessDescriptor m_pd; // Descriptor of a process we're talking to. - HANDLE m_hProcessExited; // event which will be signaled when the debuggee is terminated + minipal_process_wait *m_hProcessExited; // wait which will be signaled when the debuggee is terminated bool m_fDebuggerAttached; #endif @@ -681,7 +682,7 @@ class DbgTransportSession DWORD m_cValidEventBuffers; // Number of events that actually contain data DWORD m_idxEventBufferHead; // Index of the first valid event DWORD m_idxEventBufferTail; // Index of the first invalid event - HANDLE m_rghEventReadyEvent[IPCET_Max]; // The event signalled when a new event arrives + minipal_event *m_rghEventReadyEvent[IPCET_Max]; // The event signalled when a new event arrives #ifndef RIGHT_SIDE_COMPILE // The LS requires the addresses of a couple of runtime data structures in order to service MT_GetDCB etc. diff --git a/src/coreclr/debug/shared/dbgtransportsession.cpp b/src/coreclr/debug/shared/dbgtransportsession.cpp index 6f8083f0690f92..f5721b53ecf92e 100644 --- a/src/coreclr/debug/shared/dbgtransportsession.cpp +++ b/src/coreclr/debug/shared/dbgtransportsession.cpp @@ -53,18 +53,18 @@ DbgTransportSession::~DbgTransportSession() if (m_hTransportThread) CloseHandle(m_hTransportThread); if (m_rghEventReadyEvent[IPCET_OldStyle]) - CloseHandle(m_rghEventReadyEvent[IPCET_OldStyle]); + delete m_rghEventReadyEvent[IPCET_OldStyle]; if (m_rghEventReadyEvent[IPCET_DebugEvent]) - CloseHandle(m_rghEventReadyEvent[IPCET_DebugEvent]); + delete m_rghEventReadyEvent[IPCET_DebugEvent]; if (m_pEventBuffers) delete [] m_pEventBuffers; #ifdef RIGHT_SIDE_COMPILE if (m_hSessionOpenEvent) - CloseHandle(m_hSessionOpenEvent); + delete m_hSessionOpenEvent; if (m_hProcessExited) - CloseHandle(m_hProcessExited); + delete m_hProcessExited; #endif // RIGHT_SIDE_COMPILE if (m_fInitStateLock) @@ -81,7 +81,7 @@ DbgTransportSession::~DbgTransportSession() // addresses of a couple of runtime data structures to service certain debugger requests that may be delivered // once the session is established. #ifdef RIGHT_SIDE_COMPILE -HRESULT DbgTransportSession::Init(const ProcessDescriptor& pd, HANDLE hProcessExited) +HRESULT DbgTransportSession::Init(const ProcessDescriptor& pd, const minipal_process_wait& processExited) #else // RIGHT_SIDE_COMPILE HRESULT DbgTransportSession::Init(DebuggerIPCControlBlock *pDCB) #endif // RIGHT_SIDE_COMPILE @@ -104,21 +104,22 @@ HRESULT DbgTransportSession::Init(DebuggerIPCControlBlock *pDCB) return E_FAIL; m_pd = pd; - if (!DuplicateHandle(GetCurrentProcess(), - hProcessExited, - GetCurrentProcess(), - &m_hProcessExited, - 0, // ignored since we are going to pass DUPLICATE_SAME_ACCESS - FALSE, - DUPLICATE_SAME_ACCESS)) + m_hProcessExited = new (nothrow) minipal_process_wait(processExited); + if ((m_hProcessExited == nullptr) || !m_hProcessExited->IsValid()) { - return HRESULT_FROM_GetLastError(); + delete m_hProcessExited; + m_hProcessExited = nullptr; + return E_FAIL; } m_fDebuggerAttached = false; - m_hSessionOpenEvent = CreateEvent(NULL, TRUE, FALSE, NULL); // Manual reset, not signalled - if (m_hSessionOpenEvent == NULL) + m_hSessionOpenEvent = new (nothrow) minipal_event(true, false); + if ((m_hSessionOpenEvent == nullptr) || !m_hSessionOpenEvent->IsValid()) + { + delete m_hSessionOpenEvent; + m_hSessionOpenEvent = nullptr; return E_OUTOFMEMORY; + } #else // RIGHT_SIDE_COMPILE m_pDCB = pDCB; @@ -139,13 +140,23 @@ HRESULT DbgTransportSession::Init(DebuggerIPCControlBlock *pDCB) if (m_pEventBuffers == NULL) return E_OUTOFMEMORY; - m_rghEventReadyEvent[IPCET_OldStyle] = CreateEvent(NULL, FALSE, FALSE, NULL); // Auto reset, not signalled - if (m_rghEventReadyEvent[IPCET_OldStyle] == NULL) + m_rghEventReadyEvent[IPCET_OldStyle] = new (nothrow) minipal_event(false, false); + if ((m_rghEventReadyEvent[IPCET_OldStyle] == nullptr) || + !m_rghEventReadyEvent[IPCET_OldStyle]->IsValid()) + { + delete m_rghEventReadyEvent[IPCET_OldStyle]; + m_rghEventReadyEvent[IPCET_OldStyle] = nullptr; return E_OUTOFMEMORY; + } - m_rghEventReadyEvent[IPCET_DebugEvent] = CreateEvent(NULL, FALSE, FALSE, NULL); // Auto reset, not signalled - if (m_rghEventReadyEvent[IPCET_DebugEvent] == NULL) + m_rghEventReadyEvent[IPCET_DebugEvent] = new (nothrow) minipal_event(false, false); + if ((m_rghEventReadyEvent[IPCET_DebugEvent] == nullptr) || + !m_rghEventReadyEvent[IPCET_DebugEvent]->IsValid()) + { + delete m_rghEventReadyEvent[IPCET_DebugEvent]; + m_rghEventReadyEvent[IPCET_DebugEvent] = nullptr; return E_OUTOFMEMORY; + } // Start the transport thread which handles forming and re-forming connections, driving the session // state to SS_Open and receiving and initially processing all incoming traffic. @@ -203,7 +214,7 @@ void DbgTransportSession::Shutdown() #ifdef RIGHT_SIDE_COMPILE // Signal the m_hSessionOpenEvent now to quickly error out any callers of WaitForSessionToOpen(). - SetEvent(m_hSessionOpenEvent); + m_hSessionOpenEvent->Set(); #endif // RIGHT_SIDE_COMPILE } @@ -245,14 +256,14 @@ void DbgTransportSession::CleanupTargetProcess() // returns true if the session opened within the time given (in milliseconds) and false otherwise. bool DbgTransportSession::WaitForSessionToOpen(DWORD dwTimeout) { - DWORD dwRet = WaitForSingleObject(m_hSessionOpenEvent, dwTimeout); + int32_t waitResult = minipal_wait_handle::Wait(*m_hSessionOpenEvent, dwTimeout); if (m_eState == SS_Closed) return false; - if (dwRet == WAIT_TIMEOUT) + if (waitResult == MINIPAL_WAIT_TIMEOUT) DbgTransportLog(LC_Proxy, "DbgTransportSession::WaitForSessionToOpen(%u) timed out", dwTimeout); - return dwRet == WAIT_OBJECT_0; + return waitResult == 0; } //--------------------------------------------------------------------------------------- @@ -343,14 +354,14 @@ HRESULT DbgTransportSession::SendDebugEvent(DebuggerIPCEvent * pEvent) // Retrieves the auto-reset handle which is signalled by the session each time a new event is received from // the other side. -HANDLE DbgTransportSession::GetIPCEventReadyEvent() +minipal_event *DbgTransportSession::GetIPCEventReadyEvent() { return m_rghEventReadyEvent[IPCET_OldStyle]; } // Retrieves the auto-reset handle which is signalled by the session each time a new event (disguised as a // debug event) is received from the other side. -HANDLE DbgTransportSession::GetDebugEventReadyEvent() +minipal_event *DbgTransportSession::GetDebugEventReadyEvent() { return m_rghEventReadyEvent[IPCET_DebugEvent]; } @@ -382,7 +393,7 @@ void DbgTransportSession::GetNextEvent(DebuggerIPCEvent *pEvent, DWORD cbEvent) // If there's at least one more valid event we can signal event ready now. if (m_cValidEventBuffers) { - SetEvent(m_rghEventReadyEvent[m_pEventBuffers[m_idxEventBufferHead].m_type]); + m_rghEventReadyEvent[m_pEventBuffers[m_idxEventBufferHead].m_type]->Set(); } } @@ -694,35 +705,33 @@ HRESULT DbgTransportSession::SendMessage(Message *pMessage, bool fWaitsForReply) HRESULT DbgTransportSession::SendRequestMessageAndWait(Message *pMessage) { // Allocate event to wait for reply on. - pMessage->m_hReplyEvent = CreateEvent(NULL, FALSE, FALSE, NULL); // Auto-reset, not signalled - if (pMessage->m_hReplyEvent == NULL) + pMessage->m_hReplyEvent = new (nothrow) minipal_event(false, false); + if ((pMessage->m_hReplyEvent == nullptr) || !pMessage->m_hReplyEvent->IsValid()) + { + delete pMessage->m_hReplyEvent; + pMessage->m_hReplyEvent = nullptr; return E_OUTOFMEMORY; + } - // Duplicate the handle to the event. It's necessary to have two handles to the same event because - // both this thread and the message pumping thread may be trying to access the handle at the same - // time (e.g. closing the handle). So we make a duplicate handle. This thread is responsible for - // closing hReplyEvent (the local variable) whereas the message pumping thread is responsible for - // closing the handle on the message. - HANDLE hReplyEvent = NULL; - if (!DuplicateHandle(GetCurrentProcess(), - pMessage->m_hReplyEvent, - GetCurrentProcess(), - &hReplyEvent, - 0, // ignored since we are going to pass DUPLICATE_SAME_ACCESS - FALSE, - DUPLICATE_SAME_ACCESS)) + // Acquire a second owned reference to the event because both this thread and the message pumping + // thread may release their references at the same time. This thread owns hReplyEvent while the + // message pumping thread owns the reference on the message. + minipal_event hReplyEvent(*pMessage->m_hReplyEvent); + if (!hReplyEvent.IsValid()) { - return HRESULT_FROM_GetLastError(); + delete pMessage->m_hReplyEvent; + pMessage->m_hReplyEvent = nullptr; + return E_FAIL; } // Send the request. HRESULT hr = SendMessage(pMessage, true); if (FAILED(hr)) { - // In this case, we need to close both handles since the message is never put into the send queue. + // Release both references since the message is never put into the send queue. // This thread is the only one who has access to the message. - CloseHandle(pMessage->m_hReplyEvent); - CloseHandle(hReplyEvent); + delete pMessage->m_hReplyEvent; + pMessage->m_hReplyEvent = nullptr; return hr; } @@ -732,25 +741,26 @@ HRESULT DbgTransportSession::SendRequestMessageAndWait(Message *pMessage) // Wait for a reply (by the time this event is signalled the message header will have been overwritten by // the reply and any output buffer provided will have been filled in). #if defined(RIGHT_SIDE_COMPILE) - HANDLE rgEvents[] = { hReplyEvent, m_hProcessExited }; + const minipal_wait_handle *rgEvents[] = { &hReplyEvent, m_hProcessExited }; #else // !RIGHT_SIDE_COMPILE - HANDLE rgEvents[] = { hReplyEvent }; + const minipal_wait_handle *rgEvents[] = { &hReplyEvent }; #endif // RIGHT_SIDE_COMPILE - DWORD dwResult = WaitForMultipleObjectsEx(sizeof(rgEvents)/sizeof(rgEvents[0]), rgEvents, FALSE, INFINITE, FALSE); + int32_t waitResult = minipal_wait_handle::Wait( + rgEvents, + ARRAY_SIZE(rgEvents), + MINIPAL_WAIT_INFINITE); - if (dwResult == WAIT_OBJECT_0) + if (waitResult == 0) { // This is the normal case. The message pumping thread receives a reply from the debuggee process. // It signals the event to wake up this thread. - CloseHandle(hReplyEvent); - // Check whether the session aborted us due to a Shutdown(). if (pMessage->m_fAborted) return E_ABORT; } #if defined(RIGHT_SIDE_COMPILE) - else if (dwResult == (WAIT_OBJECT_0 + 1)) + else if (waitResult == 1) { // This is the complicated case. This thread wakes up because the debuggee process is terminated. // At the same time, the message pumping thread may be in the process of handling the reply message. @@ -769,17 +779,20 @@ HRESULT DbgTransportSession::SendRequestMessageAndWait(Message *pMessage) // Fortunately, in this case, we know the message pumping thread is going to signal the event. if (pOriginalMessage == NULL) { - WaitForSingleObject(hReplyEvent, INFINITE); + minipal_wait_handle::Wait(hReplyEvent, MINIPAL_WAIT_INFINITE); + } + else + { + delete pOriginalMessage->m_hReplyEvent; + pOriginalMessage->m_hReplyEvent = nullptr; } - CloseHandle(hReplyEvent); return CORDBG_E_PROCESS_TERMINATED; } #endif // RIGHT_SIDE_COMPILE else { // Should never get here. - CloseHandle(hReplyEvent); UNREACHABLE(); } @@ -1033,7 +1046,7 @@ bool DbgTransportSession::ProcessReply(MessageHeader *pHeader) //--------------------------------------------------------------------------------------- // // Upon receiving a reply message, signal the event on the message to wake up the thread waiting for -// the reply message and close the handle to the event. +// the reply message and release its event reference. // // Arguments: // pMessage - the reply message to be processed @@ -1044,11 +1057,12 @@ void DbgTransportSession::SignalReplyEvent(Message * pMessage) // Make a local copy of the event handle. As soon as we signal the event, the thread blocked waiting on // the reply may wake up and trash the message. See code:DbgTransportSession::SendRequestMessageAndWait() // for more info. - HANDLE hReplyEvent = pMessage->m_hReplyEvent; - _ASSERTE(hReplyEvent != NULL); + minipal_event *pReplyEvent = pMessage->m_hReplyEvent; + _ASSERTE(pReplyEvent != nullptr); + pMessage->m_hReplyEvent = nullptr; - SetEvent(hReplyEvent); - CloseHandle(hReplyEvent); + pReplyEvent->Set(); + delete pReplyEvent; } //--------------------------------------------------------------------------------------- @@ -1250,7 +1264,7 @@ void DbgTransportSession::TransportWorker() #ifdef RIGHT_SIDE_COMPILE // The session is definitely not open at this point. - ResetEvent(m_hSessionOpenEvent); + m_hSessionOpenEvent->Reset(); // On the right side we initiate the connection via Connect(). A failure is dealt with by waiting a // little while and retrying (the LS may take a little while to set up). If there's nobody listening @@ -1500,7 +1514,7 @@ void DbgTransportSession::TransportWorker() #ifdef RIGHT_SIDE_COMPILE // Signal any WaitForSessionToOpen() waiters that we've gotten to SS_Open. - SetEvent(m_hSessionOpenEvent); + m_hSessionOpenEvent->Set(); #endif // RIGHT_SIDE_COMPILE // We're ready to begin receiving normal incoming messages now. @@ -1868,7 +1882,7 @@ void DbgTransportSession::TransportWorker() // If we just added the first valid event then wake up the client so they can call // GetNextEvent(). if (m_cValidEventBuffers == 1) - SetEvent(m_rghEventReadyEvent[m_pEventBuffers[idxCurrentEvent].m_type]); + m_rghEventReadyEvent[m_pEventBuffers[idxCurrentEvent].m_type]->Set(); } } break; @@ -2033,7 +2047,7 @@ void DbgTransportSession::TransportWorker() #ifdef RIGHT_SIDE_COMPILE // The session is definitely not open at this point. - ResetEvent(m_hSessionOpenEvent); + m_hSessionOpenEvent->Reset(); #endif // RIGHT_SIDE_COMPILE // Close the connection if we haven't done so already. diff --git a/src/coreclr/dlls/mscordbi/CMakeLists.txt b/src/coreclr/dlls/mscordbi/CMakeLists.txt index a1b7147ff4be3e..e53f0b0de126cf 100644 --- a/src/coreclr/dlls/mscordbi/CMakeLists.txt +++ b/src/coreclr/dlls/mscordbi/CMakeLists.txt @@ -67,6 +67,7 @@ if(CLR_CMAKE_HOST_UNIX) endif(CLR_CMAKE_HOST_UNIX) set(COREDBI_LIBRARIES + coreclrminipal debug-pal cordbdi dbgutil diff --git a/src/coreclr/inc/cordebug.idl b/src/coreclr/inc/cordebug.idl index 5dfcb037abc5a1..9204c3944427e0 100644 --- a/src/coreclr/inc/cordebug.idl +++ b/src/coreclr/inc/cordebug.idl @@ -2690,9 +2690,10 @@ interface ICorDebugProcess : ICorDebugController HRESULT GetID([out] DWORD *pdwProcessId); /* - * GetHandle returns a handle to the process. This handle is owned - * by the debugging API; the debugger should duplicate it before - * using it. + * GetHandle returns a handle to the process on Windows. This handle + * is owned by the debugging API; the debugger should duplicate it + * before using it. On Unix, this method returns an opaque sentinel + * that must not be used as a native process handle. */ HRESULT GetHandle([out] HPROCESS *phProcessHandle); diff --git a/src/coreclr/minipal/CMakeLists.txt b/src/coreclr/minipal/CMakeLists.txt index 298e1d6fba3819..bc316de3968363 100644 --- a/src/coreclr/minipal/CMakeLists.txt +++ b/src/coreclr/minipal/CMakeLists.txt @@ -5,3 +5,7 @@ if (CLR_CMAKE_HOST_UNIX) else (CLR_CMAKE_HOST_UNIX) add_subdirectory(Windows) endif (CLR_CMAKE_HOST_UNIX) + +if (NOT CLR_CMAKE_TARGET_ARCH_WASM) + add_subdirectory(tests) +endif() diff --git a/src/coreclr/minipal/Unix/CMakeLists.txt b/src/coreclr/minipal/Unix/CMakeLists.txt index 969c502323ea79..ba6aa8472b206f 100644 --- a/src/coreclr/minipal/Unix/CMakeLists.txt +++ b/src/coreclr/minipal/Unix/CMakeLists.txt @@ -5,6 +5,10 @@ set(SOURCES memory.cpp ) +if (NOT CLR_CMAKE_TARGET_ARCH_WASM) + list(APPEND SOURCES wait.cpp) +endif() + add_library(coreclrminipal_objects OBJECT ${SOURCES} @@ -17,6 +21,12 @@ add_library(coreclrminipal target_link_libraries(coreclrminipal PRIVATE minipal) +if (HAVE_PIPE2) + target_compile_definitions(coreclrminipal_objects PRIVATE HAVE_PIPE2=1) +else() + target_compile_definitions(coreclrminipal_objects PRIVATE HAVE_PIPE2=0) +endif() + if (CLR_CMAKE_TARGET_ARCH_WASM) install_clr(TARGETS coreclrminipal DESTINATIONS sharedFramework COMPONENT runtime) endif (CLR_CMAKE_TARGET_ARCH_WASM) diff --git a/src/coreclr/minipal/Unix/wait.cpp b/src/coreclr/minipal/Unix/wait.cpp new file mode 100644 index 00000000000000..e3804ecd9991a2 --- /dev/null +++ b/src/coreclr/minipal/Unix/wait.cpp @@ -0,0 +1,1156 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(TARGET_LINUX) +#include +#endif // TARGET_LINUX + +#if defined(TARGET_APPLE) || defined(TARGET_FREEBSD) || defined(TARGET_NETBSD) || defined(TARGET_OPENBSD) +#include +#define MINIPAL_WAIT_USES_KQUEUE 1 +#else +#define MINIPAL_WAIT_USES_KQUEUE 0 +#endif // kqueue platforms + +#include + +#include "minipal-wait.h" + +namespace +{ + enum class WaitableKind + { + Event, + ProcessFileDescriptor, + ProcessKqueue, + ProcessPipe, + }; + + enum class AcquireResult + { + Failed, + NotReady, + Ready, + }; + + struct Waitable + { + int32_t refCount; + int32_t publicRefCount; + int32_t watcherRunning; + WaitableKind kind; + minipal_mutex mutex; + bool mutexInitialized; + bool manualReset; + bool signaled; + int readFileDescriptor; + int writeFileDescriptor; + int processFileDescriptor; + int cancellationReadFileDescriptor; + int cancellationWriteFileDescriptor; + int errorCode; + pid_t processId; + pthread_t watcherThread; + bool watcherStarted; + }; + + void CloseFileDescriptor(int fileDescriptor) + { + if (fileDescriptor >= 0) + { + close(fileDescriptor); + } + } + + bool SetCloseOnExec(int fileDescriptor) + { + int descriptorFlags; + do + { + descriptorFlags = fcntl(fileDescriptor, F_GETFD); + } while (descriptorFlags < 0 && errno == EINTR); + + if (descriptorFlags < 0) + { + return false; + } + + int result; + do + { + result = fcntl(fileDescriptor, F_SETFD, descriptorFlags | FD_CLOEXEC); + } while (result < 0 && errno == EINTR); + + return result == 0; + } + + bool SetPipeFlags(int fileDescriptor) + { + int statusFlags; + do + { + statusFlags = fcntl(fileDescriptor, F_GETFL); + } while (statusFlags < 0 && errno == EINTR); + + if (statusFlags < 0) + { + return false; + } + + int result; + do + { + result = fcntl(fileDescriptor, F_SETFL, statusFlags | O_NONBLOCK); + } while (result < 0 && errno == EINTR); + + return result == 0 && SetCloseOnExec(fileDescriptor); + } + + bool CreatePipe(int fileDescriptors[2]) + { +#if HAVE_PIPE2 + int result; + do + { + result = pipe2(fileDescriptors, O_CLOEXEC | O_NONBLOCK); + } while (result < 0 && errno == EINTR); + + if (result == 0) + { + return true; + } + + if (errno != ENOSYS && errno != EINVAL) + { + return false; + } +#else + int result; +#endif // HAVE_PIPE2 + do + { + result = pipe(fileDescriptors); + } while (result < 0 && errno == EINTR); + + if (result < 0) + { + return false; + } + + if (!SetPipeFlags(fileDescriptors[0]) || + !SetPipeFlags(fileDescriptors[1])) + { + CloseFileDescriptor(fileDescriptors[0]); + CloseFileDescriptor(fileDescriptors[1]); + return false; + } + + return true; + } + + Waitable* AllocateWaitable(WaitableKind kind) + { + Waitable* waitable = new (std::nothrow) Waitable{}; + if (waitable == nullptr) + { + return nullptr; + } + + waitable->refCount = 1; + waitable->publicRefCount = 1; + waitable->kind = kind; + waitable->readFileDescriptor = -1; + waitable->writeFileDescriptor = -1; + waitable->processFileDescriptor = -1; + waitable->cancellationReadFileDescriptor = -1; + waitable->cancellationWriteFileDescriptor = -1; + + if (!minipal_mutex_init(&waitable->mutex)) + { + delete waitable; + return nullptr; + } + + waitable->mutexInitialized = true; + return waitable; + } + + void DestroyWaitable(Waitable* waitable) + { + assert(__atomic_load_n(&waitable->refCount, __ATOMIC_RELAXED) == 0); + assert(__atomic_load_n(&waitable->watcherRunning, __ATOMIC_RELAXED) == 0); + assert(!waitable->watcherStarted); + + CloseFileDescriptor(waitable->readFileDescriptor); + CloseFileDescriptor(waitable->writeFileDescriptor); + CloseFileDescriptor(waitable->processFileDescriptor); + CloseFileDescriptor(waitable->cancellationReadFileDescriptor); + CloseFileDescriptor(waitable->cancellationWriteFileDescriptor); + + if (waitable->mutexInitialized) + { + minipal_mutex_destroy(&waitable->mutex); + } + + delete waitable; + } + + void AddReference(Waitable* waitable) + { + __atomic_add_fetch(&waitable->refCount, 1, __ATOMIC_RELAXED); + } + + void ReleaseReference(Waitable* waitable) + { + if (__atomic_sub_fetch(&waitable->refCount, 1, __ATOMIC_ACQ_REL) == 0) + { + DestroyWaitable(waitable); + } + } + + bool WriteByte(int fileDescriptor, bool allowFullPipe) + { + uint8_t value = 1; + ssize_t result; + do + { + result = write(fileDescriptor, &value, sizeof(value)); + } while (result < 0 && errno == EINTR); + + return result == sizeof(value) || (allowFullPipe && result < 0 && errno == EAGAIN); + } + + void ReleasePublicReference(Waitable* waitable) + { + int32_t publicRefCount = + __atomic_sub_fetch(&waitable->publicRefCount, 1, __ATOMIC_ACQ_REL); + assert(publicRefCount >= 0); + + if (publicRefCount == 0 && + waitable->watcherStarted) + { + if (__atomic_load_n(&waitable->watcherRunning, __ATOMIC_ACQUIRE) != 0) + { + WriteByte(waitable->cancellationWriteFileDescriptor, true); + } + + int joinResult = pthread_join(waitable->watcherThread, nullptr); + assert(joinResult == 0); + (void)joinResult; + waitable->watcherStarted = false; + } + + ReleaseReference(waitable); + } + + bool ReadByte(int fileDescriptor) + { + uint8_t value; + ssize_t result; + do + { + result = read(fileDescriptor, &value, sizeof(value)); + } while (result < 0 && errno == EINTR); + + return result == sizeof(value); + } + + bool SignalPipe(Waitable* waitable) + { + minipal::MutexHolder lock(waitable->mutex); + if (waitable->signaled) + { + return true; + } + + if (!WriteByte(waitable->writeFileDescriptor, false)) + { + return false; + } + + waitable->signaled = true; + return true; + } + + bool ResetPipe(Waitable* waitable) + { + minipal::MutexHolder lock(waitable->mutex); + if (!waitable->signaled) + { + return true; + } + + uint8_t buffer[16]; + ssize_t result; + do + { + result = read(waitable->readFileDescriptor, buffer, sizeof(buffer)); + } while (result > 0 || (result < 0 && errno == EINTR)); + + if (result < 0 && errno != EAGAIN) + { + return false; + } + + waitable->signaled = false; + return true; + } + + AcquireResult TryAcquirePipe(Waitable* waitable) + { + minipal::MutexHolder lock(waitable->mutex); + if (waitable->errorCode != 0) + { + errno = waitable->errorCode; + return AcquireResult::Failed; + } + + if (!waitable->signaled) + { + return AcquireResult::NotReady; + } + + if (!waitable->manualReset) + { + if (!ReadByte(waitable->readFileDescriptor)) + { + return AcquireResult::Failed; + } + + waitable->signaled = false; + } + + return AcquireResult::Ready; + } + + void ReapChild(pid_t processId) + { + int status; + pid_t result; + do + { + result = waitpid(processId, &status, WNOHANG); + } while (result < 0 && errno == EINTR); + } + + AcquireResult TryAcquireProcessFileDescriptor(Waitable* waitable) + { + minipal::MutexHolder lock(waitable->mutex); + if (waitable->signaled) + { + return AcquireResult::Ready; + } + + pollfd descriptor = {}; + descriptor.fd = waitable->processFileDescriptor; + descriptor.events = POLLIN; + + int result; + do + { + result = poll(&descriptor, 1, 0); + } while (result < 0 && errno == EINTR); + + if (result < 0 || (descriptor.revents & POLLNVAL) != 0) + { + return AcquireResult::Failed; + } + + if (result == 0) + { + return AcquireResult::NotReady; + } + + ReapChild(waitable->processId); + waitable->signaled = true; + return AcquireResult::Ready; + } + +#if MINIPAL_WAIT_USES_KQUEUE + AcquireResult TryAcquireProcessKqueue(Waitable* waitable) + { + minipal::MutexHolder lock(waitable->mutex); + if (waitable->signaled) + { + return AcquireResult::Ready; + } + + struct kevent processEvent; + timespec timeout = {}; + int result; + do + { + result = kevent( + waitable->processFileDescriptor, + nullptr, + 0, + &processEvent, + 1, + &timeout); + } while (result < 0 && errno == EINTR); + + if (result < 0) + { + return AcquireResult::Failed; + } + + if (result == 0) + { + return AcquireResult::NotReady; + } + + ReapChild(waitable->processId); + waitable->signaled = true; + return AcquireResult::Ready; + } + + void MarkExitedProcesses( + Waitable* const* waitables, + uint32_t count, + const struct kevent* events, + int eventCount) + { + for (int eventIndex = 0; eventIndex < eventCount; eventIndex++) + { + if (events[eventIndex].filter != EVFILT_PROC) + { + continue; + } + + for (uint32_t handleIndex = 0; handleIndex < count; handleIndex++) + { + Waitable* waitable = waitables[handleIndex]; + if (waitable->kind == WaitableKind::ProcessKqueue && + waitable->processId == static_cast(events[eventIndex].ident)) + { + minipal::MutexHolder lock(waitable->mutex); + ReapChild(waitable->processId); + waitable->signaled = true; + } + } + } + } +#endif // MINIPAL_WAIT_USES_KQUEUE + + AcquireResult TryAcquire(Waitable* waitable) + { + switch (waitable->kind) + { + case WaitableKind::Event: + case WaitableKind::ProcessPipe: + return TryAcquirePipe(waitable); + + case WaitableKind::ProcessFileDescriptor: + return TryAcquireProcessFileDescriptor(waitable); + +#if MINIPAL_WAIT_USES_KQUEUE + case WaitableKind::ProcessKqueue: + return TryAcquireProcessKqueue(waitable); +#endif // MINIPAL_WAIT_USES_KQUEUE + + default: + return AcquireResult::Failed; + } + } + + int32_t TryAcquireAny(Waitable* const* waitables, uint32_t count) + { + for (uint32_t index = 0; index < count; index++) + { + AcquireResult result = TryAcquire(waitables[index]); + if (result == AcquireResult::Failed) + { + return MINIPAL_WAIT_FAILED; + } + + if (result == AcquireResult::Ready) + { + return static_cast(index); + } + } + + return MINIPAL_WAIT_TIMEOUT; + } + + bool GetMonotonicTime(uint64_t* nanoseconds) + { + timespec currentTime; + if (clock_gettime(CLOCK_MONOTONIC, ¤tTime) != 0) + { + return false; + } + + *nanoseconds = + static_cast(currentTime.tv_sec) * 1000000000 + + static_cast(currentTime.tv_nsec); + return true; + } + + bool GetRemainingNanoseconds(uint64_t deadline, uint64_t* remaining) + { + uint64_t currentTime; + if (!GetMonotonicTime(¤tTime)) + { + return false; + } + + *remaining = currentTime >= deadline ? 0 : deadline - currentTime; + return true; + } + + int GetPollTimeout(uint64_t remainingNanoseconds) + { + uint64_t remainingMilliseconds = (remainingNanoseconds + 999999) / 1000000; + return remainingMilliseconds > INT_MAX + ? INT_MAX + : static_cast(remainingMilliseconds); + } + + bool HasProcessExited(pid_t processId) + { + int status; + pid_t result; + do + { + result = waitpid(processId, &status, WNOHANG); + } while (result < 0 && errno == EINTR); + + if (result == processId) + { + return true; + } + + if (result == 0) + { + return false; + } + + if (result < 0 && errno == ECHILD) + { + // No identity-stable primitive is available on this fallback path, so PID reuse can race this probe. + int killResult; + do + { + killResult = kill(processId, 0); + } while (killResult < 0 && errno == EINTR); + + return killResult < 0 && errno == ESRCH; + } + + return false; + } + + void* ProcessWatcher(void* argument) + { + Waitable* waitable = static_cast(argument); + bool exited = HasProcessExited(waitable->processId); + int errorCode = 0; + + while (!exited) + { + pollfd cancellation = {}; + cancellation.fd = waitable->cancellationReadFileDescriptor; + cancellation.events = POLLIN; + + int result; + do + { + result = poll(&cancellation, 1, 250); + } while (result < 0 && errno == EINTR); + + if (result < 0) + { + errorCode = errno; + break; + } + + if (result > 0) + { + break; + } + + exited = HasProcessExited(waitable->processId); + } + + if (exited) + { + SignalPipe(waitable); + } + else if (errorCode != 0) + { + { + minipal::MutexHolder lock(waitable->mutex); + waitable->errorCode = errorCode; + } + SignalPipe(waitable); + } + + __atomic_store_n(&waitable->watcherRunning, 0, __ATOMIC_RELEASE); + ReleaseReference(waitable); + return nullptr; + } + + bool StartProcessWatcher(Waitable* waitable) + { + int cancellationPipe[2]; + if (!CreatePipe(cancellationPipe)) + { + return false; + } + + waitable->cancellationReadFileDescriptor = cancellationPipe[0]; + waitable->cancellationWriteFileDescriptor = cancellationPipe[1]; + + AddReference(waitable); + __atomic_store_n(&waitable->watcherRunning, 1, __ATOMIC_RELEASE); + if (pthread_create( + &waitable->watcherThread, + nullptr, + ProcessWatcher, + waitable) != 0) + { + __atomic_store_n(&waitable->watcherRunning, 0, __ATOMIC_RELEASE); + ReleaseReference(waitable); + return false; + } + + waitable->watcherStarted = true; + return true; + } + + Waitable* CreatePipeWaitable(WaitableKind kind, bool manualReset, bool initialState) + { + Waitable* waitable = AllocateWaitable(kind); + if (waitable == nullptr) + { + return nullptr; + } + + int eventPipe[2]; + if (!CreatePipe(eventPipe)) + { + ReleasePublicReference(waitable); + return nullptr; + } + + waitable->readFileDescriptor = eventPipe[0]; + waitable->writeFileDescriptor = eventPipe[1]; + waitable->manualReset = manualReset; + + if (initialState && !SignalPipe(waitable)) + { + ReleasePublicReference(waitable); + return nullptr; + } + + return waitable; + } + + Waitable* CreateSignaledProcessWaitable(pid_t processId) + { + Waitable* waitable = CreatePipeWaitable(WaitableKind::ProcessPipe, true, true); + if (waitable != nullptr) + { + waitable->processId = processId; + ReapChild(processId); + } + + return waitable; + } + + Waitable* CreateProcessWatcherWaitable(pid_t processId) + { + Waitable* waitable = CreatePipeWaitable(WaitableKind::ProcessPipe, true, false); + if (waitable == nullptr) + { + return nullptr; + } + + waitable->processId = processId; + if (!StartProcessWatcher(waitable)) + { + ReleasePublicReference(waitable); + return nullptr; + } + + return waitable; + } + +#if MINIPAL_WAIT_USES_KQUEUE + Waitable* CreateKqueueProcessWaitable(pid_t processId) + { + int processQueue = kqueue(); + if (processQueue < 0) + { + return nullptr; + } + + if (!SetCloseOnExec(processQueue)) + { + CloseFileDescriptor(processQueue); + return nullptr; + } + + struct kevent change; + EV_SET( + &change, + processId, + EVFILT_PROC, + EV_ADD | EV_ENABLE | EV_ONESHOT, + NOTE_EXIT, + 0, + nullptr); + + if (kevent(processQueue, &change, 1, nullptr, 0, nullptr) < 0) + { + int error = errno; + CloseFileDescriptor(processQueue); + if (error == ESRCH) + { + return CreateSignaledProcessWaitable(processId); + } + + errno = error; + return nullptr; + } + + Waitable* waitable = AllocateWaitable(WaitableKind::ProcessKqueue); + if (waitable == nullptr) + { + CloseFileDescriptor(processQueue); + return nullptr; + } + + waitable->processId = processId; + waitable->processFileDescriptor = processQueue; + waitable->manualReset = true; + return waitable; + } +#endif // MINIPAL_WAIT_USES_KQUEUE + + int32_t WaitWithPoll( + Waitable* const* waitables, + uint32_t count, + uint32_t timeout, + uint64_t deadline) + { + pollfd descriptors[MINIPAL_MAX_WAIT_OBJECTS] = {}; + for (uint32_t index = 0; index < count; index++) + { + Waitable* waitable = waitables[index]; + switch (waitable->kind) + { + case WaitableKind::Event: + case WaitableKind::ProcessPipe: + descriptors[index].fd = waitable->readFileDescriptor; + break; + + case WaitableKind::ProcessFileDescriptor: + descriptors[index].fd = waitable->processFileDescriptor; + break; + + default: + errno = EINVAL; + return MINIPAL_WAIT_FAILED; + } + + descriptors[index].events = POLLIN; + } + + while (true) + { + int32_t acquired = TryAcquireAny(waitables, count); + if (acquired != MINIPAL_WAIT_TIMEOUT || timeout == 0) + { + return acquired; + } + + int pollTimeout = -1; + if (timeout != MINIPAL_WAIT_INFINITE) + { + uint64_t remaining; + if (!GetRemainingNanoseconds(deadline, &remaining)) + { + return MINIPAL_WAIT_FAILED; + } + + if (remaining == 0) + { + return MINIPAL_WAIT_TIMEOUT; + } + + pollTimeout = GetPollTimeout(remaining); + } + + int result = poll(descriptors, count, pollTimeout); + if (result == 0) + { + return MINIPAL_WAIT_TIMEOUT; + } + + if (result < 0) + { + if (errno == EINTR) + { + continue; + } + + return MINIPAL_WAIT_FAILED; + } + + for (uint32_t index = 0; index < count; index++) + { + if ((descriptors[index].revents & POLLNVAL) != 0) + { + errno = EBADF; + return MINIPAL_WAIT_FAILED; + } + } + } + } + +#if MINIPAL_WAIT_USES_KQUEUE + int32_t WaitWithKqueue( + Waitable* const* waitables, + uint32_t count, + uint32_t timeout, + uint64_t deadline) + { + while (true) + { + int32_t acquired = TryAcquireAny(waitables, count); + if (acquired != MINIPAL_WAIT_TIMEOUT || timeout == 0) + { + return acquired; + } + + int waitQueue = kqueue(); + if (waitQueue < 0) + { + return MINIPAL_WAIT_FAILED; + } + + if (!SetCloseOnExec(waitQueue)) + { + CloseFileDescriptor(waitQueue); + return MINIPAL_WAIT_FAILED; + } + + bool registrationFailed = false; + for (uint32_t index = 0; index < count; index++) + { + Waitable* waitable = waitables[index]; + struct kevent change; + if (waitable->kind == WaitableKind::ProcessKqueue) + { + EV_SET( + &change, + waitable->processId, + EVFILT_PROC, + EV_ADD | EV_ENABLE | EV_ONESHOT, + NOTE_EXIT, + 0, + nullptr); + } + else + { + int fileDescriptor = waitable->kind == WaitableKind::ProcessFileDescriptor + ? waitable->processFileDescriptor + : waitable->readFileDescriptor; + EV_SET( + &change, + fileDescriptor, + EVFILT_READ, + EV_ADD | EV_ENABLE, + 0, + 0, + nullptr); + } + + if (kevent(waitQueue, &change, 1, nullptr, 0, nullptr) < 0) + { + if (waitable->kind == WaitableKind::ProcessKqueue && errno == ESRCH) + { + minipal::MutexHolder lock(waitable->mutex); + ReapChild(waitable->processId); + waitable->signaled = true; + continue; + } + + registrationFailed = true; + break; + } + } + + if (registrationFailed) + { + CloseFileDescriptor(waitQueue); + return MINIPAL_WAIT_FAILED; + } + + acquired = TryAcquireAny(waitables, count); + if (acquired != MINIPAL_WAIT_TIMEOUT) + { + CloseFileDescriptor(waitQueue); + return acquired; + } + + timespec remainingTime; + timespec* waitTimeout = nullptr; + if (timeout != MINIPAL_WAIT_INFINITE) + { + uint64_t remaining; + if (!GetRemainingNanoseconds(deadline, &remaining)) + { + CloseFileDescriptor(waitQueue); + return MINIPAL_WAIT_FAILED; + } + + if (remaining == 0) + { + CloseFileDescriptor(waitQueue); + return MINIPAL_WAIT_TIMEOUT; + } + + remainingTime.tv_sec = static_cast(remaining / 1000000000); + remainingTime.tv_nsec = static_cast(remaining % 1000000000); + waitTimeout = &remainingTime; + } + + struct kevent events[MINIPAL_MAX_WAIT_OBJECTS]; + int result; + do + { + result = kevent(waitQueue, nullptr, 0, events, count, waitTimeout); + } while (result < 0 && errno == EINTR && timeout == MINIPAL_WAIT_INFINITE); + + if (result > 0) + { + for (int eventIndex = 0; eventIndex < result; eventIndex++) + { + if ((events[eventIndex].flags & EV_ERROR) != 0 && events[eventIndex].data != 0) + { + errno = static_cast(events[eventIndex].data); + CloseFileDescriptor(waitQueue); + return MINIPAL_WAIT_FAILED; + } + } + + MarkExitedProcesses(waitables, count, events, result); + } + + int error = errno; + CloseFileDescriptor(waitQueue); + + if (result == 0) + { + return MINIPAL_WAIT_TIMEOUT; + } + + if (result < 0) + { + if (error == EINTR) + { + continue; + } + + errno = error; + return MINIPAL_WAIT_FAILED; + } + } + } +#endif // MINIPAL_WAIT_USES_KQUEUE + + Waitable* CreateProcessWaitable(uint32_t processId) + { + if (processId == 0 || processId > static_cast(INT_MAX)) + { + errno = EINVAL; + return nullptr; + } + +#if defined(TARGET_LINUX) && defined(SYS_pidfd_open) + int processFileDescriptor; + do + { + processFileDescriptor = static_cast(syscall(SYS_pidfd_open, processId, 0)); + } while (processFileDescriptor < 0 && errno == EINTR); + + if (processFileDescriptor >= 0) + { + if (!SetCloseOnExec(processFileDescriptor)) + { + CloseFileDescriptor(processFileDescriptor); + return nullptr; + } + + Waitable* waitable = AllocateWaitable(WaitableKind::ProcessFileDescriptor); + if (waitable == nullptr) + { + CloseFileDescriptor(processFileDescriptor); + return nullptr; + } + + waitable->processId = static_cast(processId); + waitable->processFileDescriptor = processFileDescriptor; + waitable->manualReset = true; + return waitable; + } + + if (errno == ENOSYS) + { + // The syscall can be present in the build headers but absent from the running kernel. + return CreateProcessWatcherWaitable(static_cast(processId)); + } + + if (errno == ESRCH) + { + return CreateSignaledProcessWaitable(static_cast(processId)); + } +#endif // TARGET_LINUX && SYS_pidfd_open + +#if MINIPAL_WAIT_USES_KQUEUE + Waitable* waitable = CreateKqueueProcessWaitable(static_cast(processId)); + if (waitable != nullptr) + { + return waitable; + } +#endif // MINIPAL_WAIT_USES_KQUEUE + + return CreateProcessWatcherWaitable(static_cast(processId)); + } + + void* DuplicateWaitable(void* handle) + { + if (handle == nullptr) + { + errno = EINVAL; + return nullptr; + } + + Waitable* waitable = static_cast(handle); + __atomic_add_fetch(&waitable->publicRefCount, 1, __ATOMIC_RELAXED); + AddReference(waitable); + return waitable; + } +} + +minipal_wait_handle::minipal_wait_handle(void* handle) + : m_handle(handle) +{ +} + +minipal_wait_handle::minipal_wait_handle(const minipal_wait_handle& handle) + : minipal_wait_handle(DuplicateWaitable(handle.m_handle)) +{ +} + +minipal_wait_handle::~minipal_wait_handle() +{ + if (m_handle != nullptr) + { + ReleasePublicReference(static_cast(m_handle)); + } +} + +minipal_event::minipal_event(bool manualReset, bool initialState) + : minipal_wait_handle(CreatePipeWaitable(WaitableKind::Event, manualReset, initialState)) +{ +} + +bool minipal_event::Set() +{ + if (!IsValid()) + { + errno = EINVAL; + return false; + } + + Waitable* waitable = static_cast(GetWaitable()); + if (waitable->kind != WaitableKind::Event) + { + errno = EINVAL; + return false; + } + + return SignalPipe(waitable); +} + +bool minipal_event::Reset() +{ + if (!IsValid()) + { + errno = EINVAL; + return false; + } + + Waitable* waitable = static_cast(GetWaitable()); + if (waitable->kind != WaitableKind::Event) + { + errno = EINVAL; + return false; + } + + return ResetPipe(waitable); +} + +minipal_process_wait::minipal_process_wait(uint32_t processId) + : minipal_wait_handle(CreateProcessWaitable(processId)) +{ +} + +int32_t minipal_wait_handle::Wait( + const minipal_wait_handle* const* handles, + uint32_t count, + uint32_t timeout) +{ + if (handles == nullptr || count == 0 || count > MINIPAL_MAX_WAIT_OBJECTS) + { + errno = EINVAL; + return MINIPAL_WAIT_FAILED; + } + + Waitable* waitables[MINIPAL_MAX_WAIT_OBJECTS]; + for (uint32_t index = 0; index < count; index++) + { + if (handles[index] == nullptr || !handles[index]->IsValid()) + { + errno = EINVAL; + return MINIPAL_WAIT_FAILED; + } + + waitables[index] = static_cast(handles[index]->m_handle); + } + + uint64_t deadline = 0; + if (timeout != 0 && timeout != MINIPAL_WAIT_INFINITE) + { + uint64_t currentTime; + if (!GetMonotonicTime(¤tTime)) + { + return MINIPAL_WAIT_FAILED; + } + + deadline = currentTime + static_cast(timeout) * 1000000; + } + +#if MINIPAL_WAIT_USES_KQUEUE + return WaitWithKqueue(waitables, count, timeout, deadline); +#else + return WaitWithPoll(waitables, count, timeout, deadline); +#endif +} diff --git a/src/coreclr/minipal/Windows/CMakeLists.txt b/src/coreclr/minipal/Windows/CMakeLists.txt index fa6ed5bd8a4b82..49e9d109e280a9 100644 --- a/src/coreclr/minipal/Windows/CMakeLists.txt +++ b/src/coreclr/minipal/Windows/CMakeLists.txt @@ -3,6 +3,7 @@ set(SOURCES dn-u16.cpp dn-stdio.cpp memory.cpp + wait.cpp ) add_library(coreclrminipal STATIC diff --git a/src/coreclr/minipal/Windows/wait.cpp b/src/coreclr/minipal/Windows/wait.cpp new file mode 100644 index 00000000000000..754a2141cbaf83 --- /dev/null +++ b/src/coreclr/minipal/Windows/wait.cpp @@ -0,0 +1,124 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include + +#include "minipal-wait.h" + +namespace +{ + HANDLE DuplicateNativeHandle(HANDLE handle) + { + if (handle == nullptr) + { + SetLastError(ERROR_INVALID_HANDLE); + return nullptr; + } + + HANDLE duplicate = nullptr; + if (!DuplicateHandle( + GetCurrentProcess(), + handle, + GetCurrentProcess(), + &duplicate, + 0, + FALSE, + DUPLICATE_SAME_ACCESS)) + { + return nullptr; + } + + return duplicate; + } +} + +minipal_wait_handle::minipal_wait_handle(HANDLE handle) + : m_handle(handle) +{ +} + +minipal_wait_handle::minipal_wait_handle(const minipal_wait_handle& handle) + : minipal_wait_handle(DuplicateNativeHandle(handle.m_handle)) +{ +} + +minipal_wait_handle::~minipal_wait_handle() +{ + if (m_handle != nullptr) + { + CloseHandle(m_handle); + } +} + +minipal_event::minipal_event(bool manualReset, bool initialState) + : minipal_wait_handle(CreateEventW(nullptr, manualReset, initialState, nullptr)) +{ +} + +minipal_event::minipal_event(HANDLE handle) + : minipal_wait_handle(DuplicateNativeHandle(handle)) +{ +} + +bool minipal_event::Set() +{ + return SetEvent(GetRawHandle()) != FALSE; +} + +bool minipal_event::Reset() +{ + return ResetEvent(GetRawHandle()) != FALSE; +} + +minipal_process_wait::minipal_process_wait(uint32_t processId) + : minipal_wait_handle(OpenProcess(SYNCHRONIZE, FALSE, processId)) +{ +} + +minipal_process_wait::minipal_process_wait(HANDLE handle) + : minipal_wait_handle(DuplicateNativeHandle(handle)) +{ +} + +int32_t minipal_wait_handle::Wait( + const minipal_wait_handle* const* handles, + uint32_t count, + uint32_t timeout) +{ + if (handles == nullptr || count == 0 || count > MINIPAL_MAX_WAIT_OBJECTS) + { + SetLastError(ERROR_INVALID_PARAMETER); + return MINIPAL_WAIT_FAILED; + } + + HANDLE nativeHandles[MINIPAL_MAX_WAIT_OBJECTS]; + for (uint32_t index = 0; index < count; index++) + { + if (handles[index] == nullptr || !handles[index]->IsValid()) + { + SetLastError(ERROR_INVALID_HANDLE); + return MINIPAL_WAIT_FAILED; + } + + nativeHandles[index] = handles[index]->m_handle; + } + + DWORD result = WaitForMultipleObjectsEx( + count, + nativeHandles, + FALSE, + timeout, + FALSE); + + if (result >= WAIT_OBJECT_0 && result < WAIT_OBJECT_0 + count) + { + return static_cast(result - WAIT_OBJECT_0); + } + + if (result == WAIT_TIMEOUT) + { + return MINIPAL_WAIT_TIMEOUT; + } + + return MINIPAL_WAIT_FAILED; +} diff --git a/src/coreclr/minipal/minipal-wait.h b/src/coreclr/minipal/minipal-wait.h new file mode 100644 index 00000000000000..f8e34081382232 --- /dev/null +++ b/src/coreclr/minipal/minipal-wait.h @@ -0,0 +1,99 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef __MINIPAL_WAIT_H__ +#define __MINIPAL_WAIT_H__ + +#include + +#ifdef HOST_WINDOWS +#include +#endif + +constexpr uint32_t MINIPAL_MAX_WAIT_OBJECTS = 64; +constexpr uint32_t MINIPAL_WAIT_INFINITE = UINT32_MAX; +constexpr int32_t MINIPAL_WAIT_TIMEOUT = -1; +constexpr int32_t MINIPAL_WAIT_FAILED = -2; + +struct minipal_wait_handle +{ + bool IsValid() const + { + return m_handle != nullptr; + } + +#ifdef HOST_WINDOWS + HANDLE GetRawHandle() const + { + return m_handle; + } +#endif + + // Waits return the zero-based index of the acquired handle, or one of the negative results above. + // Handles must remain valid until the wait returns. + static int32_t Wait(const minipal_wait_handle& handle, uint32_t timeout) + { + const minipal_wait_handle* handles[] = { &handle }; + return Wait(handles, 1, timeout); + } + + static int32_t Wait( + const minipal_wait_handle* const* handles, + uint32_t count, + uint32_t timeout); + +protected: +#ifdef HOST_WINDOWS + explicit minipal_wait_handle(HANDLE handle); +#else + explicit minipal_wait_handle(void* handle); +#endif + minipal_wait_handle(const minipal_wait_handle& handle); + minipal_wait_handle& operator=(const minipal_wait_handle& handle) = delete; + ~minipal_wait_handle(); + +#ifndef HOST_WINDOWS + void* GetWaitable() const + { + return m_handle; + } +#endif + +private: +#ifdef HOST_WINDOWS + HANDLE m_handle; +#else + void* m_handle; +#endif +}; + +struct minipal_event final : minipal_wait_handle +{ + minipal_event(bool manualReset, bool initialState); + +#ifdef HOST_WINDOWS + // Duplicates an existing native event handle. + explicit minipal_event(HANDLE handle); +#endif + minipal_event(const minipal_event& event) = default; + minipal_event& operator=(const minipal_event& event) = delete; + + bool Set(); + bool Reset(); +}; + +struct minipal_process_wait final : minipal_wait_handle +{ + // Process waits remain signaled after exit. On Unix, observing a child process exit may reap it, + // matching the debugger's existing behavior. + explicit minipal_process_wait(uint32_t processId); + +#ifdef HOST_WINDOWS + // Duplicates an existing native process or thread handle. + explicit minipal_process_wait(HANDLE handle); +#endif + minipal_process_wait(const minipal_process_wait& processWait) = default; + minipal_process_wait& operator=(const minipal_process_wait& processWait) = delete; +}; + +#endif // __MINIPAL_WAIT_H__ diff --git a/src/coreclr/minipal/tests/CMakeLists.txt b/src/coreclr/minipal/tests/CMakeLists.txt new file mode 100644 index 00000000000000..50a4b772c9e071 --- /dev/null +++ b/src/coreclr/minipal/tests/CMakeLists.txt @@ -0,0 +1,13 @@ +add_executable_clr(coreclrminipal_wait_tests + EXCLUDE_FROM_ALL + wait.cpp +) + +target_link_libraries(coreclrminipal_wait_tests + PRIVATE + coreclrminipal +) + +if(CLR_CMAKE_HOST_UNIX) + target_link_libraries(coreclrminipal_wait_tests PRIVATE pthread) +endif() diff --git a/src/coreclr/minipal/tests/wait.cpp b/src/coreclr/minipal/tests/wait.cpp new file mode 100644 index 00000000000000..3c1ae985f0d60f --- /dev/null +++ b/src/coreclr/minipal/tests/wait.cpp @@ -0,0 +1,486 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include +#include +#include +#include +#include + +#ifdef HOST_WINDOWS +#include +#else +#include +#include +#include +#include +#include +#include +#endif + +#include "minipal-wait.h" + +namespace +{ + struct WaitThreadState + { + minipal_event handle; + uint32_t timeout; + int32_t result; + + WaitThreadState(const minipal_event& waitHandle, uint32_t waitTimeout) + : handle(waitHandle) + , timeout(waitTimeout) + , result(MINIPAL_WAIT_FAILED) + { + } + }; + + bool Check(bool condition, const char* message) + { + if (!condition) + { + fprintf(stderr, "FAILED: %s\n", message); + } + + return condition; + } + + void SleepMilliseconds(uint32_t milliseconds) + { +#ifdef HOST_WINDOWS + Sleep(milliseconds); +#else + timespec delay = {}; + delay.tv_sec = milliseconds / 1000; + delay.tv_nsec = static_cast(milliseconds % 1000) * 1000000; + while (nanosleep(&delay, &delay) != 0 && errno == EINTR); +#endif + } + + uint64_t GetTickMilliseconds() + { +#ifdef HOST_WINDOWS + return GetTickCount64(); +#else + timespec currentTime; + if (clock_gettime(CLOCK_MONOTONIC, ¤tTime) != 0) + { + return 0; + } + + return static_cast(currentTime.tv_sec) * 1000 + + static_cast(currentTime.tv_nsec) / 1000000; +#endif + } + +#ifdef HOST_WINDOWS + DWORD WINAPI WaitThread(void* argument) +#else + void* WaitThread(void* argument) +#endif + { + WaitThreadState* state = static_cast(argument); + state->result = minipal_wait_handle::Wait(state->handle, state->timeout); +#ifdef HOST_WINDOWS + return 0; +#else + return nullptr; +#endif + } + + bool TestAutoResetEvent() + { + minipal_event event(false, false); + bool success = + Check(event.IsValid(), "create auto-reset event") && + Check( + minipal_wait_handle::Wait(event, 0) == MINIPAL_WAIT_TIMEOUT, + "unsignaled auto-reset event times out") && + Check(event.Set(), "set auto-reset event") && + Check(event.Set(), "coalesce repeated auto-reset set") && + Check( + minipal_wait_handle::Wait(event, 0) == 0, + "signaled auto-reset event is acquired") && + Check( + minipal_wait_handle::Wait(event, 0) == MINIPAL_WAIT_TIMEOUT, + "auto-reset event is consumed"); + + return success; + } + + bool TestManualResetEvent() + { + minipal_event event(true, true); + bool success = + Check(event.IsValid(), "create manual-reset event") && + Check( + minipal_wait_handle::Wait(event, 0) == 0, + "initially signaled manual-reset event") && + Check( + minipal_wait_handle::Wait(event, 0) == 0, + "manual-reset event remains signaled") && + Check(event.Reset(), "reset manual-reset event") && + Check( + minipal_wait_handle::Wait(event, 0) == MINIPAL_WAIT_TIMEOUT, + "manual-reset event becomes unsignaled") && + Check(event.Set(), "set manual-reset event") && + Check( + minipal_wait_handle::Wait(event, 0) == 0, + "manual-reset event can be signaled again"); + + return success; + } + + bool TestLowestIndexPriority() + { + minipal_event events[3] = + { + { false, false }, + { false, false }, + { false, false }, + }; + const minipal_wait_handle* waitSet[] = { &events[0], &events[1], &events[2] }; + + bool success = + Check(events[0].IsValid() && events[1].IsValid() && events[2].IsValid(), "create priority events") && + Check(events[2].Set(), "set high-index event") && + Check(events[0].Set(), "set low-index event") && + Check( + minipal_wait_handle::Wait(waitSet, 3, 0) == 0, + "lowest signaled index wins") && + Check( + minipal_wait_handle::Wait(waitSet, 3, 0) == 2, + "higher index remains signaled"); + + return success; + } + + bool TestTimeout() + { + minipal_event event(false, false); + if (!Check(event.IsValid(), "create timeout event")) + { + return false; + } + + uint64_t start = GetTickMilliseconds(); + int32_t result = minipal_wait_handle::Wait(event, 100); + uint64_t elapsed = GetTickMilliseconds() - start; + + bool success = + Check(result == MINIPAL_WAIT_TIMEOUT, "finite wait times out") && + Check(elapsed >= 75, "finite wait observes requested timeout") && + Check(elapsed < 2000, "finite wait does not overrun"); + + return success; + } + + bool TestAutoResetReleasesSingleWaiter() + { + minipal_event event(false, false); + WaitThreadState states[2] = + { + { event, 2000 }, + { event, 2000 }, + }; + +#ifdef HOST_WINDOWS + HANDLE threads[2] = + { + CreateThread(nullptr, 0, WaitThread, &states[0], 0, nullptr), + CreateThread(nullptr, 0, WaitThread, &states[1], 0, nullptr), + }; + bool threadsStarted = threads[0] != nullptr && threads[1] != nullptr; +#else + pthread_t threads[2]; + bool threadStarted[2] = + { + pthread_create(&threads[0], nullptr, WaitThread, &states[0]) == 0, + pthread_create(&threads[1], nullptr, WaitThread, &states[1]) == 0, + }; + bool threadsStarted = threadStarted[0] && threadStarted[1]; +#endif + + SleepMilliseconds(50); + bool success = + Check(event.IsValid() && states[0].handle.IsValid() && states[1].handle.IsValid(), + "create shared auto-reset event") && + Check(threadsStarted, "start auto-reset waiters") && + Check(event.Set(), "signal one auto-reset waiter"); + +#ifdef HOST_WINDOWS + for (HANDLE thread : threads) + { + if (thread != nullptr) + { + success = + Check(WaitForSingleObject(thread, 5000) == WAIT_OBJECT_0, "join auto-reset waiter") && + success; + CloseHandle(thread); + } + } +#else + for (int index = 0; index < 2; index++) + { + if (threadStarted[index]) + { + success = Check(pthread_join(threads[index], nullptr) == 0, "join auto-reset waiter") && success; + } + } +#endif + + int acquiredCount = (states[0].result == 0 ? 1 : 0) + (states[1].result == 0 ? 1 : 0); + int timeoutCount = + (states[0].result == MINIPAL_WAIT_TIMEOUT ? 1 : 0) + + (states[1].result == MINIPAL_WAIT_TIMEOUT ? 1 : 0); + success = + Check(acquiredCount == 1, "exactly one waiter acquires auto-reset event") && + Check(timeoutCount == 1, "other auto-reset waiter times out") && + success; + + return success; + } + + bool TestDuplicateLifetime() + { + minipal_event* duplicate; + { + minipal_event event(false, false); + duplicate = new (std::nothrow) minipal_event(event); + } + + bool success = + Check(duplicate != nullptr && duplicate->IsValid(), "duplicate event") && + Check(duplicate->Set(), "set duplicate after original release") && + Check( + minipal_wait_handle::Wait(*duplicate, 0) == 0, + "duplicate keeps event alive"); + + delete duplicate; + return success; + } + + bool TestInvalidWaitSet() + { + minipal_event event(false, false); + minipal_process_wait invalidProcess(static_cast(0)); + const minipal_wait_handle* handles[] = { &event, nullptr }; + + bool success = + Check(event.IsValid(), "create invalid-wait event") && + Check(!invalidProcess.IsValid(), "reject invalid process identifier") && + Check( + minipal_wait_handle::Wait(invalidProcess, 0) == MINIPAL_WAIT_FAILED, + "reject invalid single wait handle") && + Check( + minipal_wait_handle::Wait(handles, 2, 0) == MINIPAL_WAIT_FAILED, + "reject null wait-set entry") && + Check( + minipal_wait_handle::Wait(nullptr, 1, 0) == MINIPAL_WAIT_FAILED, + "reject null wait set") && + Check( + minipal_wait_handle::Wait(handles, 0, 0) == MINIPAL_WAIT_FAILED, + "reject empty wait set") && + Check( + minipal_wait_handle::Wait(handles, MINIPAL_MAX_WAIT_OBJECTS + 1, 0) == + MINIPAL_WAIT_FAILED, + "reject oversized wait set"); + + return success; + } + + bool TestConcurrentWait() + { + minipal_event event(false, false); + WaitThreadState state(event, MINIPAL_WAIT_INFINITE); + +#ifdef HOST_WINDOWS + HANDLE thread = CreateThread(nullptr, 0, WaitThread, &state, 0, nullptr); + bool threadStarted = thread != nullptr; +#else + pthread_t thread; + bool threadStarted = pthread_create(&thread, nullptr, WaitThread, &state) == 0; +#endif + + SleepMilliseconds(50); + bool success = + Check(event.IsValid() && state.handle.IsValid(), "create concurrent event") && + Check(threadStarted, "start waiting thread") && + Check(event.Set(), "signal waiting thread"); + + if (threadStarted) + { +#ifdef HOST_WINDOWS + success = + Check(WaitForSingleObject(thread, 5000) == WAIT_OBJECT_0, "join waiting thread") && + success; + CloseHandle(thread); +#else + success = Check(pthread_join(thread, nullptr) == 0, "join waiting thread") && success; +#endif + } + + success = Check(state.result == 0, "waiting thread acquires event") && success; + return success; + } + +#ifndef HOST_WINDOWS + void InterruptSignalHandler(int) + { + } + + bool TestInterruptedWait() + { + struct sigaction action = {}; + action.sa_handler = InterruptSignalHandler; + sigemptyset(&action.sa_mask); + if (!Check(sigaction(SIGUSR1, &action, nullptr) == 0, "install interrupt handler")) + { + return false; + } + + minipal_event event(false, false); + WaitThreadState state(event, MINIPAL_WAIT_INFINITE); + pthread_t thread; + bool threadStarted = pthread_create(&thread, nullptr, WaitThread, &state) == 0; + + SleepMilliseconds(50); + bool success = + Check(event.IsValid() && state.handle.IsValid(), "create interrupted event") && + Check(threadStarted, "start interrupted waiting thread"); + + if (threadStarted) + { + success = Check(pthread_kill(thread, SIGUSR1) == 0, "interrupt waiting thread") && success; + SleepMilliseconds(50); + success = Check(event.Set(), "signal interrupted wait") && success; + success = Check(pthread_join(thread, nullptr) == 0, "join interrupted waiting thread") && success; + } + + success = Check(state.result == 0, "interrupted wait retries") && success; + return success; + } +#endif + + bool TestProcessExit(const char* executablePath) + { +#ifdef HOST_WINDOWS + char commandLine[MAX_PATH + 32]; + snprintf(commandLine, sizeof(commandLine), "\"%s\" --child", executablePath); + + STARTUPINFOA startupInfo = {}; + startupInfo.cb = sizeof(startupInfo); + PROCESS_INFORMATION processInfo = {}; + if (!Check( + CreateProcessA( + executablePath, + commandLine, + nullptr, + nullptr, + FALSE, + 0, + nullptr, + nullptr, + &startupInfo, + &processInfo) != FALSE, + "create child process")) + { + return false; + } + + minipal_process_wait process(processInfo.dwProcessId); + minipal_event event(false, false); + const minipal_wait_handle* handles[] = { &event, &process }; + int32_t result = !process.IsValid() || !event.IsValid() + ? MINIPAL_WAIT_FAILED + : minipal_wait_handle::Wait(handles, 2, 5000); + bool success = + Check(process.IsValid() && event.IsValid(), "create process waitables") && + Check(result == 1, "observe process exit in wait set") && + Check(event.Set(), "set event after process exit") && + Check( + minipal_wait_handle::Wait(handles, 2, 0) == 0, + "event has priority over exited process") && + Check( + minipal_wait_handle::Wait(process, 0) == 0, + "process exit remains signaled"); + + CloseHandle(processInfo.hThread); + CloseHandle(processInfo.hProcess); + return success; +#else + (void)executablePath; + pid_t child = fork(); + if (!Check(child >= 0, "fork child process")) + { + return false; + } + + if (child == 0) + { + SleepMilliseconds(100); + _exit(0); + } + + minipal_process_wait process(static_cast(child)); + minipal_event event(false, false); + const minipal_wait_handle* handles[] = { &event, &process }; + int32_t result = !process.IsValid() || !event.IsValid() + ? MINIPAL_WAIT_FAILED + : minipal_wait_handle::Wait(handles, 2, 5000); + bool success = + Check(process.IsValid() && event.IsValid(), "create process waitables") && + Check(result == 1, "observe process exit in wait set") && + Check(event.Set(), "set event after process exit") && + Check( + minipal_wait_handle::Wait(handles, 2, 0) == 0, + "event has priority over exited process") && + Check( + minipal_wait_handle::Wait(process, 0) == 0, + "process exit remains signaled"); + + int status; + pid_t waitResult; + do + { + waitResult = waitpid(child, &status, 0); + } while (waitResult < 0 && errno == EINTR); + success = + Check(waitResult == child || (waitResult < 0 && errno == ECHILD), "collect child process") && + success; + return success; +#endif + } +} + +int main(int argc, char** argv) +{ + if (argc == 2 && strcmp(argv[1], "--child") == 0) + { + SleepMilliseconds(100); + return 0; + } + + bool success = + TestAutoResetEvent() && + TestManualResetEvent() && + TestLowestIndexPriority() && + TestTimeout() && + TestDuplicateLifetime() && + TestInvalidWaitSet() && + TestConcurrentWait() && + TestAutoResetReleasesSingleWaiter() && +#ifndef HOST_WINDOWS + TestInterruptedWait() && +#endif + TestProcessExit(argv[0]); + + if (success) + { + printf("All minipal wait tests passed.\n"); + return 0; + } + + return 1; +} From 58f39cf9db70b6d56358927e987a489421ce7fe2 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 17 Aug 2026 13:10:20 -0700 Subject: [PATCH 02/12] Track debugger helper thread liveness Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/coreclr/debug/ee/debugger.h | 1 + src/coreclr/debug/ee/rcthread.cpp | 166 +++++++++++++++++------------- 2 files changed, 93 insertions(+), 74 deletions(-) diff --git a/src/coreclr/debug/ee/debugger.h b/src/coreclr/debug/ee/debugger.h index e7d9d4b413cf14..41b0eb55928d72 100644 --- a/src/coreclr/debug/ee/debugger.h +++ b/src/coreclr/debug/ee/debugger.h @@ -892,6 +892,7 @@ class DebuggerRCThread #endif // FEATURE_DBGIPC_TRANSPORT_VM HANDLE m_thread; + Volatile m_helperThreadRunning; bool m_run; minipal_event *m_threadControlEvent; diff --git a/src/coreclr/debug/ee/rcthread.cpp b/src/coreclr/debug/ee/rcthread.cpp index 28989f3946d420..8592fd378b9053 100644 --- a/src/coreclr/debug/ee/rcthread.cpp +++ b/src/coreclr/debug/ee/rcthread.cpp @@ -30,6 +30,7 @@ DebuggerRCThread::DebuggerRCThread(Debugger * pDebugger) : m_debugger(pDebugger), m_pDCB(NULL), m_thread(NULL), + m_helperThreadRunning(FALSE), m_run(true), m_threadControlEvent(NULL), m_helperThreadExitedEvent(NULL), @@ -283,7 +284,8 @@ HRESULT DebuggerRCThread::Init(void) ThrowOutOfMemory(); } - m_helperThreadExitedEvent = new (nothrow) minipal_event(true, false); + // Track liveness separately so this auto-reset event is only an exit notification. + m_helperThreadExitedEvent = new (nothrow) minipal_event(false, false); if ((m_helperThreadExitedEvent == nullptr) || !m_helperThreadExitedEvent->IsValid()) { delete m_helperThreadExitedEvent; @@ -583,10 +585,12 @@ void DebuggerRCThread::ThreadProc(void) struct HelperThreadExitSignal { + Volatile &Running; minipal_event *Event; ~HelperThreadExitSignal() { + Running.Store(FALSE); Event->Set(); } }; @@ -678,11 +682,9 @@ void DebuggerRCThread::ThreadProc(void) // Mark that we're the true helper thread. Now that we've marked // this, no other threads will ever become the temporary helper // thread. - bool resetResult = m_helperThreadExitedEvent->Reset(); - _ASSERTE(resetResult); - (void)resetResult; + HelperThreadExitSignal helperThreadExitSignal = { m_helperThreadRunning, m_helperThreadExitedEvent }; m_pDCB->m_helperThreadId = GetCurrentThreadId(); - HelperThreadExitSignal helperThreadExitSignal = { m_helperThreadExitedEvent }; + m_helperThreadRunning.Store(TRUE); LOG((LF_CORDB, LL_INFO1000, "DRCT::TP: helper thread id is 0x%x helperThreadId\n", m_pDCB->m_helperThreadId)); @@ -1595,6 +1597,11 @@ bool DebuggerRCThread::IsRCThreadReady() return false; } + if (!m_helperThreadRunning.Load()) + { + return false; + } + // a more subtle check. It's possible the thread was up, but then // an bad call to ExitProcess suddenly terminated the helper thread, // leaving the threadid still non-0. So check the actual thread object @@ -1674,96 +1681,105 @@ void DebuggerRCThread::DoFavor(FAVORCALLBACK fp, void * pData) // We are being called on managed thread only. // - // We'll have problems if another thread comes in and - // deletes the RCThread object on us while we're in this call. - if (IsRCThreadReady()) - { - // If the helper thread calls this, we deadlock. - // (Since we wait on an event that only the helper thread sets) - _ASSERTE(GetRCThreadId() != GetCurrentThreadId()); + bool executeFavorOnCurrentThread = !IsRCThreadReady(); - // Only lock if we're waiting on the helper thread. + if (!executeFavorOnCurrentThread) + { + // Serialize the readiness check with publishing and waiting for a favor. // This should be the only place the FavorLock is used. // Note this is never called on the helper thread. CrstHolder ch(GetFavorLock()); - SetFavorFnPtr(fp, pData); + // Check readiness while holding the favor lock so that a prior waiter cannot consume + // the auto-reset exit notification before this caller starts waiting. + if (IsRCThreadReady()) + { + // If the helper thread calls this, we deadlock. + // (Since we wait on an event that only the helper thread sets) + _ASSERTE(GetRCThreadId() != GetCurrentThreadId()); - // Our main message loop operating on the Helper thread will - // pickup that event, call the fp, and set the Read event - GetFavorAvailableEvent()->Set(); + SetFavorFnPtr(fp, pData); - LOG((LF_CORDB, LL_INFO10000, "DRCT::DF - Waiting on FavorReadEvent for favor %p\n", (void*)fp)); + // Our main message loop operating on the Helper thread will + // pickup that event, call the fp, and set the Read event + GetFavorAvailableEvent()->Set(); - // Wait for either the FavorEventRead to be set (which means that the favor - // was executed by the helper thread) or the helper thread's handle (which means - // that the helper thread exited without doing the favor, so we should do it) - // - // Note we are assuming that there's only 2 ways the helper thread can exit: - // 1) Someone calls ::ExitProcess, killing all threads. That will kill us too, so we're "ok". - // 2) Someone calls Stop(), causing the helper to exit gracefully. That's ok too. The helper - // didn't execute the Favor (else the FREvent would have been set first) and so we can. - // - // Beware of problems: - // 1) If the helper can block, we may deadlock. - // 2) If the helper can exit magically (or if we change the Wait to include a timeout) , - // the helper thread may have not executed the favor, partially executed the favor, - // or totally executed the favor but not yet signaled the FavorReadEvent. We don't - // know what it did, so we don't know what we can do; so we're in an unstable state. - - const minipal_wait_handle *waitSet[] = { - GetFavorReadEvent(), + LOG((LF_CORDB, LL_INFO10000, "DRCT::DF - Waiting on FavorReadEvent for favor %p\n", (void*)fp)); + + // Wait for either the FavorEventRead to be set (which means that the favor + // was executed by the helper thread) or the helper thread's handle (which means + // that the helper thread exited without doing the favor, so we should do it) + // + // Note we are assuming that there's only 2 ways the helper thread can exit: + // 1) Someone calls ::ExitProcess, killing all threads. That will kill us too, so we're "ok". + // 2) Someone calls Stop(), causing the helper to exit gracefully. That's ok too. The helper + // didn't execute the Favor (else the FREvent would have been set first) and so we can. + // + // Beware of problems: + // 1) If the helper can block, we may deadlock. + // 2) If the helper can exit magically (or if we change the Wait to include a timeout) , + // the helper thread may have not executed the favor, partially executed the favor, + // or totally executed the favor but not yet signaled the FavorReadEvent. We don't + // know what it did, so we don't know what we can do; so we're in an unstable state. + + const minipal_wait_handle *waitSet[] = { + GetFavorReadEvent(), #ifdef HOST_WINDOWS - // Preserve detection of abnormal helper termination through the native thread handle. - nullptr + // Preserve detection of abnormal helper termination through the native thread handle. + nullptr #else - m_helperThreadExitedEvent + m_helperThreadExitedEvent #endif - }; + }; #ifdef HOST_WINDOWS - minipal_process_wait helperThread(m_thread); - waitSet[1] = &helperThread; + minipal_process_wait helperThread(m_thread); + waitSet[1] = &helperThread; #endif - // the favor worker thread will require a transition to cooperative mode in order to complete its work and we will - // wait for the favor to complete before terminating the process. if there is a GC in progress the favor thread - // will be blocked and if the thread requesting the favor is in cooperative mode we'll deadlock, so we switch to - // preemptive mode before waiting for the favor to complete (see Dev11 72349). - GCX_PREEMP(); + // the favor worker thread will require a transition to cooperative mode in order to complete its work and we will + // wait for the favor to complete before terminating the process. if there is a GC in progress the favor thread + // will be blocked and if the thread requesting the favor is in cooperative mode we'll deadlock, so we switch to + // preemptive mode before waiting for the favor to complete (see Dev11 72349). + GCX_PREEMP(); - int32_t waitResult = minipal_wait_handle::Wait( - waitSet, - ARRAY_SIZE(waitSet), - MINIPAL_WAIT_INFINITE); - - if (waitResult == 0) - { - // Favor was executed, nothing to do here. - LOG((LF_CORDB, LL_INFO10000, "DRCT::DF - favor %p finished, ret = %d\n", (void*)fp, waitResult)); - } - else - { - LOG((LF_CORDB, LL_INFO10000, "DRCT::DF - lost helper thread during wait, " - "doing favor %p on current thread\n", (void*)fp)); + int32_t waitResult = minipal_wait_handle::Wait( + waitSet, + ARRAY_SIZE(waitSet), + MINIPAL_WAIT_INFINITE); - // Since we have no timeout, we shouldn't be able to get an error on the wait, - // but just in case ... - _ASSERTE(waitResult != MINIPAL_WAIT_FAILED); - _ASSERTE((waitResult == 1) && !"DoFavor - unexpected wait result"); + if (waitResult == 0) + { + // Favor was executed, nothing to do here. + LOG((LF_CORDB, LL_INFO10000, "DRCT::DF - favor %p finished, ret = %d\n", (void*)fp, waitResult)); + } + else + { + LOG((LF_CORDB, LL_INFO10000, "DRCT::DF - lost helper thread during wait, " + "doing favor %p on current thread\n", (void*)fp)); - // Thread exited without doing favor, so execute it on our thread. - // If we're here because of a stack overflow, this may push us over the edge, - // but there's nothing else we can really do - (*fp)(pData); + // Since we have no timeout, we shouldn't be able to get an error on the wait, + // but just in case ... + _ASSERTE(waitResult != MINIPAL_WAIT_FAILED); + _ASSERTE((waitResult == 1) || !"DoFavor - unexpected wait result"); - GetFavorAvailableEvent()->Reset(); - } + // Thread exited without doing favor, so execute it on our thread. + // If we're here because of a stack overflow, this may push us over the edge, + // but there's nothing else we can really do + (*fp)(pData); - // m_fpFavor & m_pFavorData are meaningless now. We could set them - // to NULL, but we may as well leave them as is to leave a trail. + GetFavorAvailableEvent()->Reset(); + } + // m_fpFavor & m_pFavorData are meaningless now. We could set them + // to NULL, but we may as well leave them as is to leave a trail. + } + else + { + executeFavorOnCurrentThread = true; + } } - else + + if (executeFavorOnCurrentThread) { LOG((LF_CORDB, LL_INFO10000, "DRCT::DF - helper thread not ready, " "doing favor %p on current thread\n", (void*)fp)); @@ -1831,6 +1847,8 @@ void DebuggerRCThread::EarlyHelperThreadDeath(void) { LOG((LF_CORDB, LL_INFO10000, "DRCT::EHTD\n")); + m_helperThreadRunning.Store(FALSE); + // If we ever spun up a thread... if (m_thread != NULL && m_pDCB) { From cf02278055edb43a224d0efae083dc1785ce09b2 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 17 Aug 2026 14:50:42 -0700 Subject: [PATCH 03/12] Detach minipal process watcher threads Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/coreclr/minipal/Unix/wait.cpp | 198 ++++++++++++------ src/coreclr/minipal/tests/CMakeLists.txt | 24 +++ src/coreclr/minipal/tests/wait.cpp | 243 +++++++++++++++++++++++ 3 files changed, 399 insertions(+), 66 deletions(-) diff --git a/src/coreclr/minipal/Unix/wait.cpp b/src/coreclr/minipal/Unix/wait.cpp index e3804ecd9991a2..e7a505104d4aa3 100644 --- a/src/coreclr/minipal/Unix/wait.cpp +++ b/src/coreclr/minipal/Unix/wait.cpp @@ -32,6 +32,15 @@ namespace { +#if defined(MINIPAL_WAIT_TESTS) + int32_t s_processWatcherCount; + int32_t s_pauseExitedProcessWatchers; + int32_t s_pausedProcessWatcherCount; + int32_t s_pausedExitedProcessWatcherCount; + int32_t s_pauseProcessWatchers; + int32_t s_processWatcherSignalCount; +#endif // MINIPAL_WAIT_TESTS + enum class WaitableKind { Event, @@ -51,7 +60,6 @@ namespace { int32_t refCount; int32_t publicRefCount; - int32_t watcherRunning; WaitableKind kind; minipal_mutex mutex; bool mutexInitialized; @@ -60,12 +68,8 @@ namespace int readFileDescriptor; int writeFileDescriptor; int processFileDescriptor; - int cancellationReadFileDescriptor; - int cancellationWriteFileDescriptor; int errorCode; pid_t processId; - pthread_t watcherThread; - bool watcherStarted; }; void CloseFileDescriptor(int fileDescriptor) @@ -176,8 +180,6 @@ namespace waitable->readFileDescriptor = -1; waitable->writeFileDescriptor = -1; waitable->processFileDescriptor = -1; - waitable->cancellationReadFileDescriptor = -1; - waitable->cancellationWriteFileDescriptor = -1; if (!minipal_mutex_init(&waitable->mutex)) { @@ -192,14 +194,11 @@ namespace void DestroyWaitable(Waitable* waitable) { assert(__atomic_load_n(&waitable->refCount, __ATOMIC_RELAXED) == 0); - assert(__atomic_load_n(&waitable->watcherRunning, __ATOMIC_RELAXED) == 0); - assert(!waitable->watcherStarted); + assert(__atomic_load_n(&waitable->publicRefCount, __ATOMIC_RELAXED) == 0); CloseFileDescriptor(waitable->readFileDescriptor); CloseFileDescriptor(waitable->writeFileDescriptor); CloseFileDescriptor(waitable->processFileDescriptor); - CloseFileDescriptor(waitable->cancellationReadFileDescriptor); - CloseFileDescriptor(waitable->cancellationWriteFileDescriptor); if (waitable->mutexInitialized) { @@ -236,22 +235,11 @@ namespace void ReleasePublicReference(Waitable* waitable) { - int32_t publicRefCount = - __atomic_sub_fetch(&waitable->publicRefCount, 1, __ATOMIC_ACQ_REL); - assert(publicRefCount >= 0); - - if (publicRefCount == 0 && - waitable->watcherStarted) { - if (__atomic_load_n(&waitable->watcherRunning, __ATOMIC_ACQUIRE) != 0) - { - WriteByte(waitable->cancellationWriteFileDescriptor, true); - } - - int joinResult = pthread_join(waitable->watcherThread, nullptr); - assert(joinResult == 0); - (void)joinResult; - waitable->watcherStarted = false; + minipal::MutexHolder lock(waitable->mutex); + int32_t publicRefCount = + __atomic_sub_fetch(&waitable->publicRefCount, 1, __ATOMIC_ACQ_REL); + assert(publicRefCount >= 0); } ReleaseReference(waitable); @@ -269,9 +257,8 @@ namespace return result == sizeof(value); } - bool SignalPipe(Waitable* waitable) + bool SignalPipeLocked(Waitable* waitable) { - minipal::MutexHolder lock(waitable->mutex); if (waitable->signaled) { return true; @@ -286,6 +273,12 @@ namespace return true; } + bool SignalPipe(Waitable* waitable) + { + minipal::MutexHolder lock(waitable->mutex); + return SignalPipeLocked(waitable); + } + bool ResetPipe(Waitable* waitable) { minipal::MutexHolder lock(waitable->mutex); @@ -554,22 +547,54 @@ namespace return false; } + void SignalProcessWatcherResult(Waitable* waitable, int errorCode) + { + minipal::MutexHolder lock(waitable->mutex); + if (__atomic_load_n(&waitable->publicRefCount, __ATOMIC_ACQUIRE) == 0) + { + return; + } + + waitable->errorCode = errorCode; + if (SignalPipeLocked(waitable)) + { +#if defined(MINIPAL_WAIT_TESTS) + __atomic_add_fetch(&s_processWatcherSignalCount, 1, __ATOMIC_ACQ_REL); +#endif // MINIPAL_WAIT_TESTS + } + } + void* ProcessWatcher(void* argument) { Waitable* waitable = static_cast(argument); - bool exited = HasProcessExited(waitable->processId); +#if defined(MINIPAL_WAIT_TESTS) + __atomic_add_fetch(&s_processWatcherCount, 1, __ATOMIC_ACQ_REL); + if (__atomic_load_n(&s_pauseProcessWatchers, __ATOMIC_ACQUIRE) != 0) + { + __atomic_add_fetch(&s_pausedProcessWatcherCount, 1, __ATOMIC_ACQ_REL); + while (__atomic_load_n(&s_pauseProcessWatchers, __ATOMIC_ACQUIRE) != 0) + { + poll(nullptr, 0, 1); + } + __atomic_sub_fetch(&s_pausedProcessWatcherCount, 1, __ATOMIC_ACQ_REL); + } +#endif // MINIPAL_WAIT_TESTS + + bool exited = false; int errorCode = 0; - while (!exited) + while (__atomic_load_n(&waitable->publicRefCount, __ATOMIC_ACQUIRE) != 0) { - pollfd cancellation = {}; - cancellation.fd = waitable->cancellationReadFileDescriptor; - cancellation.events = POLLIN; + exited = HasProcessExited(waitable->processId); + if (exited) + { + break; + } int result; do { - result = poll(&cancellation, 1, 250); + result = poll(nullptr, 0, 250); } while (result < 0 && errno == EINTR); if (result < 0) @@ -577,59 +602,61 @@ namespace errorCode = errno; break; } + } - if (result > 0) +#if defined(MINIPAL_WAIT_TESTS) + if (exited && __atomic_load_n(&s_pauseExitedProcessWatchers, __ATOMIC_ACQUIRE) != 0) + { + __atomic_add_fetch(&s_pausedExitedProcessWatcherCount, 1, __ATOMIC_ACQ_REL); + while (__atomic_load_n(&s_pauseExitedProcessWatchers, __ATOMIC_ACQUIRE) != 0) { - break; + poll(nullptr, 0, 1); } - - exited = HasProcessExited(waitable->processId); + __atomic_sub_fetch(&s_pausedExitedProcessWatcherCount, 1, __ATOMIC_ACQ_REL); } +#endif // MINIPAL_WAIT_TESTS - if (exited) + if (exited || errorCode != 0) { - SignalPipe(waitable); - } - else if (errorCode != 0) - { - { - minipal::MutexHolder lock(waitable->mutex); - waitable->errorCode = errorCode; - } - SignalPipe(waitable); + SignalProcessWatcherResult(waitable, errorCode); } - __atomic_store_n(&waitable->watcherRunning, 0, __ATOMIC_RELEASE); +#if defined(MINIPAL_WAIT_TESTS) + __atomic_sub_fetch(&s_processWatcherCount, 1, __ATOMIC_ACQ_REL); +#endif // MINIPAL_WAIT_TESTS ReleaseReference(waitable); return nullptr; } bool StartProcessWatcher(Waitable* waitable) { - int cancellationPipe[2]; - if (!CreatePipe(cancellationPipe)) + pthread_attr_t attributes; + if (pthread_attr_init(&attributes) != 0) { return false; } - waitable->cancellationReadFileDescriptor = cancellationPipe[0]; - waitable->cancellationWriteFileDescriptor = cancellationPipe[1]; - - AddReference(waitable); - __atomic_store_n(&waitable->watcherRunning, 1, __ATOMIC_RELEASE); - if (pthread_create( - &waitable->watcherThread, - nullptr, - ProcessWatcher, - waitable) != 0) + int result = pthread_attr_setdetachstate(&attributes, PTHREAD_CREATE_DETACHED); + if (result == 0) { - __atomic_store_n(&waitable->watcherRunning, 0, __ATOMIC_RELEASE); - ReleaseReference(waitable); - return false; + pthread_t watcherThread; + AddReference(waitable); + result = pthread_create( + &watcherThread, + &attributes, + ProcessWatcher, + waitable); + + if (result != 0) + { + ReleaseReference(waitable); + } } - waitable->watcherStarted = true; - return true; + int destroyResult = pthread_attr_destroy(&attributes); + assert(destroyResult == 0); + (void)destroyResult; + return result == 0; } Waitable* CreatePipeWaitable(WaitableKind kind, bool manualReset, bool initialState) @@ -983,6 +1010,7 @@ namespace return nullptr; } +#if !defined(MINIPAL_WAIT_FORCE_PROCESS_WATCHER) #if defined(TARGET_LINUX) && defined(SYS_pidfd_open) int processFileDescriptor; do @@ -1030,6 +1058,7 @@ namespace return waitable; } #endif // MINIPAL_WAIT_USES_KQUEUE +#endif // !MINIPAL_WAIT_FORCE_PROCESS_WATCHER return CreateProcessWatcherWaitable(static_cast(processId)); } @@ -1049,6 +1078,43 @@ namespace } } +#if defined(MINIPAL_WAIT_TESTS) +extern "C" void minipal_wait_test_pause_process_watchers(bool pause) +{ + __atomic_store_n(&s_pauseProcessWatchers, pause ? 1 : 0, __ATOMIC_RELEASE); +} + +extern "C" void minipal_wait_test_pause_exited_process_watchers(bool pause) +{ + __atomic_store_n(&s_pauseExitedProcessWatchers, pause ? 1 : 0, __ATOMIC_RELEASE); +} + +extern "C" int32_t minipal_wait_test_get_process_watcher_count() +{ + return __atomic_load_n(&s_processWatcherCount, __ATOMIC_ACQUIRE); +} + +extern "C" int32_t minipal_wait_test_get_paused_process_watcher_count() +{ + return __atomic_load_n(&s_pausedProcessWatcherCount, __ATOMIC_ACQUIRE); +} + +extern "C" int32_t minipal_wait_test_get_paused_exited_process_watcher_count() +{ + return __atomic_load_n(&s_pausedExitedProcessWatcherCount, __ATOMIC_ACQUIRE); +} + +extern "C" int32_t minipal_wait_test_get_process_watcher_signal_count() +{ + return __atomic_load_n(&s_processWatcherSignalCount, __ATOMIC_ACQUIRE); +} + +extern "C" void minipal_wait_test_reset_process_watcher_signal_count() +{ + __atomic_store_n(&s_processWatcherSignalCount, 0, __ATOMIC_RELEASE); +} +#endif // MINIPAL_WAIT_TESTS + minipal_wait_handle::minipal_wait_handle(void* handle) : m_handle(handle) { diff --git a/src/coreclr/minipal/tests/CMakeLists.txt b/src/coreclr/minipal/tests/CMakeLists.txt index 50a4b772c9e071..fa3e9b21b4f05a 100644 --- a/src/coreclr/minipal/tests/CMakeLists.txt +++ b/src/coreclr/minipal/tests/CMakeLists.txt @@ -10,4 +10,28 @@ target_link_libraries(coreclrminipal_wait_tests if(CLR_CMAKE_HOST_UNIX) target_link_libraries(coreclrminipal_wait_tests PRIVATE pthread) + + add_executable_clr(coreclrminipal_process_watcher_tests + EXCLUDE_FROM_ALL + wait.cpp + ../Unix/wait.cpp + ) + + target_compile_definitions(coreclrminipal_process_watcher_tests + PRIVATE + MINIPAL_WAIT_FORCE_PROCESS_WATCHER=1 + MINIPAL_WAIT_TESTS=1 + ) + + if(HAVE_PIPE2) + target_compile_definitions(coreclrminipal_process_watcher_tests PRIVATE HAVE_PIPE2=1) + else() + target_compile_definitions(coreclrminipal_process_watcher_tests PRIVATE HAVE_PIPE2=0) + endif() + + target_link_libraries(coreclrminipal_process_watcher_tests + PRIVATE + minipal + pthread + ) endif() diff --git a/src/coreclr/minipal/tests/wait.cpp b/src/coreclr/minipal/tests/wait.cpp index 3c1ae985f0d60f..c72b93f4232f85 100644 --- a/src/coreclr/minipal/tests/wait.cpp +++ b/src/coreclr/minipal/tests/wait.cpp @@ -20,6 +20,16 @@ #include "minipal-wait.h" +#if defined(MINIPAL_WAIT_TESTS) +extern "C" void minipal_wait_test_pause_process_watchers(bool pause); +extern "C" void minipal_wait_test_pause_exited_process_watchers(bool pause); +extern "C" int32_t minipal_wait_test_get_process_watcher_count(); +extern "C" int32_t minipal_wait_test_get_paused_process_watcher_count(); +extern "C" int32_t minipal_wait_test_get_paused_exited_process_watcher_count(); +extern "C" int32_t minipal_wait_test_get_process_watcher_signal_count(); +extern "C" void minipal_wait_test_reset_process_watcher_signal_count(); +#endif // MINIPAL_WAIT_TESTS + namespace { struct WaitThreadState @@ -74,6 +84,38 @@ namespace #endif } +#if defined(MINIPAL_WAIT_TESTS) + struct ProcessWaitReleaseState + { + minipal_process_wait* process; + int32_t released; + }; + + bool WaitForValue(int32_t (*getValue)(), int32_t expected, uint32_t timeout) + { + uint64_t start = GetTickMilliseconds(); + do + { + if (getValue() == expected) + { + return true; + } + + SleepMilliseconds(1); + } while (GetTickMilliseconds() - start < timeout); + + return getValue() == expected; + } + + void* ReleaseProcessWait(void* argument) + { + ProcessWaitReleaseState* state = static_cast(argument); + delete state->process; + __atomic_store_n(&state->released, 1, __ATOMIC_RELEASE); + return nullptr; + } +#endif // MINIPAL_WAIT_TESTS + #ifdef HOST_WINDOWS DWORD WINAPI WaitThread(void* argument) #else @@ -363,6 +405,202 @@ namespace } #endif +#if defined(MINIPAL_WAIT_TESTS) + bool TestAbandonedProcessWatcher() + { + pid_t child = fork(); + if (!Check(child >= 0, "fork abandoned process-watcher child")) + { + return false; + } + + if (child == 0) + { + SleepMilliseconds(30000); + _exit(0); + } + + minipal_wait_test_pause_process_watchers(true); + + minipal_process_wait* process = + new (std::nothrow) minipal_process_wait(static_cast(child)); + + bool watcherPaused = + process != nullptr && + process->IsValid() && + WaitForValue(minipal_wait_test_get_paused_process_watcher_count, 1, 2000); + bool success = + Check(process != nullptr && process->IsValid(), "create abandoned process watcher") && + Check(watcherPaused, "pause process watcher"); + + minipal_process_wait* duplicate = watcherPaused + ? new (std::nothrow) minipal_process_wait(*process) + : nullptr; + success = + Check(duplicate != nullptr && duplicate->IsValid(), "duplicate process watcher") && + success; + + if (duplicate != nullptr) + { + delete process; + process = duplicate; + success = + Check( + minipal_wait_test_get_process_watcher_count() == 1, + "process watcher remains while public reference exists") && + success; + } + + ProcessWaitReleaseState state = { process, 0 }; + pthread_t releaseThread; + bool releaseThreadStarted = + watcherPaused && + pthread_create(&releaseThread, nullptr, ReleaseProcessWait, &state) == 0; + success = Check(releaseThreadStarted, "start process-wait release thread") && success; + + bool releasedWithoutWaiting = false; + if (releaseThreadStarted) + { + uint64_t start = GetTickMilliseconds(); + do + { + releasedWithoutWaiting = __atomic_load_n(&state.released, __ATOMIC_ACQUIRE) != 0; + if (!releasedWithoutWaiting) + { + SleepMilliseconds(1); + } + } while (!releasedWithoutWaiting && GetTickMilliseconds() - start < 2000); + } + + success = + Check(releasedWithoutWaiting, "final public release does not join process watcher") && + success; + + minipal_wait_test_pause_process_watchers(false); + + if (releaseThreadStarted) + { + success = Check(pthread_join(releaseThread, nullptr) == 0, "join process-wait release thread") && success; + } + else + { + delete state.process; + } + + success = + Check( + WaitForValue(minipal_wait_test_get_process_watcher_count, 0, 2000), + "abandoned process watcher exits") && + success; + + int status; + pid_t waitResult = waitpid(child, &status, WNOHANG); + success = Check(waitResult == 0, "abandoned process remains running") && success; + if (waitResult == 0) + { + success = Check(kill(child, SIGKILL) == 0, "terminate abandoned process-watcher child") && success; + } + + do + { + waitResult = waitpid(child, &status, 0); + } while (waitResult < 0 && errno == EINTR); + + success = + Check(waitResult == child || (waitResult < 0 && errno == ECHILD), "collect abandoned child process") && + success; + return success; + } + + bool TestAbandonedExitedProcessWatcher() + { + pid_t child = fork(); + if (!Check(child >= 0, "fork exited process-watcher child")) + { + return false; + } + + if (child == 0) + { + SleepMilliseconds(100); + _exit(0); + } + + minipal_wait_test_reset_process_watcher_signal_count(); + minipal_wait_test_pause_exited_process_watchers(true); + + ProcessWaitReleaseState state = + { + new (std::nothrow) minipal_process_wait(static_cast(child)), + 0, + }; + + bool watcherPaused = + state.process != nullptr && + state.process->IsValid() && + WaitForValue(minipal_wait_test_get_paused_exited_process_watcher_count, 1, 2000); + bool success = + Check(state.process != nullptr && state.process->IsValid(), "create exited process watcher") && + Check(watcherPaused, "pause exited process watcher before signaling"); + + pthread_t releaseThread; + bool releaseThreadStarted = + watcherPaused && + pthread_create(&releaseThread, nullptr, ReleaseProcessWait, &state) == 0; + success = Check(releaseThreadStarted, "start exited process-wait release thread") && success; + + bool releasedWithoutWaiting = false; + if (releaseThreadStarted) + { + uint64_t start = GetTickMilliseconds(); + do + { + releasedWithoutWaiting = __atomic_load_n(&state.released, __ATOMIC_ACQUIRE) != 0; + if (!releasedWithoutWaiting) + { + SleepMilliseconds(1); + } + } while (!releasedWithoutWaiting && GetTickMilliseconds() - start < 2000); + } + + success = + Check(releasedWithoutWaiting, "final public release does not join exited process watcher") && + success; + + minipal_wait_test_pause_exited_process_watchers(false); + + if (releaseThreadStarted) + { + success = Check(pthread_join(releaseThread, nullptr) == 0, "join exited process-wait release thread") && success; + } + else + { + delete state.process; + } + + success = + Check( + WaitForValue(minipal_wait_test_get_process_watcher_count, 0, 2000), + "abandoned exited process watcher exits") && + Check( + minipal_wait_test_get_process_watcher_signal_count() == 0, + "abandoned exited process watcher does not signal") && + success; + + int status; + pid_t waitResult; + do + { + waitResult = waitpid(child, &status, 0); + } while (waitResult < 0 && errno == EINTR); + + success = + Check(waitResult == child || (waitResult < 0 && errno == ECHILD), "collect exited child process") && + success; + return success; + } +#endif // MINIPAL_WAIT_TESTS + bool TestProcessExit(const char* executablePath) { #ifdef HOST_WINDOWS @@ -476,6 +714,11 @@ int main(int argc, char** argv) #endif TestProcessExit(argv[0]); +#if defined(MINIPAL_WAIT_TESTS) + success = TestAbandonedProcessWatcher() && success; + success = TestAbandonedExitedProcessWatcher() && success; +#endif // MINIPAL_WAIT_TESTS + if (success) { printf("All minipal wait tests passed.\n"); From 28ac76b54ae7debde8db6f9c92efee8725259b19 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 17 Aug 2026 16:44:16 -0700 Subject: [PATCH 04/12] Move manual-reset event support out of minipal Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/coreclr/debug/di/CMakeLists.txt | 23 ++ src/coreclr/debug/di/process.cpp | 6 +- src/coreclr/debug/di/shared.cpp | 1 + src/coreclr/debug/ee/debugger.cpp | 4 +- src/coreclr/debug/ee/rcthread.cpp | 4 +- src/coreclr/debug/inc/dbgtransportsession.h | 5 +- .../debug/shared/dbgtransportsession.cpp | 38 ++- .../debug/shared/dbgtransportsessionevent.cpp | 301 ++++++++++++++++++ .../debug/shared/dbgtransportsessionevent.h | 50 +++ .../shared/dbgtransportsessioneventtest.cpp | 149 +++++++++ src/coreclr/minipal/Unix/wait.cpp | 16 +- src/coreclr/minipal/Windows/wait.cpp | 4 +- src/coreclr/minipal/minipal-wait.h | 2 +- src/coreclr/minipal/tests/wait.cpp | 62 ++-- 14 files changed, 590 insertions(+), 75 deletions(-) create mode 100644 src/coreclr/debug/shared/dbgtransportsessionevent.cpp create mode 100644 src/coreclr/debug/shared/dbgtransportsessionevent.h create mode 100644 src/coreclr/debug/shared/dbgtransportsessioneventtest.cpp diff --git a/src/coreclr/debug/di/CMakeLists.txt b/src/coreclr/debug/di/CMakeLists.txt index 53f41b9320f760..ce468f49198501 100644 --- a/src/coreclr/debug/di/CMakeLists.txt +++ b/src/coreclr/debug/di/CMakeLists.txt @@ -2,6 +2,10 @@ add_definitions( -DFEATURE_NO_HOST -DFEATURE_METADATA_LOAD_TRUSTED_IMAGES) +if(CLR_CMAKE_HOST_UNIX AND HAVE_PTHREAD_CONDATTR_SETCLOCK) + add_compile_definitions(DBG_TRANSPORT_HAS_PTHREAD_CONDATTR_SETCLOCK=1) +endif() + set(CORDBDI_SOURCES shimprocess.cpp shimcallback.cpp @@ -79,6 +83,25 @@ set_target_properties(cordbdi PROPERTIES DBI_COMPONENT TRUE) target_precompile_headers(cordbdi PRIVATE stdafx.h) add_dependencies(cordbdi eventing_headers) +add_executable_clr(coreclr_debug_session_event_tests + EXCLUDE_FROM_ALL + ../shared/dbgtransportsessionevent.cpp + ../shared/dbgtransportsessioneventtest.cpp +) + +target_link_libraries(coreclr_debug_session_event_tests PRIVATE coreclrminipal) + +if(CLR_CMAKE_HOST_WIN32) + target_link_libraries(coreclr_debug_session_event_tests + PRIVATE + kernel32.lib + ${STATIC_MT_CRT_LIB} + ${STATIC_MT_VCRT_LIB} + ) +elseif(CLR_CMAKE_HOST_UNIX) + target_link_libraries(coreclr_debug_session_event_tests PRIVATE pthread) +endif() + if(CLR_CMAKE_HOST_UNIX) add_library_clr(cordbdi_universal STATIC ${CORDBDI_SOURCES}) set_target_properties(cordbdi_universal PROPERTIES DBI_COMPONENT TRUE) diff --git a/src/coreclr/debug/di/process.cpp b/src/coreclr/debug/di/process.cpp index 9ea3e1ca230eb3..a80fecf3743e7e 100644 --- a/src/coreclr/debug/di/process.cpp +++ b/src/coreclr/debug/di/process.cpp @@ -1620,7 +1620,7 @@ HRESULT CordbProcess::Init() // signal existing RS infrastructure. Eventually get rid of LSEA, LSER completely. // - m_leftSideEventAvailable = new (nothrow) minipal_event(false, false); + m_leftSideEventAvailable = new (nothrow) minipal_event(false); if ((m_leftSideEventAvailable == nullptr) || !m_leftSideEventAvailable->IsValid()) { delete m_leftSideEventAvailable; @@ -8772,7 +8772,7 @@ HRESULT CordbRCEventThread::Init() if (m_cordb == NULL) return E_INVALIDARG; - m_threadControlEvent = new (nothrow) minipal_event(false, false); + m_threadControlEvent = new (nothrow) minipal_event(false); if ((m_threadControlEvent == nullptr) || !m_threadControlEvent->IsValid()) { @@ -10252,7 +10252,7 @@ HRESULT CordbWin32EventThread::Init() m_sendToWin32EventThreadMutex.Init("Win32-Send lock", RSLock::cLockFlat, RSLock::LL_WIN32_SEND_LOCK); - m_threadControlEvent = new (nothrow) minipal_event(false, false); + m_threadControlEvent = new (nothrow) minipal_event(false); if ((m_threadControlEvent == nullptr) || !m_threadControlEvent->IsValid()) { delete m_threadControlEvent; diff --git a/src/coreclr/debug/di/shared.cpp b/src/coreclr/debug/di/shared.cpp index 0ea501673d59b3..4dfb31a3ce445c 100644 --- a/src/coreclr/debug/di/shared.cpp +++ b/src/coreclr/debug/di/shared.cpp @@ -11,5 +11,6 @@ #include "stdafx.h" #include "../shared/utils.cpp" +#include "../shared/dbgtransportsessionevent.cpp" #include "../shared/dbgtransportsession.cpp" #include "../shared/stringcopyholder.cpp" diff --git a/src/coreclr/debug/ee/debugger.cpp b/src/coreclr/debug/ee/debugger.cpp index 8dd92201776b5b..229f3b27a2ed39 100644 --- a/src/coreclr/debug/ee/debugger.cpp +++ b/src/coreclr/debug/ee/debugger.cpp @@ -2078,7 +2078,7 @@ void HelperThreadFavor::Init() CONTRACTL_END; // Create events for managing favors. - m_FavorReadEvent = new (nothrow) minipal_event(false, false); + m_FavorReadEvent = new (nothrow) minipal_event(false); if ((m_FavorReadEvent == nullptr) || !m_FavorReadEvent->IsValid()) { delete m_FavorReadEvent; @@ -2086,7 +2086,7 @@ void HelperThreadFavor::Init() ThrowOutOfMemory(); } - m_FavorAvailableEvent = new (nothrow) minipal_event(false, false); + m_FavorAvailableEvent = new (nothrow) minipal_event(false); if ((m_FavorAvailableEvent == nullptr) || !m_FavorAvailableEvent->IsValid()) { delete m_FavorAvailableEvent; diff --git a/src/coreclr/debug/ee/rcthread.cpp b/src/coreclr/debug/ee/rcthread.cpp index 8592fd378b9053..00934353fc48c0 100644 --- a/src/coreclr/debug/ee/rcthread.cpp +++ b/src/coreclr/debug/ee/rcthread.cpp @@ -276,7 +276,7 @@ HRESULT DebuggerRCThread::Init(void) // Create the thread control event. - m_threadControlEvent = new (nothrow) minipal_event(false, false); + m_threadControlEvent = new (nothrow) minipal_event(false); if ((m_threadControlEvent == nullptr) || !m_threadControlEvent->IsValid()) { delete m_threadControlEvent; @@ -285,7 +285,7 @@ HRESULT DebuggerRCThread::Init(void) } // Track liveness separately so this auto-reset event is only an exit notification. - m_helperThreadExitedEvent = new (nothrow) minipal_event(false, false); + m_helperThreadExitedEvent = new (nothrow) minipal_event(false); if ((m_helperThreadExitedEvent == nullptr) || !m_helperThreadExitedEvent->IsValid()) { delete m_helperThreadExitedEvent; diff --git a/src/coreclr/debug/inc/dbgtransportsession.h b/src/coreclr/debug/inc/dbgtransportsession.h index 5df30d9e1e3349..3fda448d70a462 100644 --- a/src/coreclr/debug/inc/dbgtransportsession.h +++ b/src/coreclr/debug/inc/dbgtransportsession.h @@ -55,6 +55,9 @@ struct DebuggerIPCEvent; struct DbgEventBufferEntry; +#ifdef RIGHT_SIDE_COMPILE +class DbgTransportSessionEvent; +#endif // RIGHT_SIDE_COMPILE // Some simple ad-hoc debug only transport logging. This output is too chatty for an existng CLR logging // channel (and we've run out of bits for an additional channel) and is likely to be of limited use to anyone @@ -643,7 +646,7 @@ class DbgTransportSession #ifdef RIGHT_SIDE_COMPILE // Manual reset event that is signalled whenever the session state is SS_Open or SS_Closed (after waiting // on this event the caller should check to see which state it was). - minipal_event *m_hSessionOpenEvent; + DbgTransportSessionEvent *m_sessionOpenEvent; #endif // RIGHT_SIDE_COMPILE // Thread responsible for initial Connect()/Accept() on a low level transport connection and diff --git a/src/coreclr/debug/shared/dbgtransportsession.cpp b/src/coreclr/debug/shared/dbgtransportsession.cpp index f5721b53ecf92e..915851b841d154 100644 --- a/src/coreclr/debug/shared/dbgtransportsession.cpp +++ b/src/coreclr/debug/shared/dbgtransportsession.cpp @@ -4,6 +4,10 @@ #include "dbgtransportsession.h" +#ifdef RIGHT_SIDE_COMPILE +#include "dbgtransportsessionevent.h" +#endif // RIGHT_SIDE_COMPILE + #if (!defined(RIGHT_SIDE_COMPILE) && defined(FEATURE_DBGIPC_TRANSPORT_VM)) || (defined(RIGHT_SIDE_COMPILE) && defined(FEATURE_DBGIPC_TRANSPORT_DI)) // This is the entry type for the IPC event queue owned by the transport. @@ -60,8 +64,8 @@ DbgTransportSession::~DbgTransportSession() delete [] m_pEventBuffers; #ifdef RIGHT_SIDE_COMPILE - if (m_hSessionOpenEvent) - delete m_hSessionOpenEvent; + if (m_sessionOpenEvent) + delete m_sessionOpenEvent; if (m_hProcessExited) delete m_hProcessExited; @@ -113,11 +117,11 @@ HRESULT DbgTransportSession::Init(DebuggerIPCControlBlock *pDCB) } m_fDebuggerAttached = false; - m_hSessionOpenEvent = new (nothrow) minipal_event(true, false); - if ((m_hSessionOpenEvent == nullptr) || !m_hSessionOpenEvent->IsValid()) + m_sessionOpenEvent = new (nothrow) DbgTransportSessionEvent(); + if ((m_sessionOpenEvent == nullptr) || !m_sessionOpenEvent->IsValid()) { - delete m_hSessionOpenEvent; - m_hSessionOpenEvent = nullptr; + delete m_sessionOpenEvent; + m_sessionOpenEvent = nullptr; return E_OUTOFMEMORY; } @@ -140,7 +144,7 @@ HRESULT DbgTransportSession::Init(DebuggerIPCControlBlock *pDCB) if (m_pEventBuffers == NULL) return E_OUTOFMEMORY; - m_rghEventReadyEvent[IPCET_OldStyle] = new (nothrow) minipal_event(false, false); + m_rghEventReadyEvent[IPCET_OldStyle] = new (nothrow) minipal_event(false); if ((m_rghEventReadyEvent[IPCET_OldStyle] == nullptr) || !m_rghEventReadyEvent[IPCET_OldStyle]->IsValid()) { @@ -149,7 +153,7 @@ HRESULT DbgTransportSession::Init(DebuggerIPCControlBlock *pDCB) return E_OUTOFMEMORY; } - m_rghEventReadyEvent[IPCET_DebugEvent] = new (nothrow) minipal_event(false, false); + m_rghEventReadyEvent[IPCET_DebugEvent] = new (nothrow) minipal_event(false); if ((m_rghEventReadyEvent[IPCET_DebugEvent] == nullptr) || !m_rghEventReadyEvent[IPCET_DebugEvent]->IsValid()) { @@ -213,8 +217,8 @@ void DbgTransportSession::Shutdown() } // Leave m_sStateLock #ifdef RIGHT_SIDE_COMPILE - // Signal the m_hSessionOpenEvent now to quickly error out any callers of WaitForSessionToOpen(). - m_hSessionOpenEvent->Set(); + // Signal the session-open event now to quickly error out any callers of WaitForSessionToOpen(). + m_sessionOpenEvent->Set(); #endif // RIGHT_SIDE_COMPILE } @@ -256,14 +260,14 @@ void DbgTransportSession::CleanupTargetProcess() // returns true if the session opened within the time given (in milliseconds) and false otherwise. bool DbgTransportSession::WaitForSessionToOpen(DWORD dwTimeout) { - int32_t waitResult = minipal_wait_handle::Wait(*m_hSessionOpenEvent, dwTimeout); + DbgTransportSessionEvent::WaitResult waitResult = m_sessionOpenEvent->Wait(dwTimeout); if (m_eState == SS_Closed) return false; - if (waitResult == MINIPAL_WAIT_TIMEOUT) + if (waitResult == DbgTransportSessionEvent::WaitResult::TimedOut) DbgTransportLog(LC_Proxy, "DbgTransportSession::WaitForSessionToOpen(%u) timed out", dwTimeout); - return waitResult == 0; + return waitResult == DbgTransportSessionEvent::WaitResult::Signaled; } //--------------------------------------------------------------------------------------- @@ -705,7 +709,7 @@ HRESULT DbgTransportSession::SendMessage(Message *pMessage, bool fWaitsForReply) HRESULT DbgTransportSession::SendRequestMessageAndWait(Message *pMessage) { // Allocate event to wait for reply on. - pMessage->m_hReplyEvent = new (nothrow) minipal_event(false, false); + pMessage->m_hReplyEvent = new (nothrow) minipal_event(false); if ((pMessage->m_hReplyEvent == nullptr) || !pMessage->m_hReplyEvent->IsValid()) { delete pMessage->m_hReplyEvent; @@ -1264,7 +1268,7 @@ void DbgTransportSession::TransportWorker() #ifdef RIGHT_SIDE_COMPILE // The session is definitely not open at this point. - m_hSessionOpenEvent->Reset(); + m_sessionOpenEvent->Reset(); // On the right side we initiate the connection via Connect(). A failure is dealt with by waiting a // little while and retrying (the LS may take a little while to set up). If there's nobody listening @@ -1514,7 +1518,7 @@ void DbgTransportSession::TransportWorker() #ifdef RIGHT_SIDE_COMPILE // Signal any WaitForSessionToOpen() waiters that we've gotten to SS_Open. - m_hSessionOpenEvent->Set(); + m_sessionOpenEvent->Set(); #endif // RIGHT_SIDE_COMPILE // We're ready to begin receiving normal incoming messages now. @@ -2047,7 +2051,7 @@ void DbgTransportSession::TransportWorker() #ifdef RIGHT_SIDE_COMPILE // The session is definitely not open at this point. - m_hSessionOpenEvent->Reset(); + m_sessionOpenEvent->Reset(); #endif // RIGHT_SIDE_COMPILE // Close the connection if we haven't done so already. diff --git a/src/coreclr/debug/shared/dbgtransportsessionevent.cpp b/src/coreclr/debug/shared/dbgtransportsessionevent.cpp new file mode 100644 index 00000000000000..9b9084631ed7e1 --- /dev/null +++ b/src/coreclr/debug/shared/dbgtransportsessionevent.cpp @@ -0,0 +1,301 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "dbgtransportsessionevent.h" + +#ifdef HOST_WINDOWS + +DbgTransportSessionEvent::DbgTransportSessionEvent() + : m_event(CreateEventW(nullptr, TRUE, FALSE, nullptr)) +{ +} + +DbgTransportSessionEvent::~DbgTransportSessionEvent() +{ + if (m_event != nullptr) + { + CloseHandle(m_event); + } +} + +bool DbgTransportSessionEvent::IsValid() const +{ + return m_event != nullptr; +} + +bool DbgTransportSessionEvent::Set() +{ + if (!IsValid()) + { + return false; + } + + return SetEvent(m_event) != FALSE; +} + +bool DbgTransportSessionEvent::Reset() +{ + if (!IsValid()) + { + return false; + } + + return ResetEvent(m_event) != FALSE; +} + +DbgTransportSessionEvent::WaitResult DbgTransportSessionEvent::Wait(uint32_t timeoutMilliseconds) +{ + if (!IsValid()) + { + return WaitResult::Failed; + } + + DWORD result = WaitForSingleObject(m_event, timeoutMilliseconds); + if (result == WAIT_OBJECT_0) + { + return WaitResult::Signaled; + } + + return result == WAIT_TIMEOUT + ? WaitResult::TimedOut + : WaitResult::Failed; +} + +#else // HOST_WINDOWS + +#include +#include +#include + +namespace +{ + constexpr uint64_t NanosecondsPerMillisecond = 1000000; + constexpr uint64_t NanosecondsPerSecond = 1000000000; + +#ifdef HOST_OSX + void NanosecondsToTimeSpec(uint64_t nanoseconds, timespec* time) + { + time->tv_sec = static_cast(nanoseconds / NanosecondsPerSecond); + time->tv_nsec = static_cast(nanoseconds % NanosecondsPerSecond); + } +#endif // HOST_OSX + +#ifndef HOST_OSX + bool GetDeadline(clockid_t clock, uint32_t timeoutMilliseconds, timespec* deadline) + { + if (clock_gettime(clock, deadline) != 0) + { + return false; + } + + uint64_t nanoseconds = + static_cast(deadline->tv_nsec) + + static_cast(timeoutMilliseconds) * NanosecondsPerMillisecond; + deadline->tv_sec += static_cast(nanoseconds / NanosecondsPerSecond); + deadline->tv_nsec = static_cast(nanoseconds % NanosecondsPerSecond); + return true; + } +#endif // !HOST_OSX +} + +DbgTransportSessionEvent::DbgTransportSessionEvent() + : m_condition{} + , m_mutex{} + , m_generation(0) + , m_conditionInitialized(false) + , m_mutexInitialized(false) + , m_signaled(false) +{ + pthread_condattr_t attributes; + if (pthread_condattr_init(&attributes) != 0) + { + return; + } + +#ifdef DBG_TRANSPORT_HAS_PTHREAD_CONDATTR_SETCLOCK + if (pthread_condattr_setclock(&attributes, CLOCK_MONOTONIC) != 0) + { + int error = pthread_condattr_destroy(&attributes); + assert(error == 0); + (void)error; + return; + } +#endif // DBG_TRANSPORT_HAS_PTHREAD_CONDATTR_SETCLOCK + + if (pthread_mutex_init(&m_mutex, nullptr) != 0) + { + int error = pthread_condattr_destroy(&attributes); + assert(error == 0); + (void)error; + return; + } + + m_mutexInitialized = true; + if (pthread_cond_init(&m_condition, &attributes) == 0) + { + m_conditionInitialized = true; + } + + int error = pthread_condattr_destroy(&attributes); + assert(error == 0); + (void)error; +} + +DbgTransportSessionEvent::~DbgTransportSessionEvent() +{ + if (m_conditionInitialized) + { + int error = pthread_cond_destroy(&m_condition); + assert(error == 0); + (void)error; + } + + if (m_mutexInitialized) + { + int error = pthread_mutex_destroy(&m_mutex); + assert(error == 0); + (void)error; + } +} + +bool DbgTransportSessionEvent::IsValid() const +{ + return m_conditionInitialized; +} + +bool DbgTransportSessionEvent::Set() +{ + if (!IsValid()) + { + return false; + } + + int error = pthread_mutex_lock(&m_mutex); + if (error != 0) + { + return false; + } + + if (!m_signaled) + { + m_signaled = true; + // PAL unregisters every current manual-reset-event waiter during SetEvent. Recording the + // transition provides the same guarantee for condition-variable waiters after a racing Reset. + m_generation++; + error = pthread_cond_broadcast(&m_condition); + } + int unlockError = pthread_mutex_unlock(&m_mutex); + assert(unlockError == 0); + return error == 0 && unlockError == 0; +} + +bool DbgTransportSessionEvent::Reset() +{ + if (!IsValid()) + { + return false; + } + + int error = pthread_mutex_lock(&m_mutex); + if (error != 0) + { + return false; + } + + m_signaled = false; + error = pthread_mutex_unlock(&m_mutex); + assert(error == 0); + return error == 0; +} + +DbgTransportSessionEvent::WaitResult DbgTransportSessionEvent::Wait(uint32_t timeoutMilliseconds) +{ + if (!IsValid()) + { + return WaitResult::Failed; + } + + constexpr uint32_t InfiniteTimeout = UINT32_MAX; + timespec deadline; +#ifdef HOST_OSX + uint64_t endNanoseconds = 0; +#endif // HOST_OSX + if (timeoutMilliseconds != InfiniteTimeout) + { +#ifdef HOST_OSX + uint64_t timeoutNanoseconds = + static_cast(timeoutMilliseconds) * NanosecondsPerMillisecond; + NanosecondsToTimeSpec(timeoutNanoseconds, &deadline); + endNanoseconds = clock_gettime_nsec_np(CLOCK_UPTIME_RAW) + timeoutNanoseconds; +#else +#ifdef DBG_TRANSPORT_HAS_PTHREAD_CONDATTR_SETCLOCK + constexpr clockid_t WaitClock = CLOCK_MONOTONIC; +#else + constexpr clockid_t WaitClock = CLOCK_REALTIME; +#endif // DBG_TRANSPORT_HAS_PTHREAD_CONDATTR_SETCLOCK + if (!GetDeadline(WaitClock, timeoutMilliseconds, &deadline)) + { + return WaitResult::Failed; + } +#endif // HOST_OSX + } + + int error = pthread_mutex_lock(&m_mutex); + if (error != 0) + { + return WaitResult::Failed; + } + + // A generation change means this waiter was present for a Set, regardless of the current reset state. + uint64_t generation = m_generation; + while (!m_signaled && generation == m_generation) + { + error = timeoutMilliseconds == InfiniteTimeout + ? pthread_cond_wait(&m_condition, &m_mutex) +#ifdef HOST_OSX + : pthread_cond_timedwait_relative_np(&m_condition, &m_mutex, &deadline); +#else + : pthread_cond_timedwait(&m_condition, &m_mutex, &deadline); +#endif // HOST_OSX +#ifdef HOST_OSX + if (timeoutMilliseconds != InfiniteTimeout && + error == 0 && + !m_signaled && + generation == m_generation) + { + uint64_t currentNanoseconds = clock_gettime_nsec_np(CLOCK_UPTIME_RAW); + if (currentNanoseconds < endNanoseconds) + { + NanosecondsToTimeSpec(endNanoseconds - currentNanoseconds, &deadline); + } + else + { + error = ETIMEDOUT; + } + } +#endif // HOST_OSX + if (error != 0) + { + break; + } + } + + bool signaled = m_signaled || generation != m_generation; + int unlockError = pthread_mutex_unlock(&m_mutex); + assert(unlockError == 0); + if (unlockError != 0) + { + return WaitResult::Failed; + } + + if (signaled) + { + return WaitResult::Signaled; + } + + return error == ETIMEDOUT + ? WaitResult::TimedOut + : WaitResult::Failed; +} + +#endif // HOST_WINDOWS diff --git a/src/coreclr/debug/shared/dbgtransportsessionevent.h b/src/coreclr/debug/shared/dbgtransportsessionevent.h new file mode 100644 index 00000000000000..c8400bfb561f93 --- /dev/null +++ b/src/coreclr/debug/shared/dbgtransportsessionevent.h @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef __DBGTRANSPORTSESSIONEVENT_H__ +#define __DBGTRANSPORTSESSIONEVENT_H__ + +#include + +#ifdef HOST_WINDOWS +#include +#else +#include +#endif // HOST_WINDOWS + +class DbgTransportSessionEvent final +{ +public: + enum class WaitResult + { + Signaled, + TimedOut, + Failed, + }; + + DbgTransportSessionEvent(); + ~DbgTransportSessionEvent(); + + bool IsValid() const; + bool Set(); + bool Reset(); + WaitResult Wait(uint32_t timeoutMilliseconds); + + DbgTransportSessionEvent(const DbgTransportSessionEvent&) = delete; + DbgTransportSessionEvent& operator=(const DbgTransportSessionEvent&) = delete; + +private: +#ifdef HOST_WINDOWS + HANDLE m_event; +#else + pthread_cond_t m_condition; + pthread_mutex_t m_mutex; + // Allows waiters released by Set to complete even if Reset runs before they reacquire the mutex. + uint64_t m_generation; + bool m_conditionInitialized; + bool m_mutexInitialized; + bool m_signaled; +#endif // HOST_WINDOWS +}; + +#endif // __DBGTRANSPORTSESSIONEVENT_H__ diff --git a/src/coreclr/debug/shared/dbgtransportsessioneventtest.cpp b/src/coreclr/debug/shared/dbgtransportsessioneventtest.cpp new file mode 100644 index 00000000000000..c89e486fbcad06 --- /dev/null +++ b/src/coreclr/debug/shared/dbgtransportsessioneventtest.cpp @@ -0,0 +1,149 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include +#include +#include +#include + +#include "dbgtransportsessionevent.h" + +namespace +{ + struct WaiterState + { + DbgTransportSessionEvent* event; + DbgTransportSessionEvent::WaitResult result; + }; + + bool Check(bool condition, const char* message) + { + if (!condition) + { + fprintf(stderr, "FAILED: %s\n", message); + } + + return condition; + } + + void SleepMilliseconds(uint32_t milliseconds) + { +#ifdef HOST_WINDOWS + Sleep(milliseconds); +#else + timespec delay = {}; + delay.tv_sec = milliseconds / 1000; + delay.tv_nsec = static_cast(milliseconds % 1000) * 1000000; + while (nanosleep(&delay, &delay) != 0 && errno == EINTR); +#endif // HOST_WINDOWS + } + +#ifdef HOST_WINDOWS + DWORD WINAPI WaiterThread(void* argument) +#else + void* WaiterThread(void* argument) +#endif + { + WaiterState* state = static_cast(argument); + state->result = state->event->Wait(UINT32_MAX); +#ifdef HOST_WINDOWS + return 0; +#else + return nullptr; +#endif // HOST_WINDOWS + } + + bool TestManualResetEvent() + { + DbgTransportSessionEvent event; + if (!Check(event.IsValid(), "create session event")) + { + return false; + } + + WaiterState states[] = + { + { &event, DbgTransportSessionEvent::WaitResult::Failed }, + { &event, DbgTransportSessionEvent::WaitResult::Failed }, + }; + +#ifdef HOST_WINDOWS + HANDLE threads[] = + { + CreateThread(nullptr, 0, WaiterThread, &states[0], 0, nullptr), + CreateThread(nullptr, 0, WaiterThread, &states[1], 0, nullptr), + }; + bool threadsStarted = threads[0] != nullptr && threads[1] != nullptr; +#else + pthread_t threads[2]; + bool threadStarted[] = + { + pthread_create(&threads[0], nullptr, WaiterThread, &states[0]) == 0, + pthread_create(&threads[1], nullptr, WaiterThread, &states[1]) == 0, + }; + bool threadsStarted = threadStarted[0] && threadStarted[1]; +#endif // HOST_WINDOWS + + SleepMilliseconds(50); + bool success = + Check( + event.Wait(0) == DbgTransportSessionEvent::WaitResult::TimedOut, + "initial session event is reset") && + Check(threadsStarted, "start session event waiters"); + + success = Check(event.Set(), "set session event") && success; + +#ifdef HOST_WINDOWS + for (HANDLE thread : threads) + { + if (thread != nullptr) + { + success = + Check(WaitForSingleObject(thread, 5000) == WAIT_OBJECT_0, "join session event waiter") && + success; + CloseHandle(thread); + } + } +#else + for (int index = 0; index < 2; index++) + { + if (threadStarted[index]) + { + success = + Check(pthread_join(threads[index], nullptr) == 0, "join session event waiter") && + success; + } + } +#endif // HOST_WINDOWS + + success = + Check( + states[0].result == DbgTransportSessionEvent::WaitResult::Signaled && + states[1].result == DbgTransportSessionEvent::WaitResult::Signaled, + "session event releases every waiter") && + Check( + event.Wait(0) == DbgTransportSessionEvent::WaitResult::Signaled, + "session event remains signaled") && + Check( + event.Wait(0) == DbgTransportSessionEvent::WaitResult::Signaled, + "session event remains signaled for subsequent waits") && + Check(event.Reset(), "reset session event") && + Check( + event.Wait(50) == DbgTransportSessionEvent::WaitResult::TimedOut, + "reset session event times out") && + success; + + return success; + } +} + +int main() +{ + if (!TestManualResetEvent()) + { + return 1; + } + + printf("DbgTransportSessionEvent tests passed.\n"); + return 0; +} diff --git a/src/coreclr/minipal/Unix/wait.cpp b/src/coreclr/minipal/Unix/wait.cpp index e7a505104d4aa3..7cc1c0f25d1596 100644 --- a/src/coreclr/minipal/Unix/wait.cpp +++ b/src/coreclr/minipal/Unix/wait.cpp @@ -63,7 +63,6 @@ namespace WaitableKind kind; minipal_mutex mutex; bool mutexInitialized; - bool manualReset; bool signaled; int readFileDescriptor; int writeFileDescriptor; @@ -317,7 +316,7 @@ namespace return AcquireResult::NotReady; } - if (!waitable->manualReset) + if (waitable->kind == WaitableKind::Event) { if (!ReadByte(waitable->readFileDescriptor)) { @@ -659,7 +658,7 @@ namespace return result == 0; } - Waitable* CreatePipeWaitable(WaitableKind kind, bool manualReset, bool initialState) + Waitable* CreatePipeWaitable(WaitableKind kind, bool initialState) { Waitable* waitable = AllocateWaitable(kind); if (waitable == nullptr) @@ -676,7 +675,6 @@ namespace waitable->readFileDescriptor = eventPipe[0]; waitable->writeFileDescriptor = eventPipe[1]; - waitable->manualReset = manualReset; if (initialState && !SignalPipe(waitable)) { @@ -689,7 +687,7 @@ namespace Waitable* CreateSignaledProcessWaitable(pid_t processId) { - Waitable* waitable = CreatePipeWaitable(WaitableKind::ProcessPipe, true, true); + Waitable* waitable = CreatePipeWaitable(WaitableKind::ProcessPipe, true); if (waitable != nullptr) { waitable->processId = processId; @@ -701,7 +699,7 @@ namespace Waitable* CreateProcessWatcherWaitable(pid_t processId) { - Waitable* waitable = CreatePipeWaitable(WaitableKind::ProcessPipe, true, false); + Waitable* waitable = CreatePipeWaitable(WaitableKind::ProcessPipe, false); if (waitable == nullptr) { return nullptr; @@ -764,7 +762,6 @@ namespace waitable->processId = processId; waitable->processFileDescriptor = processQueue; - waitable->manualReset = true; return waitable; } #endif // MINIPAL_WAIT_USES_KQUEUE @@ -1035,7 +1032,6 @@ namespace waitable->processId = static_cast(processId); waitable->processFileDescriptor = processFileDescriptor; - waitable->manualReset = true; return waitable; } @@ -1133,8 +1129,8 @@ minipal_wait_handle::~minipal_wait_handle() } } -minipal_event::minipal_event(bool manualReset, bool initialState) - : minipal_wait_handle(CreatePipeWaitable(WaitableKind::Event, manualReset, initialState)) +minipal_event::minipal_event(bool initialState) + : minipal_wait_handle(CreatePipeWaitable(WaitableKind::Event, initialState)) { } diff --git a/src/coreclr/minipal/Windows/wait.cpp b/src/coreclr/minipal/Windows/wait.cpp index 754a2141cbaf83..e0cc00f57cd63c 100644 --- a/src/coreclr/minipal/Windows/wait.cpp +++ b/src/coreclr/minipal/Windows/wait.cpp @@ -50,8 +50,8 @@ minipal_wait_handle::~minipal_wait_handle() } } -minipal_event::minipal_event(bool manualReset, bool initialState) - : minipal_wait_handle(CreateEventW(nullptr, manualReset, initialState, nullptr)) +minipal_event::minipal_event(bool initialState) + : minipal_wait_handle(CreateEventW(nullptr, FALSE, initialState, nullptr)) { } diff --git a/src/coreclr/minipal/minipal-wait.h b/src/coreclr/minipal/minipal-wait.h index f8e34081382232..921adb749a7dbf 100644 --- a/src/coreclr/minipal/minipal-wait.h +++ b/src/coreclr/minipal/minipal-wait.h @@ -69,7 +69,7 @@ struct minipal_wait_handle struct minipal_event final : minipal_wait_handle { - minipal_event(bool manualReset, bool initialState); + explicit minipal_event(bool initialState); #ifdef HOST_WINDOWS // Duplicates an existing native event handle. diff --git a/src/coreclr/minipal/tests/wait.cpp b/src/coreclr/minipal/tests/wait.cpp index c72b93f4232f85..ae2bfebf2ca89f 100644 --- a/src/coreclr/minipal/tests/wait.cpp +++ b/src/coreclr/minipal/tests/wait.cpp @@ -133,12 +133,24 @@ namespace bool TestAutoResetEvent() { - minipal_event event(false, false); + minipal_event event(false); + minipal_event initiallySignaledEvent(true); bool success = - Check(event.IsValid(), "create auto-reset event") && + Check(event.IsValid() && initiallySignaledEvent.IsValid(), "create auto-reset events") && + Check( + minipal_wait_handle::Wait(initiallySignaledEvent, 0) == 0, + "initially signaled auto-reset event is acquired") && + Check( + minipal_wait_handle::Wait(initiallySignaledEvent, 0) == MINIPAL_WAIT_TIMEOUT, + "initially signaled auto-reset event is consumed") && Check( minipal_wait_handle::Wait(event, 0) == MINIPAL_WAIT_TIMEOUT, "unsignaled auto-reset event times out") && + Check(event.Set(), "set auto-reset event before reset") && + Check(event.Reset(), "reset auto-reset event") && + Check( + minipal_wait_handle::Wait(event, 0) == MINIPAL_WAIT_TIMEOUT, + "reset auto-reset event times out") && Check(event.Set(), "set auto-reset event") && Check(event.Set(), "coalesce repeated auto-reset set") && Check( @@ -151,36 +163,13 @@ namespace return success; } - bool TestManualResetEvent() - { - minipal_event event(true, true); - bool success = - Check(event.IsValid(), "create manual-reset event") && - Check( - minipal_wait_handle::Wait(event, 0) == 0, - "initially signaled manual-reset event") && - Check( - minipal_wait_handle::Wait(event, 0) == 0, - "manual-reset event remains signaled") && - Check(event.Reset(), "reset manual-reset event") && - Check( - minipal_wait_handle::Wait(event, 0) == MINIPAL_WAIT_TIMEOUT, - "manual-reset event becomes unsignaled") && - Check(event.Set(), "set manual-reset event") && - Check( - minipal_wait_handle::Wait(event, 0) == 0, - "manual-reset event can be signaled again"); - - return success; - } - bool TestLowestIndexPriority() { minipal_event events[3] = { - { false, false }, - { false, false }, - { false, false }, + minipal_event(false), + minipal_event(false), + minipal_event(false), }; const minipal_wait_handle* waitSet[] = { &events[0], &events[1], &events[2] }; @@ -200,7 +189,7 @@ namespace bool TestTimeout() { - minipal_event event(false, false); + minipal_event event(false); if (!Check(event.IsValid(), "create timeout event")) { return false; @@ -220,7 +209,7 @@ namespace bool TestAutoResetReleasesSingleWaiter() { - minipal_event event(false, false); + minipal_event event(false); WaitThreadState states[2] = { { event, 2000 }, @@ -288,7 +277,7 @@ namespace { minipal_event* duplicate; { - minipal_event event(false, false); + minipal_event event(false); duplicate = new (std::nothrow) minipal_event(event); } @@ -305,7 +294,7 @@ namespace bool TestInvalidWaitSet() { - minipal_event event(false, false); + minipal_event event(false); minipal_process_wait invalidProcess(static_cast(0)); const minipal_wait_handle* handles[] = { &event, nullptr }; @@ -334,7 +323,7 @@ namespace bool TestConcurrentWait() { - minipal_event event(false, false); + minipal_event event(false); WaitThreadState state(event, MINIPAL_WAIT_INFINITE); #ifdef HOST_WINDOWS @@ -382,7 +371,7 @@ namespace return false; } - minipal_event event(false, false); + minipal_event event(false); WaitThreadState state(event, MINIPAL_WAIT_INFINITE); pthread_t thread; bool threadStarted = pthread_create(&thread, nullptr, WaitThread, &state) == 0; @@ -628,7 +617,7 @@ namespace } minipal_process_wait process(processInfo.dwProcessId); - minipal_event event(false, false); + minipal_event event(false); const minipal_wait_handle* handles[] = { &event, &process }; int32_t result = !process.IsValid() || !event.IsValid() ? MINIPAL_WAIT_FAILED @@ -662,7 +651,7 @@ namespace } minipal_process_wait process(static_cast(child)); - minipal_event event(false, false); + minipal_event event(false); const minipal_wait_handle* handles[] = { &event, &process }; int32_t result = !process.IsValid() || !event.IsValid() ? MINIPAL_WAIT_FAILED @@ -702,7 +691,6 @@ int main(int argc, char** argv) bool success = TestAutoResetEvent() && - TestManualResetEvent() && TestLowestIndexPriority() && TestTimeout() && TestDuplicateLifetime() && From 4195181c656d067735edd54cbcab47ec89e061e3 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 18 Aug 2026 10:19:05 -0700 Subject: [PATCH 05/12] Add a minipal condition variable Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/coreclr/debug/di/CMakeLists.txt | 15 +- src/coreclr/debug/di/shared.cpp | 1 - src/coreclr/debug/inc/dbgtransportsession.h | 19 +- .../debug/shared/dbgtransportsession.cpp | 114 ++++--- .../dbgtransportsessionconditiontest.cpp | 217 +++++++++++++ .../debug/shared/dbgtransportsessionevent.cpp | 301 ------------------ .../debug/shared/dbgtransportsessionevent.h | 50 --- .../shared/dbgtransportsessioneventtest.cpp | 149 --------- src/native/minipal/CMakeLists.txt | 1 + src/native/minipal/conditionvariable.c | 140 ++++++++ src/native/minipal/conditionvariable.h | 60 ++++ src/native/minipal/configure.cmake | 14 + src/native/minipal/minipalconfig.h.in | 1 + 13 files changed, 522 insertions(+), 560 deletions(-) create mode 100644 src/coreclr/debug/shared/dbgtransportsessionconditiontest.cpp delete mode 100644 src/coreclr/debug/shared/dbgtransportsessionevent.cpp delete mode 100644 src/coreclr/debug/shared/dbgtransportsessionevent.h delete mode 100644 src/coreclr/debug/shared/dbgtransportsessioneventtest.cpp create mode 100644 src/native/minipal/conditionvariable.c create mode 100644 src/native/minipal/conditionvariable.h diff --git a/src/coreclr/debug/di/CMakeLists.txt b/src/coreclr/debug/di/CMakeLists.txt index ce468f49198501..7ae8b8e9fce48a 100644 --- a/src/coreclr/debug/di/CMakeLists.txt +++ b/src/coreclr/debug/di/CMakeLists.txt @@ -2,10 +2,6 @@ add_definitions( -DFEATURE_NO_HOST -DFEATURE_METADATA_LOAD_TRUSTED_IMAGES) -if(CLR_CMAKE_HOST_UNIX AND HAVE_PTHREAD_CONDATTR_SETCLOCK) - add_compile_definitions(DBG_TRANSPORT_HAS_PTHREAD_CONDATTR_SETCLOCK=1) -endif() - set(CORDBDI_SOURCES shimprocess.cpp shimcallback.cpp @@ -83,23 +79,22 @@ set_target_properties(cordbdi PROPERTIES DBI_COMPONENT TRUE) target_precompile_headers(cordbdi PRIVATE stdafx.h) add_dependencies(cordbdi eventing_headers) -add_executable_clr(coreclr_debug_session_event_tests +add_executable_clr(coreclr_debug_session_condition_tests EXCLUDE_FROM_ALL - ../shared/dbgtransportsessionevent.cpp - ../shared/dbgtransportsessioneventtest.cpp + ../shared/dbgtransportsessionconditiontest.cpp ) -target_link_libraries(coreclr_debug_session_event_tests PRIVATE coreclrminipal) +target_link_libraries(coreclr_debug_session_condition_tests PRIVATE coreclrminipal) if(CLR_CMAKE_HOST_WIN32) - target_link_libraries(coreclr_debug_session_event_tests + target_link_libraries(coreclr_debug_session_condition_tests PRIVATE kernel32.lib ${STATIC_MT_CRT_LIB} ${STATIC_MT_VCRT_LIB} ) elseif(CLR_CMAKE_HOST_UNIX) - target_link_libraries(coreclr_debug_session_event_tests PRIVATE pthread) + target_link_libraries(coreclr_debug_session_condition_tests PRIVATE pthread) endif() if(CLR_CMAKE_HOST_UNIX) diff --git a/src/coreclr/debug/di/shared.cpp b/src/coreclr/debug/di/shared.cpp index 4dfb31a3ce445c..0ea501673d59b3 100644 --- a/src/coreclr/debug/di/shared.cpp +++ b/src/coreclr/debug/di/shared.cpp @@ -11,6 +11,5 @@ #include "stdafx.h" #include "../shared/utils.cpp" -#include "../shared/dbgtransportsessionevent.cpp" #include "../shared/dbgtransportsession.cpp" #include "../shared/stringcopyholder.cpp" diff --git a/src/coreclr/debug/inc/dbgtransportsession.h b/src/coreclr/debug/inc/dbgtransportsession.h index 3fda448d70a462..03764e750afc76 100644 --- a/src/coreclr/debug/inc/dbgtransportsession.h +++ b/src/coreclr/debug/inc/dbgtransportsession.h @@ -11,6 +11,7 @@ #endif // !RIGHT_SIDE_COMPILE #include +#include #include #include "minipal-wait.h" @@ -55,9 +56,6 @@ struct DebuggerIPCEvent; struct DbgEventBufferEntry; -#ifdef RIGHT_SIDE_COMPILE -class DbgTransportSessionEvent; -#endif // RIGHT_SIDE_COMPILE // Some simple ad-hoc debug only transport logging. This output is too chatty for an existng CLR logging // channel (and we've run out of bits for an additional channel) and is likely to be of limited use to anyone @@ -283,6 +281,13 @@ class DbgTransportLock final void Enter(); void Leave(); +#ifdef RIGHT_SIDE_COMPILE + minipal_mutex& GetMutex() + { + return m_sLock; + } +#endif // RIGHT_SIDE_COMPILE + private: #ifdef RIGHT_SIDE_COMPILE minipal_mutex m_sLock; @@ -644,9 +649,9 @@ class DbgTransportSession SessionState m_eState; #ifdef RIGHT_SIDE_COMPILE - // Manual reset event that is signalled whenever the session state is SS_Open or SS_Closed (after waiting - // on this event the caller should check to see which state it was). - DbgTransportSessionEvent *m_sessionOpenEvent; + // Notified whenever the session reaches a state that resolves WaitForSessionToOpen(). + minipal_condition_variable m_sessionStateCondition; + bool m_fInitSessionStateCondition; #endif // RIGHT_SIDE_COMPILE // Thread responsible for initial Connect()/Accept() on a low level transport connection and @@ -815,6 +820,8 @@ class DbgTransportSession // Initialize all session state to correct starting values. Used during Init() and on the LS when we // gracefully close one session and prepare for another. void InitSessionState(); + void SetSessionState(SessionState state); + void SetSessionStateUnderLock(SessionState state); // The entry point of the transport worker thread. This one's static, so we immediately dispatch to an // instance method version defined below for convenience in the implementation. diff --git a/src/coreclr/debug/shared/dbgtransportsession.cpp b/src/coreclr/debug/shared/dbgtransportsession.cpp index 915851b841d154..a9c7ba859df802 100644 --- a/src/coreclr/debug/shared/dbgtransportsession.cpp +++ b/src/coreclr/debug/shared/dbgtransportsession.cpp @@ -5,7 +5,7 @@ #include "dbgtransportsession.h" #ifdef RIGHT_SIDE_COMPILE -#include "dbgtransportsessionevent.h" +#include #endif // RIGHT_SIDE_COMPILE #if (!defined(RIGHT_SIDE_COMPILE) && defined(FEATURE_DBGIPC_TRANSPORT_VM)) || (defined(RIGHT_SIDE_COMPILE) && defined(FEATURE_DBGIPC_TRANSPORT_DI)) @@ -64,8 +64,8 @@ DbgTransportSession::~DbgTransportSession() delete [] m_pEventBuffers; #ifdef RIGHT_SIDE_COMPILE - if (m_sessionOpenEvent) - delete m_sessionOpenEvent; + if (m_fInitSessionStateCondition) + minipal_condition_variable_destroy(&m_sessionStateCondition); if (m_hProcessExited) delete m_hProcessExited; @@ -101,6 +101,10 @@ HRESULT DbgTransportSession::Init(DebuggerIPCControlBlock *pDCB) m_fInitStateLock = true; #ifdef RIGHT_SIDE_COMPILE + if (!minipal_condition_variable_init(&m_sessionStateCondition)) + return E_OUTOFMEMORY; + m_fInitSessionStateCondition = true; + // The RS randomly allocates a session ID which is sent to the LS in the SessionRequest message. In the // case of network errors during session formation this allows the LS to tell SessionRequest re-sends from // a new request from a different RS. @@ -117,14 +121,6 @@ HRESULT DbgTransportSession::Init(DebuggerIPCControlBlock *pDCB) } m_fDebuggerAttached = false; - m_sessionOpenEvent = new (nothrow) DbgTransportSessionEvent(); - if ((m_sessionOpenEvent == nullptr) || !m_sessionOpenEvent->IsValid()) - { - delete m_sessionOpenEvent; - m_sessionOpenEvent = nullptr; - return E_OUTOFMEMORY; - } - #else // RIGHT_SIDE_COMPILE m_pDCB = pDCB; @@ -207,7 +203,7 @@ void DbgTransportSession::Shutdown() // Remember previous state and transition to SS_Closed. SessionState ePreviousState = m_eState; - m_eState = SS_Closed; + SetSessionStateUnderLock(SS_Closed); if (ePreviousState != SS_Closed && m_channel != NULL) { @@ -215,11 +211,6 @@ void DbgTransportSession::Shutdown() } } // Leave m_sStateLock - -#ifdef RIGHT_SIDE_COMPILE - // Signal the session-open event now to quickly error out any callers of WaitForSessionToOpen(). - m_sessionOpenEvent->Set(); -#endif // RIGHT_SIDE_COMPILE } // The transport instance is no longer valid @@ -236,7 +227,7 @@ void DbgTransportSession::Neuter() // Simply set the session state to SS_Closed. The transport thread will switch itself off if it ever gets // a connection but the rest of the transport resources remain valid (so the debugger helper thread won't // AV on a deallocated handle, which might happen if we simply called Shutdown()). - m_eState = SS_Closed; + SetSessionState(SS_Closed); } #else // RIGHT_SIDE_COMPILE @@ -260,14 +251,44 @@ void DbgTransportSession::CleanupTargetProcess() // returns true if the session opened within the time given (in milliseconds) and false otherwise. bool DbgTransportSession::WaitForSessionToOpen(DWORD dwTimeout) { - DbgTransportSessionEvent::WaitResult waitResult = m_sessionOpenEvent->Wait(dwTimeout); - if (m_eState == SS_Closed) - return false; + int64_t start = minipal_lowres_ticks(); + uint32_t remaining = dwTimeout; + TransportLockHolder lock(m_sStateLock); + + while (m_eState != SS_Open && m_eState != SS_Closed) + { + minipal_condition_variable_result result = + minipal_condition_variable_wait(&m_sessionStateCondition, &m_sStateLock.GetMutex(), remaining); + if (result == MINIPAL_CONDITION_VARIABLE_FAILED) + { + return false; + } + + if (m_eState == SS_Open || m_eState == SS_Closed) + { + break; + } + + if (result == MINIPAL_CONDITION_VARIABLE_TIMED_OUT) + { + DbgTransportLog(LC_Proxy, "DbgTransportSession::WaitForSessionToOpen(%u) timed out", dwTimeout); + return false; + } + + if (dwTimeout != INFINITE) + { + int64_t elapsed = minipal_lowres_ticks() - start; + if (elapsed >= static_cast(dwTimeout)) + { + DbgTransportLog(LC_Proxy, "DbgTransportSession::WaitForSessionToOpen(%u) timed out", dwTimeout); + return false; + } - if (waitResult == DbgTransportSessionEvent::WaitResult::TimedOut) - DbgTransportLog(LC_Proxy, "DbgTransportSession::WaitForSessionToOpen(%u) timed out", dwTimeout); + remaining = dwTimeout - static_cast(elapsed); + } + } - return waitResult == DbgTransportSessionEvent::WaitResult::Signaled; + return m_eState == SS_Open; } //--------------------------------------------------------------------------------------- @@ -1211,6 +1232,26 @@ void DbgTransportSession::InitSessionState() m_idxEventBufferTail = 0; } +void DbgTransportSession::SetSessionState(SessionState state) +{ + TransportLockHolder lock(m_sStateLock); + SetSessionStateUnderLock(state); +} + +void DbgTransportSession::SetSessionStateUnderLock(SessionState state) +{ + m_eState = state; + +#ifdef RIGHT_SIDE_COMPILE + if (state == SS_Open || state == SS_Closed) + { + bool result = minipal_condition_variable_broadcast(&m_sessionStateCondition); + _ASSERTE(result); + (void)result; + } +#endif // RIGHT_SIDE_COMPILE +} + // The entry point of the transport worker thread. This one's static, so we immediately dispatch to an // instance method version defined below for convenience in the implementation. DWORD WINAPI DbgTransportSession::TransportWorkerStatic(LPVOID pvContext) @@ -1236,7 +1277,7 @@ DWORD WINAPI DbgTransportSession::TransportWorkerStatic(LPVOID pvContext) } while (false) #define HANDLE_CRITICAL_ERROR() do { \ - m_eState = SS_Closed; \ + SetSessionState(SS_Closed); \ goto Shutdown; \ } while (false) @@ -1267,9 +1308,6 @@ void DbgTransportSession::TransportWorker() } #ifdef RIGHT_SIDE_COMPILE - // The session is definitely not open at this point. - m_sessionOpenEvent->Reset(); - // On the right side we initiate the connection via Connect(). A failure is dealt with by waiting a // little while and retrying (the LS may take a little while to set up). If there's nobody listening // the debugger will eventually get bored waiting for us and shutdown the session, which will @@ -1511,16 +1549,11 @@ void DbgTransportSession::TransportWorker() if (m_eState == SS_Closed) break; else if (m_eState == SS_Opening) - m_eState = SS_Open; + SetSessionStateUnderLock(SS_Open); else _ASSERTE(!"Bad session state"); } // Leave m_sStateLock -#ifdef RIGHT_SIDE_COMPILE - // Signal any WaitForSessionToOpen() waiters that we've gotten to SS_Open. - m_sessionOpenEvent->Set(); -#endif // RIGHT_SIDE_COMPILE - // We're ready to begin receiving normal incoming messages now. } else @@ -1642,7 +1675,7 @@ void DbgTransportSession::TransportWorker() // Finished processing queued sends. We can transition to the SS_Open state now as long as there // wasn't a send failure or an asynchronous Shutdown(). if (m_eState == SS_Resync) - m_eState = SS_Open; + SetSessionStateUnderLock(SS_Open); else if (m_eState == SS_Closed) break; else if (m_eState == SS_Resync_NC) @@ -1759,14 +1792,14 @@ void DbgTransportSession::TransportWorker() case MT_SessionReject: case MT_SessionResync: // Illegal messages at this time, fail the transport entirely. - m_eState = SS_Closed; + SetSessionState(SS_Closed); break; case MT_SessionClose: // Close is legal on the LS and transitions to the SS_Opening_NC state. It's illegal on the RS // and should shutdown the transport. #ifdef RIGHT_SIDE_COMPILE - m_eState = SS_Closed; + SetSessionState(SS_Closed); break; #else // RIGHT_SIDE_COMPILE // We need to do some state cleanup here, since when we reform a connection (if ever, it will @@ -1777,7 +1810,7 @@ void DbgTransportSession::TransportWorker() // Check we're still in a good state before a clean restart. if (m_eState != SS_Open) { - m_eState = SS_Closed; + SetSessionStateUnderLock(SS_Closed); break; } @@ -2049,11 +2082,6 @@ void DbgTransportSession::TransportWorker() _ASSERTE(m_eState == SS_Closed); -#ifdef RIGHT_SIDE_COMPILE - // The session is definitely not open at this point. - m_sessionOpenEvent->Reset(); -#endif // RIGHT_SIDE_COMPILE - // Close the connection if we haven't done so already. if (m_channel != NULL) m_channel->CloseConnection(); diff --git a/src/coreclr/debug/shared/dbgtransportsessionconditiontest.cpp b/src/coreclr/debug/shared/dbgtransportsessionconditiontest.cpp new file mode 100644 index 00000000000000..d2bf4c9061041e --- /dev/null +++ b/src/coreclr/debug/shared/dbgtransportsessionconditiontest.cpp @@ -0,0 +1,217 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include +#include +#include +#include + +#include + +namespace +{ + enum class TestState + { + Opening, + Open, + Closed, + }; + + struct WaiterState + { + minipal_condition_variable* condition; + minipal_mutex* mutex; + TestState* state; + TestState result; + }; + + bool Check(bool condition, const char* message) + { + if (!condition) + { + fprintf(stderr, "FAILED: %s\n", message); + } + + return condition; + } + + void SleepMilliseconds(uint32_t milliseconds) + { +#ifdef HOST_WINDOWS + Sleep(milliseconds); +#else + timespec delay = {}; + delay.tv_sec = milliseconds / 1000; + delay.tv_nsec = static_cast(milliseconds % 1000) * 1000000; + while (nanosleep(&delay, &delay) != 0 && errno == EINTR); +#endif // HOST_WINDOWS + } + +#ifdef HOST_WINDOWS + DWORD WINAPI WaiterThread(void* argument) +#else + void* WaiterThread(void* argument) +#endif + { + WaiterState* waiter = static_cast(argument); + minipal_mutex_enter(waiter->mutex); + while (*waiter->state == TestState::Opening) + { + minipal_condition_variable_result result = + minipal_condition_variable_wait( + waiter->condition, + waiter->mutex, + MINIPAL_CONDITION_VARIABLE_INFINITE); + if (result == MINIPAL_CONDITION_VARIABLE_FAILED) + { + break; + } + } + waiter->result = *waiter->state; + minipal_mutex_leave(waiter->mutex); + +#ifdef HOST_WINDOWS + return 0; +#else + return nullptr; +#endif // HOST_WINDOWS + } + + bool TestStateCondition() + { + minipal_mutex mutex; + if (!Check(minipal_mutex_init(&mutex), "create state mutex")) + { + return false; + } + + minipal_condition_variable condition; + if (!Check(minipal_condition_variable_init(&condition), "create state condition")) + { + minipal_mutex_destroy(&mutex); + return false; + } + + TestState state = TestState::Opening; + WaiterState waiters[] = + { + { &condition, &mutex, &state, TestState::Opening }, + { &condition, &mutex, &state, TestState::Opening }, + }; + +#ifdef HOST_WINDOWS + HANDLE threads[] = + { + CreateThread(nullptr, 0, WaiterThread, &waiters[0], 0, nullptr), + CreateThread(nullptr, 0, WaiterThread, &waiters[1], 0, nullptr), + }; + bool threadsStarted = threads[0] != nullptr && threads[1] != nullptr; +#else + pthread_t threads[2]; + bool threadStarted[] = + { + pthread_create(&threads[0], nullptr, WaiterThread, &waiters[0]) == 0, + pthread_create(&threads[1], nullptr, WaiterThread, &waiters[1]) == 0, + }; + bool threadsStarted = threadStarted[0] && threadStarted[1]; +#endif // HOST_WINDOWS + + SleepMilliseconds(50); + minipal_mutex_enter(&mutex); + state = TestState::Open; + bool broadcast = minipal_condition_variable_broadcast(&condition); + minipal_mutex_leave(&mutex); + + bool success = + Check(threadsStarted, "start state condition waiters") && + Check(broadcast, "broadcast open state"); + +#ifdef HOST_WINDOWS + for (HANDLE thread : threads) + { + if (thread != nullptr) + { + success = + Check(WaitForSingleObject(thread, 5000) == WAIT_OBJECT_0, "join state condition waiter") && + success; + CloseHandle(thread); + } + } +#else + for (int index = 0; index < 2; index++) + { + if (threadStarted[index]) + { + success = + Check(pthread_join(threads[index], nullptr) == 0, "join state condition waiter") && + success; + } + } +#endif // HOST_WINDOWS + + success = + Check( + waiters[0].result == TestState::Open && waiters[1].result == TestState::Open, + "all waiters observe open state") && + success; + + minipal_mutex_enter(&mutex); + state = TestState::Opening; + minipal_condition_variable_result timeout = + minipal_condition_variable_wait(&condition, &mutex, 50); + minipal_mutex_leave(&mutex); + + success = Check(timeout == MINIPAL_CONDITION_VARIABLE_TIMED_OUT, "state condition times out") && success; + + WaiterState closedWaiter = { &condition, &mutex, &state, TestState::Opening }; +#ifdef HOST_WINDOWS + HANDLE closedThread = CreateThread(nullptr, 0, WaiterThread, &closedWaiter, 0, nullptr); + bool closedThreadStarted = closedThread != nullptr; +#else + pthread_t closedThread; + bool closedThreadStarted = pthread_create(&closedThread, nullptr, WaiterThread, &closedWaiter) == 0; +#endif // HOST_WINDOWS + + SleepMilliseconds(50); + minipal_mutex_enter(&mutex); + state = TestState::Closed; + broadcast = minipal_condition_variable_broadcast(&condition); + minipal_mutex_leave(&mutex); + + success = + Check(closedThreadStarted, "start closed-state waiter") && + Check(broadcast, "broadcast closed state") && + success; + + if (closedThreadStarted) + { +#ifdef HOST_WINDOWS + success = + Check(WaitForSingleObject(closedThread, 5000) == WAIT_OBJECT_0, "join closed-state waiter") && + success; + CloseHandle(closedThread); +#else + success = + Check(pthread_join(closedThread, nullptr) == 0, "join closed-state waiter") && + success; +#endif // HOST_WINDOWS + } + + success = Check(closedWaiter.result == TestState::Closed, "waiter observes closed state") && success; + + minipal_condition_variable_destroy(&condition); + minipal_mutex_destroy(&mutex); + return success; + } +} + +int main() +{ + if (!TestStateCondition()) + { + return 1; + } + + printf("DbgTransport session state condition tests passed.\n"); + return 0; +} diff --git a/src/coreclr/debug/shared/dbgtransportsessionevent.cpp b/src/coreclr/debug/shared/dbgtransportsessionevent.cpp deleted file mode 100644 index 9b9084631ed7e1..00000000000000 --- a/src/coreclr/debug/shared/dbgtransportsessionevent.cpp +++ /dev/null @@ -1,301 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#include "dbgtransportsessionevent.h" - -#ifdef HOST_WINDOWS - -DbgTransportSessionEvent::DbgTransportSessionEvent() - : m_event(CreateEventW(nullptr, TRUE, FALSE, nullptr)) -{ -} - -DbgTransportSessionEvent::~DbgTransportSessionEvent() -{ - if (m_event != nullptr) - { - CloseHandle(m_event); - } -} - -bool DbgTransportSessionEvent::IsValid() const -{ - return m_event != nullptr; -} - -bool DbgTransportSessionEvent::Set() -{ - if (!IsValid()) - { - return false; - } - - return SetEvent(m_event) != FALSE; -} - -bool DbgTransportSessionEvent::Reset() -{ - if (!IsValid()) - { - return false; - } - - return ResetEvent(m_event) != FALSE; -} - -DbgTransportSessionEvent::WaitResult DbgTransportSessionEvent::Wait(uint32_t timeoutMilliseconds) -{ - if (!IsValid()) - { - return WaitResult::Failed; - } - - DWORD result = WaitForSingleObject(m_event, timeoutMilliseconds); - if (result == WAIT_OBJECT_0) - { - return WaitResult::Signaled; - } - - return result == WAIT_TIMEOUT - ? WaitResult::TimedOut - : WaitResult::Failed; -} - -#else // HOST_WINDOWS - -#include -#include -#include - -namespace -{ - constexpr uint64_t NanosecondsPerMillisecond = 1000000; - constexpr uint64_t NanosecondsPerSecond = 1000000000; - -#ifdef HOST_OSX - void NanosecondsToTimeSpec(uint64_t nanoseconds, timespec* time) - { - time->tv_sec = static_cast(nanoseconds / NanosecondsPerSecond); - time->tv_nsec = static_cast(nanoseconds % NanosecondsPerSecond); - } -#endif // HOST_OSX - -#ifndef HOST_OSX - bool GetDeadline(clockid_t clock, uint32_t timeoutMilliseconds, timespec* deadline) - { - if (clock_gettime(clock, deadline) != 0) - { - return false; - } - - uint64_t nanoseconds = - static_cast(deadline->tv_nsec) + - static_cast(timeoutMilliseconds) * NanosecondsPerMillisecond; - deadline->tv_sec += static_cast(nanoseconds / NanosecondsPerSecond); - deadline->tv_nsec = static_cast(nanoseconds % NanosecondsPerSecond); - return true; - } -#endif // !HOST_OSX -} - -DbgTransportSessionEvent::DbgTransportSessionEvent() - : m_condition{} - , m_mutex{} - , m_generation(0) - , m_conditionInitialized(false) - , m_mutexInitialized(false) - , m_signaled(false) -{ - pthread_condattr_t attributes; - if (pthread_condattr_init(&attributes) != 0) - { - return; - } - -#ifdef DBG_TRANSPORT_HAS_PTHREAD_CONDATTR_SETCLOCK - if (pthread_condattr_setclock(&attributes, CLOCK_MONOTONIC) != 0) - { - int error = pthread_condattr_destroy(&attributes); - assert(error == 0); - (void)error; - return; - } -#endif // DBG_TRANSPORT_HAS_PTHREAD_CONDATTR_SETCLOCK - - if (pthread_mutex_init(&m_mutex, nullptr) != 0) - { - int error = pthread_condattr_destroy(&attributes); - assert(error == 0); - (void)error; - return; - } - - m_mutexInitialized = true; - if (pthread_cond_init(&m_condition, &attributes) == 0) - { - m_conditionInitialized = true; - } - - int error = pthread_condattr_destroy(&attributes); - assert(error == 0); - (void)error; -} - -DbgTransportSessionEvent::~DbgTransportSessionEvent() -{ - if (m_conditionInitialized) - { - int error = pthread_cond_destroy(&m_condition); - assert(error == 0); - (void)error; - } - - if (m_mutexInitialized) - { - int error = pthread_mutex_destroy(&m_mutex); - assert(error == 0); - (void)error; - } -} - -bool DbgTransportSessionEvent::IsValid() const -{ - return m_conditionInitialized; -} - -bool DbgTransportSessionEvent::Set() -{ - if (!IsValid()) - { - return false; - } - - int error = pthread_mutex_lock(&m_mutex); - if (error != 0) - { - return false; - } - - if (!m_signaled) - { - m_signaled = true; - // PAL unregisters every current manual-reset-event waiter during SetEvent. Recording the - // transition provides the same guarantee for condition-variable waiters after a racing Reset. - m_generation++; - error = pthread_cond_broadcast(&m_condition); - } - int unlockError = pthread_mutex_unlock(&m_mutex); - assert(unlockError == 0); - return error == 0 && unlockError == 0; -} - -bool DbgTransportSessionEvent::Reset() -{ - if (!IsValid()) - { - return false; - } - - int error = pthread_mutex_lock(&m_mutex); - if (error != 0) - { - return false; - } - - m_signaled = false; - error = pthread_mutex_unlock(&m_mutex); - assert(error == 0); - return error == 0; -} - -DbgTransportSessionEvent::WaitResult DbgTransportSessionEvent::Wait(uint32_t timeoutMilliseconds) -{ - if (!IsValid()) - { - return WaitResult::Failed; - } - - constexpr uint32_t InfiniteTimeout = UINT32_MAX; - timespec deadline; -#ifdef HOST_OSX - uint64_t endNanoseconds = 0; -#endif // HOST_OSX - if (timeoutMilliseconds != InfiniteTimeout) - { -#ifdef HOST_OSX - uint64_t timeoutNanoseconds = - static_cast(timeoutMilliseconds) * NanosecondsPerMillisecond; - NanosecondsToTimeSpec(timeoutNanoseconds, &deadline); - endNanoseconds = clock_gettime_nsec_np(CLOCK_UPTIME_RAW) + timeoutNanoseconds; -#else -#ifdef DBG_TRANSPORT_HAS_PTHREAD_CONDATTR_SETCLOCK - constexpr clockid_t WaitClock = CLOCK_MONOTONIC; -#else - constexpr clockid_t WaitClock = CLOCK_REALTIME; -#endif // DBG_TRANSPORT_HAS_PTHREAD_CONDATTR_SETCLOCK - if (!GetDeadline(WaitClock, timeoutMilliseconds, &deadline)) - { - return WaitResult::Failed; - } -#endif // HOST_OSX - } - - int error = pthread_mutex_lock(&m_mutex); - if (error != 0) - { - return WaitResult::Failed; - } - - // A generation change means this waiter was present for a Set, regardless of the current reset state. - uint64_t generation = m_generation; - while (!m_signaled && generation == m_generation) - { - error = timeoutMilliseconds == InfiniteTimeout - ? pthread_cond_wait(&m_condition, &m_mutex) -#ifdef HOST_OSX - : pthread_cond_timedwait_relative_np(&m_condition, &m_mutex, &deadline); -#else - : pthread_cond_timedwait(&m_condition, &m_mutex, &deadline); -#endif // HOST_OSX -#ifdef HOST_OSX - if (timeoutMilliseconds != InfiniteTimeout && - error == 0 && - !m_signaled && - generation == m_generation) - { - uint64_t currentNanoseconds = clock_gettime_nsec_np(CLOCK_UPTIME_RAW); - if (currentNanoseconds < endNanoseconds) - { - NanosecondsToTimeSpec(endNanoseconds - currentNanoseconds, &deadline); - } - else - { - error = ETIMEDOUT; - } - } -#endif // HOST_OSX - if (error != 0) - { - break; - } - } - - bool signaled = m_signaled || generation != m_generation; - int unlockError = pthread_mutex_unlock(&m_mutex); - assert(unlockError == 0); - if (unlockError != 0) - { - return WaitResult::Failed; - } - - if (signaled) - { - return WaitResult::Signaled; - } - - return error == ETIMEDOUT - ? WaitResult::TimedOut - : WaitResult::Failed; -} - -#endif // HOST_WINDOWS diff --git a/src/coreclr/debug/shared/dbgtransportsessionevent.h b/src/coreclr/debug/shared/dbgtransportsessionevent.h deleted file mode 100644 index c8400bfb561f93..00000000000000 --- a/src/coreclr/debug/shared/dbgtransportsessionevent.h +++ /dev/null @@ -1,50 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#ifndef __DBGTRANSPORTSESSIONEVENT_H__ -#define __DBGTRANSPORTSESSIONEVENT_H__ - -#include - -#ifdef HOST_WINDOWS -#include -#else -#include -#endif // HOST_WINDOWS - -class DbgTransportSessionEvent final -{ -public: - enum class WaitResult - { - Signaled, - TimedOut, - Failed, - }; - - DbgTransportSessionEvent(); - ~DbgTransportSessionEvent(); - - bool IsValid() const; - bool Set(); - bool Reset(); - WaitResult Wait(uint32_t timeoutMilliseconds); - - DbgTransportSessionEvent(const DbgTransportSessionEvent&) = delete; - DbgTransportSessionEvent& operator=(const DbgTransportSessionEvent&) = delete; - -private: -#ifdef HOST_WINDOWS - HANDLE m_event; -#else - pthread_cond_t m_condition; - pthread_mutex_t m_mutex; - // Allows waiters released by Set to complete even if Reset runs before they reacquire the mutex. - uint64_t m_generation; - bool m_conditionInitialized; - bool m_mutexInitialized; - bool m_signaled; -#endif // HOST_WINDOWS -}; - -#endif // __DBGTRANSPORTSESSIONEVENT_H__ diff --git a/src/coreclr/debug/shared/dbgtransportsessioneventtest.cpp b/src/coreclr/debug/shared/dbgtransportsessioneventtest.cpp deleted file mode 100644 index c89e486fbcad06..00000000000000 --- a/src/coreclr/debug/shared/dbgtransportsessioneventtest.cpp +++ /dev/null @@ -1,149 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#include -#include -#include -#include - -#include "dbgtransportsessionevent.h" - -namespace -{ - struct WaiterState - { - DbgTransportSessionEvent* event; - DbgTransportSessionEvent::WaitResult result; - }; - - bool Check(bool condition, const char* message) - { - if (!condition) - { - fprintf(stderr, "FAILED: %s\n", message); - } - - return condition; - } - - void SleepMilliseconds(uint32_t milliseconds) - { -#ifdef HOST_WINDOWS - Sleep(milliseconds); -#else - timespec delay = {}; - delay.tv_sec = milliseconds / 1000; - delay.tv_nsec = static_cast(milliseconds % 1000) * 1000000; - while (nanosleep(&delay, &delay) != 0 && errno == EINTR); -#endif // HOST_WINDOWS - } - -#ifdef HOST_WINDOWS - DWORD WINAPI WaiterThread(void* argument) -#else - void* WaiterThread(void* argument) -#endif - { - WaiterState* state = static_cast(argument); - state->result = state->event->Wait(UINT32_MAX); -#ifdef HOST_WINDOWS - return 0; -#else - return nullptr; -#endif // HOST_WINDOWS - } - - bool TestManualResetEvent() - { - DbgTransportSessionEvent event; - if (!Check(event.IsValid(), "create session event")) - { - return false; - } - - WaiterState states[] = - { - { &event, DbgTransportSessionEvent::WaitResult::Failed }, - { &event, DbgTransportSessionEvent::WaitResult::Failed }, - }; - -#ifdef HOST_WINDOWS - HANDLE threads[] = - { - CreateThread(nullptr, 0, WaiterThread, &states[0], 0, nullptr), - CreateThread(nullptr, 0, WaiterThread, &states[1], 0, nullptr), - }; - bool threadsStarted = threads[0] != nullptr && threads[1] != nullptr; -#else - pthread_t threads[2]; - bool threadStarted[] = - { - pthread_create(&threads[0], nullptr, WaiterThread, &states[0]) == 0, - pthread_create(&threads[1], nullptr, WaiterThread, &states[1]) == 0, - }; - bool threadsStarted = threadStarted[0] && threadStarted[1]; -#endif // HOST_WINDOWS - - SleepMilliseconds(50); - bool success = - Check( - event.Wait(0) == DbgTransportSessionEvent::WaitResult::TimedOut, - "initial session event is reset") && - Check(threadsStarted, "start session event waiters"); - - success = Check(event.Set(), "set session event") && success; - -#ifdef HOST_WINDOWS - for (HANDLE thread : threads) - { - if (thread != nullptr) - { - success = - Check(WaitForSingleObject(thread, 5000) == WAIT_OBJECT_0, "join session event waiter") && - success; - CloseHandle(thread); - } - } -#else - for (int index = 0; index < 2; index++) - { - if (threadStarted[index]) - { - success = - Check(pthread_join(threads[index], nullptr) == 0, "join session event waiter") && - success; - } - } -#endif // HOST_WINDOWS - - success = - Check( - states[0].result == DbgTransportSessionEvent::WaitResult::Signaled && - states[1].result == DbgTransportSessionEvent::WaitResult::Signaled, - "session event releases every waiter") && - Check( - event.Wait(0) == DbgTransportSessionEvent::WaitResult::Signaled, - "session event remains signaled") && - Check( - event.Wait(0) == DbgTransportSessionEvent::WaitResult::Signaled, - "session event remains signaled for subsequent waits") && - Check(event.Reset(), "reset session event") && - Check( - event.Wait(50) == DbgTransportSessionEvent::WaitResult::TimedOut, - "reset session event times out") && - success; - - return success; - } -} - -int main() -{ - if (!TestManualResetEvent()) - { - return 1; - } - - printf("DbgTransportSessionEvent tests passed.\n"); - return 0; -} diff --git a/src/native/minipal/CMakeLists.txt b/src/native/minipal/CMakeLists.txt index 53960991f53080..309495bc96e307 100644 --- a/src/native/minipal/CMakeLists.txt +++ b/src/native/minipal/CMakeLists.txt @@ -3,6 +3,7 @@ set(CMAKE_INCLUDE_CURRENT_DIR OFF) include(configure.cmake) set(SOURCES + conditionvariable.c cpufeatures.c descriptorlimit.c entrypoints.c diff --git a/src/native/minipal/conditionvariable.c b/src/native/minipal/conditionvariable.c new file mode 100644 index 00000000000000..18d080dba9d138 --- /dev/null +++ b/src/native/minipal/conditionvariable.c @@ -0,0 +1,140 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include +#include +#include + +#include + +#include "minipalconfig.h" + +#define NANOSECONDS_PER_MILLISECOND 1000000 +#define NANOSECONDS_PER_SECOND 1000000000 + +#if !defined(HOST_WINDOWS) && !HAVE_CLOCK_GETTIME_NSEC_NP +static bool GetDeadline(clockid_t clock, uint32_t timeoutMilliseconds, struct timespec* deadline) +{ + if (clock_gettime(clock, deadline) != 0) + { + return false; + } + + uint64_t nanoseconds = + (uint64_t)deadline->tv_nsec + + (uint64_t)timeoutMilliseconds * NANOSECONDS_PER_MILLISECOND; + deadline->tv_sec += (time_t)(nanoseconds / NANOSECONDS_PER_SECOND); + deadline->tv_nsec = (long)(nanoseconds % NANOSECONDS_PER_SECOND); + return true; +} +#endif // !HOST_WINDOWS && !HAVE_CLOCK_GETTIME_NSEC_NP + +bool minipal_condition_variable_init(minipal_condition_variable* condition) +{ + assert(condition != NULL); +#ifdef HOST_WINDOWS + InitializeConditionVariable(&condition->_impl); + return true; +#else + pthread_condattr_t attributes; + if (pthread_condattr_init(&attributes) != 0) + { + return false; + } + +#if HAVE_PTHREAD_CONDATTR_SETCLOCK + if (pthread_condattr_setclock(&attributes, CLOCK_MONOTONIC) != 0) + { + int error = pthread_condattr_destroy(&attributes); + assert(error == 0); + (void)error; + return false; + } +#endif // HAVE_PTHREAD_CONDATTR_SETCLOCK + + bool success = pthread_cond_init(&condition->_impl, &attributes) == 0; + int error = pthread_condattr_destroy(&attributes); + assert(error == 0); + (void)error; + return success; +#endif // HOST_WINDOWS +} + +void minipal_condition_variable_destroy(minipal_condition_variable* condition) +{ + assert(condition != NULL); +#ifndef HOST_WINDOWS + int error = pthread_cond_destroy(&condition->_impl); + assert(error == 0); + (void)error; +#else + (void)condition; +#endif // !HOST_WINDOWS +} + +bool minipal_condition_variable_broadcast(minipal_condition_variable* condition) +{ + assert(condition != NULL); +#ifdef HOST_WINDOWS + WakeAllConditionVariable(&condition->_impl); + return true; +#else + return pthread_cond_broadcast(&condition->_impl) == 0; +#endif // HOST_WINDOWS +} + +minipal_condition_variable_result minipal_condition_variable_wait( + minipal_condition_variable* condition, + minipal_mutex* mutex, + uint32_t timeoutMilliseconds) +{ + assert(condition != NULL); + assert(mutex != NULL); + +#ifdef HOST_WINDOWS + if (SleepConditionVariableCS(&condition->_impl, &mutex->_impl, timeoutMilliseconds) != FALSE) + { + return MINIPAL_CONDITION_VARIABLE_SIGNALED; + } + + return GetLastError() == ERROR_TIMEOUT + ? MINIPAL_CONDITION_VARIABLE_TIMED_OUT + : MINIPAL_CONDITION_VARIABLE_FAILED; +#else + int error; + if (timeoutMilliseconds == MINIPAL_CONDITION_VARIABLE_INFINITE) + { + error = pthread_cond_wait(&condition->_impl, &mutex->_impl); + } + else + { + struct timespec timeout; +#if HAVE_CLOCK_GETTIME_NSEC_NP + uint64_t nanoseconds = (uint64_t)timeoutMilliseconds * NANOSECONDS_PER_MILLISECOND; + timeout.tv_sec = (time_t)(nanoseconds / NANOSECONDS_PER_SECOND); + timeout.tv_nsec = (long)(nanoseconds % NANOSECONDS_PER_SECOND); + error = pthread_cond_timedwait_relative_np(&condition->_impl, &mutex->_impl, &timeout); +#else +#if HAVE_PTHREAD_CONDATTR_SETCLOCK + const clockid_t waitClock = CLOCK_MONOTONIC; +#else + const clockid_t waitClock = CLOCK_REALTIME; +#endif // HAVE_PTHREAD_CONDATTR_SETCLOCK + if (!GetDeadline(waitClock, timeoutMilliseconds, &timeout)) + { + return MINIPAL_CONDITION_VARIABLE_FAILED; + } + error = pthread_cond_timedwait(&condition->_impl, &mutex->_impl, &timeout); +#endif // HAVE_CLOCK_GETTIME_NSEC_NP + } + + if (error == 0) + { + return MINIPAL_CONDITION_VARIABLE_SIGNALED; + } + + return error == ETIMEDOUT + ? MINIPAL_CONDITION_VARIABLE_TIMED_OUT + : MINIPAL_CONDITION_VARIABLE_FAILED; +#endif // HOST_WINDOWS +} diff --git a/src/native/minipal/conditionvariable.h b/src/native/minipal/conditionvariable.h new file mode 100644 index 00000000000000..85279e74c301c0 --- /dev/null +++ b/src/native/minipal/conditionvariable.h @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef HAVE_MINIPAL_CONDITION_VARIABLE_H +#define HAVE_MINIPAL_CONDITION_VARIABLE_H + +#include +#include + +#include "mutex.h" + +#ifdef HOST_WINDOWS +#include +typedef CONDITION_VARIABLE MINIPAL_CONDITION_VARIABLE_IMPL; +#else +#include +typedef pthread_cond_t MINIPAL_CONDITION_VARIABLE_IMPL; +#endif // HOST_WINDOWS + +#ifdef __cplusplus +extern "C" +{ +#endif // __cplusplus + +typedef struct _minipal_condition_variable +{ + MINIPAL_CONDITION_VARIABLE_IMPL _impl; +} minipal_condition_variable; + +typedef enum _minipal_condition_variable_result +{ + MINIPAL_CONDITION_VARIABLE_SIGNALED, + MINIPAL_CONDITION_VARIABLE_TIMED_OUT, + MINIPAL_CONDITION_VARIABLE_FAILED, +} minipal_condition_variable_result; + +#define MINIPAL_CONDITION_VARIABLE_INFINITE UINT32_MAX + +// Initialize the condition variable. +bool minipal_condition_variable_init(minipal_condition_variable* condition); + +// Destroy the condition variable. No threads may be waiting. +void minipal_condition_variable_destroy(minipal_condition_variable* condition); + +// Wake all threads waiting on the condition variable. +bool minipal_condition_variable_broadcast(minipal_condition_variable* condition); + +// Atomically release the entered mutex and wait, then reacquire it before returning. +// The calling thread must have entered the mutex exactly once. +// The caller must recheck its predicate after every signaled result because wakes may be spurious. +minipal_condition_variable_result minipal_condition_variable_wait( + minipal_condition_variable* condition, + minipal_mutex* mutex, + uint32_t timeoutMilliseconds); + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // HAVE_MINIPAL_CONDITION_VARIABLE_H diff --git a/src/native/minipal/configure.cmake b/src/native/minipal/configure.cmake index 00be7c33163c69..acf3892985996c 100644 --- a/src/native/minipal/configure.cmake +++ b/src/native/minipal/configure.cmake @@ -1,5 +1,6 @@ include(CheckFunctionExists) include(CheckIncludeFiles) +include(CheckLibraryExists) include(CheckSymbolExists) check_include_files("windows.h;bcrypt.h" HAVE_BCRYPT_H) @@ -19,6 +20,19 @@ check_symbol_exists(O_CLOEXEC fcntl.h HAVE_O_CLOEXEC) check_symbol_exists(CLOCK_MONOTONIC_COARSE time.h HAVE_CLOCK_MONOTONIC_COARSE) check_symbol_exists(clock_gettime_nsec_np time.h HAVE_CLOCK_GETTIME_NSEC_NP) +if(CLR_CMAKE_HOST_UNIX) + check_library_exists(pthread pthread_create "" HAVE_LIBPTHREAD) + check_library_exists(c pthread_create "" HAVE_PTHREAD_IN_LIBC) + if(HAVE_LIBPTHREAD) + set(PTHREAD_LIBRARY pthread) + elseif(HAVE_PTHREAD_IN_LIBC) + set(PTHREAD_LIBRARY c) + endif() + if(PTHREAD_LIBRARY) + check_library_exists(${PTHREAD_LIBRARY} pthread_condattr_setclock "" HAVE_PTHREAD_CONDATTR_SETCLOCK) + endif() +endif() + if(CMAKE_C_BYTE_ORDER STREQUAL "BIG_ENDIAN") set(BIGENDIAN 1) endif() diff --git a/src/native/minipal/minipalconfig.h.in b/src/native/minipal/minipalconfig.h.in index f78a66a7fcd852..de01c3ece7bfaf 100644 --- a/src/native/minipal/minipalconfig.h.in +++ b/src/native/minipal/minipalconfig.h.in @@ -13,6 +13,7 @@ #cmakedefine01 HAVE_ELF_AUX_INFO #cmakedefine01 HAVE_CLOCK_MONOTONIC_COARSE #cmakedefine01 HAVE_CLOCK_GETTIME_NSEC_NP +#cmakedefine01 HAVE_PTHREAD_CONDATTR_SETCLOCK #cmakedefine01 BIGENDIAN #cmakedefine01 HAVE_BCRYPT_H #cmakedefine01 HAVE_FSYNC From 876414c515d684eb5fec1982e9fe6b2edf745269 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 18 Aug 2026 11:28:14 -0700 Subject: [PATCH 06/12] Use minipal synchronization for LowLevelMonitor Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../dbgtransportsessionconditiontest.cpp | 159 +++++++++++++++++- .../System/Threading/WaitSubsystem.Unix.cs | 6 +- src/native/libs/System.Native/pal_threading.c | 131 ++++----------- src/native/minipal/conditionvariable.c | 77 +++++++-- src/native/minipal/conditionvariable.h | 10 ++ src/native/minipal/mutex.c | 60 +++++++ src/native/minipal/mutex.h | 19 +++ 7 files changed, 341 insertions(+), 121 deletions(-) diff --git a/src/coreclr/debug/shared/dbgtransportsessionconditiontest.cpp b/src/coreclr/debug/shared/dbgtransportsessionconditiontest.cpp index d2bf4c9061041e..c252266337c6c3 100644 --- a/src/coreclr/debug/shared/dbgtransportsessionconditiontest.cpp +++ b/src/coreclr/debug/shared/dbgtransportsessionconditiontest.cpp @@ -25,6 +25,16 @@ namespace TestState result; }; + struct SignalWaiterState + { + minipal_condition_variable* condition; + minipal_nonrecursive_mutex* mutex; + int* readyCount; + int* wakeTokens; + int* wakeCount; + bool* waitFailed; + }; + bool Check(bool condition, const char* message) { if (!condition) @@ -77,6 +87,152 @@ namespace #endif // HOST_WINDOWS } +#ifdef HOST_WINDOWS + DWORD WINAPI SignalWaiterThread(void* argument) +#else + void* SignalWaiterThread(void* argument) +#endif + { + SignalWaiterState* waiter = static_cast(argument); + minipal_nonrecursive_mutex_enter(waiter->mutex); + (*waiter->readyCount)++; + while (*waiter->wakeTokens == 0) + { + minipal_condition_variable_result result = + minipal_condition_variable_wait_nonrecursive( + waiter->condition, + waiter->mutex, + MINIPAL_CONDITION_VARIABLE_INFINITE); + if (result == MINIPAL_CONDITION_VARIABLE_FAILED) + { + *waiter->waitFailed = true; + break; + } + } + if (*waiter->wakeTokens != 0) + { + (*waiter->wakeTokens)--; + (*waiter->wakeCount)++; + } + minipal_nonrecursive_mutex_leave(waiter->mutex); + +#ifdef HOST_WINDOWS + return 0; +#else + return nullptr; +#endif // HOST_WINDOWS + } + + bool TestSignalCondition() + { + minipal_nonrecursive_mutex mutex; + if (!Check(minipal_nonrecursive_mutex_init(&mutex), "create non-recursive mutex")) + { + return false; + } + + minipal_condition_variable condition; + if (!Check(minipal_condition_variable_init(&condition), "create signal condition")) + { + minipal_nonrecursive_mutex_destroy(&mutex); + return false; + } + + int readyCount = 0; + int wakeTokens = 0; + int wakeCount = 0; + bool waitFailed = false; + SignalWaiterState waiters[] = + { + { &condition, &mutex, &readyCount, &wakeTokens, &wakeCount, &waitFailed }, + { &condition, &mutex, &readyCount, &wakeTokens, &wakeCount, &waitFailed }, + }; + +#ifdef HOST_WINDOWS + HANDLE threads[] = + { + CreateThread(nullptr, 0, SignalWaiterThread, &waiters[0], 0, nullptr), + CreateThread(nullptr, 0, SignalWaiterThread, &waiters[1], 0, nullptr), + }; + bool threadStarted[] = { threads[0] != nullptr, threads[1] != nullptr }; +#else + pthread_t threads[2]; + bool threadStarted[] = + { + pthread_create(&threads[0], nullptr, SignalWaiterThread, &waiters[0]) == 0, + pthread_create(&threads[1], nullptr, SignalWaiterThread, &waiters[1]) == 0, + }; +#endif // HOST_WINDOWS + + int startedCount = (threadStarted[0] ? 1 : 0) + (threadStarted[1] ? 1 : 0); + for (int retry = 0; retry < 2000; retry++) + { + minipal_nonrecursive_mutex_enter(&mutex); + int currentReadyCount = readyCount; + minipal_nonrecursive_mutex_leave(&mutex); + if (currentReadyCount == startedCount) + { + break; + } + SleepMilliseconds(1); + } + + minipal_nonrecursive_mutex_enter(&mutex); + bool waitersReady = readyCount == 2; + wakeTokens = 1; + bool signaled = minipal_condition_variable_signal(&condition); + minipal_nonrecursive_mutex_leave(&mutex); + + for (int retry = 0; retry < 2000; retry++) + { + minipal_nonrecursive_mutex_enter(&mutex); + int currentWakeCount = wakeCount; + minipal_nonrecursive_mutex_leave(&mutex); + if (currentWakeCount == 1) + { + break; + } + SleepMilliseconds(1); + } + + minipal_nonrecursive_mutex_enter(&mutex); + bool oneWaiterWoke = wakeCount == 1; + wakeTokens += startedCount - wakeCount; + bool broadcast = minipal_condition_variable_broadcast(&condition); + minipal_nonrecursive_mutex_leave(&mutex); + + bool success = + Check(startedCount == 2, "start signal waiters") && + Check(waitersReady, "signal waiters are ready") && + Check(signaled, "signal one waiter") && + Check(oneWaiterWoke, "exactly one waiter satisfies the signaled predicate") && + Check(!waitFailed, "condition wait succeeds") && + Check(broadcast, "broadcast remaining waiter"); + + for (int index = 0; index < 2; index++) + { + if (threadStarted[index]) + { +#ifdef HOST_WINDOWS + success = + Check(WaitForSingleObject(threads[index], 5000) == WAIT_OBJECT_0, "join signal waiter") && + success; + CloseHandle(threads[index]); +#else + success = Check(pthread_join(threads[index], nullptr) == 0, "join signal waiter") && success; +#endif // HOST_WINDOWS + } + } + + minipal_nonrecursive_mutex_enter(&mutex); + success = Check(wakeCount == startedCount, "all signal waiters wake") && success; + minipal_nonrecursive_mutex_leave(&mutex); + + minipal_condition_variable_destroy(&condition); + minipal_nonrecursive_mutex_destroy(&mutex); + return success; + } + bool TestStateCondition() { minipal_mutex mutex; @@ -207,7 +363,8 @@ namespace int main() { - if (!TestStateCondition()) + if (!TestSignalCondition() || + !TestStateCondition()) { return 1; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.Unix.cs index 5232c73bd31bc5..1de0d19adb8d8b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.Unix.cs @@ -36,8 +36,8 @@ namespace System.Threading /// interruptible /// - is used for the process-wide lock /// - is the main system dependency of the wait subsystem, and all waits are done through - /// it. It is backed by a C++ equivalent in CoreLib.Native's pal_threading.*, which wraps a pthread mutex/condition - /// pair. Each thread has an instance in , which is used to synchronize the + /// it. It is backed by a native implementation in System.Native that uses a minipal mutex/condition pair. + /// Each thread has an instance in , which is used to synchronize the /// thread's wait state and for waiting. also uses an instance of /// for waiting. /// @@ -107,7 +107,7 @@ namespace System.Threading /// - Most of the wait subsystem is written in C#, so there is no initially required p/invoke /// - , used by the process-wide lock , uses interlocked operations to /// acquire and release the lock when there is no need to wait or to release a waiter. This is significantly faster than - /// using as a lock, which uses pthread mutex functionality through p/invoke. The lock is + /// using as a lock, which uses native synchronization through p/invoke. The lock is /// typically not held for very long, especially since allocations inside the lock will be rare. /// - Since provides mutual exclusion for the states of all s in the /// process, any operation that does not involve waiting or releasing a wait can occur with minimal p/invokes diff --git a/src/native/libs/System.Native/pal_threading.c b/src/native/libs/System.Native/pal_threading.c index eb64103d8e4766..bc25b29016e577 100644 --- a/src/native/libs/System.Native/pal_threading.c +++ b/src/native/libs/System.Native/pal_threading.c @@ -12,17 +12,13 @@ #include #include #include -#include +#include +#include #include #if HAVE_SCHED_GETCPU #include #endif -#if defined(TARGET_OSX) -// So we can use the declaration of pthread_cond_timedwait_relative_np -#undef _XOPEN_SOURCE -#endif - #if defined(TARGET_LINUX) #include /* Definition of FUTEX_* constants */ #include /* Definition of SYS_* constants */ @@ -41,8 +37,8 @@ struct LowLevelMonitor { - pthread_mutex_t Mutex; - pthread_cond_t Condition; + minipal_nonrecursive_mutex Mutex; + minipal_condition_variable Condition; #ifdef DEBUG bool IsLocked; #endif @@ -67,42 +63,17 @@ LowLevelMonitor* SystemNative_LowLevelMonitor_Create(void) return NULL; } - int error; - - error = pthread_mutex_init(&monitor->Mutex, NULL); - if (error != 0) + if (!minipal_nonrecursive_mutex_init(&monitor->Mutex)) { free(monitor); return NULL; } -#if HAVE_PTHREAD_CONDATTR_SETCLOCK - pthread_condattr_t conditionAttributes; - error = pthread_condattr_init(&conditionAttributes); - if (error != 0) - { - goto mutex_destroy; - } - - error = pthread_condattr_setclock(&conditionAttributes, CLOCK_MONOTONIC); - if (error != 0) + if (!minipal_condition_variable_init(&monitor->Condition)) { - error = pthread_condattr_destroy(&conditionAttributes); - assert(error == 0); - goto mutex_destroy; - } - - error = pthread_cond_init(&monitor->Condition, &conditionAttributes); - - int condAttrDestroyError; - condAttrDestroyError = pthread_condattr_destroy(&conditionAttributes); - assert(condAttrDestroyError == 0); -#else - error = pthread_cond_init(&monitor->Condition, NULL); -#endif - if (error != 0) - { - goto mutex_destroy; + minipal_nonrecursive_mutex_destroy(&monitor->Mutex); + free(monitor); + return NULL; } #ifdef DEBUG @@ -110,25 +81,14 @@ LowLevelMonitor* SystemNative_LowLevelMonitor_Create(void) #endif return monitor; - -mutex_destroy: - error = pthread_mutex_destroy(&monitor->Mutex); - assert(error == 0); - free(monitor); - return NULL; } void SystemNative_LowLevelMonitor_Destroy(LowLevelMonitor* monitor) { assert(monitor != NULL); - int error; - - error = pthread_cond_destroy(&monitor->Condition); - assert(error == 0); - - error = pthread_mutex_destroy(&monitor->Mutex); - assert(error == 0); + minipal_condition_variable_destroy(&monitor->Condition); + minipal_nonrecursive_mutex_destroy(&monitor->Mutex); free(monitor); } @@ -137,10 +97,7 @@ void SystemNative_LowLevelMonitor_Acquire(LowLevelMonitor* monitor) { assert(monitor != NULL); - int error; - - error = pthread_mutex_lock(&monitor->Mutex); - assert(error == 0); + minipal_nonrecursive_mutex_enter(&monitor->Mutex); SetIsLocked(monitor, true); } @@ -151,10 +108,7 @@ void SystemNative_LowLevelMonitor_Release(LowLevelMonitor* monitor) SetIsLocked(monitor, false); - int error; - - error = pthread_mutex_unlock(&monitor->Mutex); - assert(error == 0); + minipal_nonrecursive_mutex_leave(&monitor->Mutex); } void SystemNative_LowLevelMonitor_Wait(LowLevelMonitor* monitor) @@ -163,10 +117,12 @@ void SystemNative_LowLevelMonitor_Wait(LowLevelMonitor* monitor) SetIsLocked(monitor, false); - int error; - - error = pthread_cond_wait(&monitor->Condition, &monitor->Mutex); - assert(error == 0); + minipal_condition_variable_result result = + minipal_condition_variable_wait_nonrecursive( + &monitor->Condition, + &monitor->Mutex, + MINIPAL_CONDITION_VARIABLE_INFINITE); + assert(result == MINIPAL_CONDITION_VARIABLE_SIGNALED); SetIsLocked(monitor, true); } @@ -177,55 +133,30 @@ int32_t SystemNative_LowLevelMonitor_TimedWait(LowLevelMonitor *monitor, int32_t SetIsLocked(monitor, false); - int error; - - // Calculate the time at which a timeout should occur, and wait. Older versions of OSX don't support clock_gettime with - // CLOCK_MONOTONIC, so we instead compute the relative timeout duration, and use a relative variant of the timed wait. - struct timespec timeoutTimeSpec; -#if HAVE_CLOCK_GETTIME_NSEC_NP - timeoutTimeSpec.tv_sec = timeoutMilliseconds / 1000; - timeoutTimeSpec.tv_nsec = (timeoutMilliseconds % 1000) * 1000 * 1000; - - error = pthread_cond_timedwait_relative_np(&monitor->Condition, &monitor->Mutex, &timeoutTimeSpec); -#else -#if HAVE_PTHREAD_CONDATTR_SETCLOCK - error = clock_gettime(CLOCK_MONOTONIC, &timeoutTimeSpec); - assert(error == 0); -#else - struct timeval tv; - - error = gettimeofday(&tv, NULL); - assert(error == 0); - - timeoutTimeSpec.tv_sec = tv.tv_sec; - timeoutTimeSpec.tv_nsec = tv.tv_usec * 1000; -#endif - uint64_t nanoseconds = (uint64_t)timeoutMilliseconds * 1000 * 1000 + (uint64_t)timeoutTimeSpec.tv_nsec; - timeoutTimeSpec.tv_sec += nanoseconds / (1000 * 1000 * 1000); - timeoutTimeSpec.tv_nsec = nanoseconds % (1000 * 1000 * 1000); - - error = pthread_cond_timedwait(&monitor->Condition, &monitor->Mutex, &timeoutTimeSpec); -#endif - assert(error == 0 || error == ETIMEDOUT); + minipal_condition_variable_result result = + minipal_condition_variable_wait_nonrecursive( + &monitor->Condition, + &monitor->Mutex, + (uint32_t)timeoutMilliseconds); + assert( + result == MINIPAL_CONDITION_VARIABLE_SIGNALED || + result == MINIPAL_CONDITION_VARIABLE_TIMED_OUT); SetIsLocked(monitor, true); - return error == 0; + return result == MINIPAL_CONDITION_VARIABLE_SIGNALED; } void SystemNative_LowLevelMonitor_Signal_Release(LowLevelMonitor* monitor) { assert(monitor != NULL); - int error; - - error = pthread_cond_signal(&monitor->Condition); - assert(error == 0); + bool result = minipal_condition_variable_signal(&monitor->Condition); + assert(result); SetIsLocked(monitor, false); - error = pthread_mutex_unlock(&monitor->Mutex); - assert(error == 0); + minipal_nonrecursive_mutex_leave(&monitor->Mutex); } #if defined(TARGET_LINUX) diff --git a/src/native/minipal/conditionvariable.c b/src/native/minipal/conditionvariable.c index 18d080dba9d138..1cdcb83ead36ab 100644 --- a/src/native/minipal/conditionvariable.c +++ b/src/native/minipal/conditionvariable.c @@ -83,28 +83,27 @@ bool minipal_condition_variable_broadcast(minipal_condition_variable* condition) #endif // HOST_WINDOWS } -minipal_condition_variable_result minipal_condition_variable_wait( - minipal_condition_variable* condition, - minipal_mutex* mutex, - uint32_t timeoutMilliseconds) +bool minipal_condition_variable_signal(minipal_condition_variable* condition) { assert(condition != NULL); - assert(mutex != NULL); - #ifdef HOST_WINDOWS - if (SleepConditionVariableCS(&condition->_impl, &mutex->_impl, timeoutMilliseconds) != FALSE) - { - return MINIPAL_CONDITION_VARIABLE_SIGNALED; - } - - return GetLastError() == ERROR_TIMEOUT - ? MINIPAL_CONDITION_VARIABLE_TIMED_OUT - : MINIPAL_CONDITION_VARIABLE_FAILED; + WakeConditionVariable(&condition->_impl); + return true; #else + return pthread_cond_signal(&condition->_impl) == 0; +#endif // HOST_WINDOWS +} + +#ifndef HOST_WINDOWS +static minipal_condition_variable_result minipal_condition_variable_wait_pthread( + minipal_condition_variable* condition, + pthread_mutex_t* mutex, + uint32_t timeoutMilliseconds) +{ int error; if (timeoutMilliseconds == MINIPAL_CONDITION_VARIABLE_INFINITE) { - error = pthread_cond_wait(&condition->_impl, &mutex->_impl); + error = pthread_cond_wait(&condition->_impl, mutex); } else { @@ -113,7 +112,7 @@ minipal_condition_variable_result minipal_condition_variable_wait( uint64_t nanoseconds = (uint64_t)timeoutMilliseconds * NANOSECONDS_PER_MILLISECOND; timeout.tv_sec = (time_t)(nanoseconds / NANOSECONDS_PER_SECOND); timeout.tv_nsec = (long)(nanoseconds % NANOSECONDS_PER_SECOND); - error = pthread_cond_timedwait_relative_np(&condition->_impl, &mutex->_impl, &timeout); + error = pthread_cond_timedwait_relative_np(&condition->_impl, mutex, &timeout); #else #if HAVE_PTHREAD_CONDATTR_SETCLOCK const clockid_t waitClock = CLOCK_MONOTONIC; @@ -124,7 +123,7 @@ minipal_condition_variable_result minipal_condition_variable_wait( { return MINIPAL_CONDITION_VARIABLE_FAILED; } - error = pthread_cond_timedwait(&condition->_impl, &mutex->_impl, &timeout); + error = pthread_cond_timedwait(&condition->_impl, mutex, &timeout); #endif // HAVE_CLOCK_GETTIME_NSEC_NP } @@ -136,5 +135,49 @@ minipal_condition_variable_result minipal_condition_variable_wait( return error == ETIMEDOUT ? MINIPAL_CONDITION_VARIABLE_TIMED_OUT : MINIPAL_CONDITION_VARIABLE_FAILED; +} +#endif // !HOST_WINDOWS + +minipal_condition_variable_result minipal_condition_variable_wait( + minipal_condition_variable* condition, + minipal_mutex* mutex, + uint32_t timeoutMilliseconds) +{ + assert(condition != NULL); + assert(mutex != NULL); + +#ifdef HOST_WINDOWS + if (SleepConditionVariableCS(&condition->_impl, &mutex->_impl, timeoutMilliseconds) != FALSE) + { + return MINIPAL_CONDITION_VARIABLE_SIGNALED; + } + + return GetLastError() == ERROR_TIMEOUT + ? MINIPAL_CONDITION_VARIABLE_TIMED_OUT + : MINIPAL_CONDITION_VARIABLE_FAILED; +#else + return minipal_condition_variable_wait_pthread(condition, &mutex->_impl, timeoutMilliseconds); +#endif // HOST_WINDOWS +} + +minipal_condition_variable_result minipal_condition_variable_wait_nonrecursive( + minipal_condition_variable* condition, + minipal_nonrecursive_mutex* mutex, + uint32_t timeoutMilliseconds) +{ + assert(condition != NULL); + assert(mutex != NULL); + +#ifdef HOST_WINDOWS + if (SleepConditionVariableSRW(&condition->_impl, &mutex->_impl, timeoutMilliseconds, 0) != FALSE) + { + return MINIPAL_CONDITION_VARIABLE_SIGNALED; + } + + return GetLastError() == ERROR_TIMEOUT + ? MINIPAL_CONDITION_VARIABLE_TIMED_OUT + : MINIPAL_CONDITION_VARIABLE_FAILED; +#else + return minipal_condition_variable_wait_pthread(condition, &mutex->_impl, timeoutMilliseconds); #endif // HOST_WINDOWS } diff --git a/src/native/minipal/conditionvariable.h b/src/native/minipal/conditionvariable.h index 85279e74c301c0..dd8d7ed2b206df 100644 --- a/src/native/minipal/conditionvariable.h +++ b/src/native/minipal/conditionvariable.h @@ -45,6 +45,9 @@ void minipal_condition_variable_destroy(minipal_condition_variable* condition); // Wake all threads waiting on the condition variable. bool minipal_condition_variable_broadcast(minipal_condition_variable* condition); +// Wake one thread waiting on the condition variable. +bool minipal_condition_variable_signal(minipal_condition_variable* condition); + // Atomically release the entered mutex and wait, then reacquire it before returning. // The calling thread must have entered the mutex exactly once. // The caller must recheck its predicate after every signaled result because wakes may be spurious. @@ -53,6 +56,13 @@ minipal_condition_variable_result minipal_condition_variable_wait( minipal_mutex* mutex, uint32_t timeoutMilliseconds); +// Atomically release the entered non-recursive mutex and wait, then reacquire it before returning. +// The caller must recheck its predicate after every signaled result because wakes may be spurious. +minipal_condition_variable_result minipal_condition_variable_wait_nonrecursive( + minipal_condition_variable* condition, + minipal_nonrecursive_mutex* mutex, + uint32_t timeoutMilliseconds); + #ifdef __cplusplus } #endif // __cplusplus diff --git a/src/native/minipal/mutex.c b/src/native/minipal/mutex.c index 8d34ca95f5a9ae..0fafc8190d4c2a 100644 --- a/src/native/minipal/mutex.c +++ b/src/native/minipal/mutex.c @@ -27,6 +27,28 @@ bool minipal_mutex_init(minipal_mutex* mtx) #endif // HOST_WINDOWS } +bool minipal_nonrecursive_mutex_init(minipal_nonrecursive_mutex* mtx) +{ + assert(mtx != NULL); +#ifdef HOST_WINDOWS + InitializeSRWLock(&mtx->_impl); + return true; +#else + pthread_mutexattr_t mutexAttributes; + int st = pthread_mutexattr_init(&mutexAttributes); + if (st != 0) + return false; + + st = pthread_mutexattr_settype(&mutexAttributes, PTHREAD_MUTEX_NORMAL); + if (st == 0) + st = pthread_mutex_init(&mtx->_impl, &mutexAttributes); + + pthread_mutexattr_destroy(&mutexAttributes); + + return (st == 0); +#endif // HOST_WINDOWS +} + void minipal_mutex_destroy(minipal_mutex* mtx) { assert(mtx != NULL); @@ -43,6 +65,20 @@ void minipal_mutex_destroy(minipal_mutex* mtx) #endif // _DEBUG } +void minipal_nonrecursive_mutex_destroy(minipal_nonrecursive_mutex* mtx) +{ + assert(mtx != NULL); +#ifndef HOST_WINDOWS + int st = pthread_mutex_destroy(&mtx->_impl); + assert(st == 0); + (void)st; +#endif // !HOST_WINDOWS + +#ifdef _DEBUG + memset(mtx, 0, sizeof(*mtx)); +#endif // _DEBUG +} + void minipal_mutex_enter(minipal_mutex* mtx) { assert(mtx != NULL); @@ -55,6 +91,18 @@ void minipal_mutex_enter(minipal_mutex* mtx) #endif // HOST_WINDOWS } +void minipal_nonrecursive_mutex_enter(minipal_nonrecursive_mutex* mtx) +{ + assert(mtx != NULL); +#ifdef HOST_WINDOWS + AcquireSRWLockExclusive(&mtx->_impl); +#else + int st = pthread_mutex_lock(&mtx->_impl); + assert(st == 0); + (void)st; +#endif // HOST_WINDOWS +} + void minipal_mutex_leave(minipal_mutex* mtx) { assert(mtx != NULL); @@ -66,3 +114,15 @@ void minipal_mutex_leave(minipal_mutex* mtx) (void)st; #endif // HOST_WINDOWS } + +void minipal_nonrecursive_mutex_leave(minipal_nonrecursive_mutex* mtx) +{ + assert(mtx != NULL); +#ifdef HOST_WINDOWS + ReleaseSRWLockExclusive(&mtx->_impl); +#else + int st = pthread_mutex_unlock(&mtx->_impl); + assert(st == 0); + (void)st; +#endif // HOST_WINDOWS +} diff --git a/src/native/minipal/mutex.h b/src/native/minipal/mutex.h index df94bac6f1bd33..cc7002cd509bb4 100644 --- a/src/native/minipal/mutex.h +++ b/src/native/minipal/mutex.h @@ -9,9 +9,11 @@ #ifdef HOST_WINDOWS #include typedef CRITICAL_SECTION MINIPAL_MUTEX_IMPL; +typedef SRWLOCK MINIPAL_NONRECURSIVE_MUTEX_IMPL; #else // !HOST_WINDOWS #include typedef pthread_mutex_t MINIPAL_MUTEX_IMPL; +typedef pthread_mutex_t MINIPAL_NONRECURSIVE_MUTEX_IMPL; #endif // HOST_WINDOWS #ifdef __cplusplus @@ -24,19 +26,36 @@ typedef struct _minipal_mutex MINIPAL_MUTEX_IMPL _impl; } minipal_mutex; +typedef struct _minipal_nonrecursive_mutex +{ + MINIPAL_NONRECURSIVE_MUTEX_IMPL _impl; +} minipal_nonrecursive_mutex; + // Initialize the mutex. bool minipal_mutex_init(minipal_mutex* mt); +// Initialize a non-recursive mutex. +bool minipal_nonrecursive_mutex_init(minipal_nonrecursive_mutex* mt); + // Destroy the mutex. void minipal_mutex_destroy(minipal_mutex* mt); +// Destroy the non-recursive mutex. +void minipal_nonrecursive_mutex_destroy(minipal_nonrecursive_mutex* mt); + // Enter the mutex. Blocks until the mutex can be entered. // Recursive enters are allowed. void minipal_mutex_enter(minipal_mutex* mt); +// Enter the non-recursive mutex. Blocks until the mutex can be entered. +void minipal_nonrecursive_mutex_enter(minipal_nonrecursive_mutex* mt); + // Leave the mutex. void minipal_mutex_leave(minipal_mutex* mt); +// Leave the non-recursive mutex. +void minipal_nonrecursive_mutex_leave(minipal_nonrecursive_mutex* mt); + #ifdef __cplusplus } #endif // __cplusplus From bee262ecb80a5c0ce60d7ce51612329047bdc402 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 18 Aug 2026 13:49:32 -0700 Subject: [PATCH 07/12] Use tagged storage for Unix waitables Separate event, process descriptor, and process pipe state into tagged union arms. Use nanosleep for watcher polling and remove the obsolete poll error state. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/coreclr/minipal/Unix/wait.cpp | 197 +++++++++++++++++++++--------- 1 file changed, 137 insertions(+), 60 deletions(-) diff --git a/src/coreclr/minipal/Unix/wait.cpp b/src/coreclr/minipal/Unix/wait.cpp index 7cc1c0f25d1596..110356c0cc83b0 100644 --- a/src/coreclr/minipal/Unix/wait.cpp +++ b/src/coreclr/minipal/Unix/wait.cpp @@ -64,11 +64,27 @@ namespace minipal_mutex mutex; bool mutexInitialized; bool signaled; - int readFileDescriptor; - int writeFileDescriptor; - int processFileDescriptor; - int errorCode; - pid_t processId; + union + { + struct + { + int readFileDescriptor; + int writeFileDescriptor; + } event; + + struct + { + int fileDescriptor; + pid_t processId; + } processFileDescriptor; + + struct + { + int readFileDescriptor; + int writeFileDescriptor; + pid_t processId; + } processPipe; + }; }; void CloseFileDescriptor(int fileDescriptor) @@ -167,7 +183,7 @@ namespace Waitable* AllocateWaitable(WaitableKind kind) { - Waitable* waitable = new (std::nothrow) Waitable{}; + Waitable* waitable = new (std::nothrow) Waitable; if (waitable == nullptr) { return nullptr; @@ -176,9 +192,28 @@ namespace waitable->refCount = 1; waitable->publicRefCount = 1; waitable->kind = kind; - waitable->readFileDescriptor = -1; - waitable->writeFileDescriptor = -1; - waitable->processFileDescriptor = -1; + waitable->mutexInitialized = false; + waitable->signaled = false; + + switch (kind) + { + case WaitableKind::Event: + waitable->event.readFileDescriptor = -1; + waitable->event.writeFileDescriptor = -1; + break; + + case WaitableKind::ProcessFileDescriptor: + case WaitableKind::ProcessKqueue: + waitable->processFileDescriptor.fileDescriptor = -1; + waitable->processFileDescriptor.processId = 0; + break; + + case WaitableKind::ProcessPipe: + waitable->processPipe.readFileDescriptor = -1; + waitable->processPipe.writeFileDescriptor = -1; + waitable->processPipe.processId = 0; + break; + } if (!minipal_mutex_init(&waitable->mutex)) { @@ -195,9 +230,23 @@ namespace assert(__atomic_load_n(&waitable->refCount, __ATOMIC_RELAXED) == 0); assert(__atomic_load_n(&waitable->publicRefCount, __ATOMIC_RELAXED) == 0); - CloseFileDescriptor(waitable->readFileDescriptor); - CloseFileDescriptor(waitable->writeFileDescriptor); - CloseFileDescriptor(waitable->processFileDescriptor); + switch (waitable->kind) + { + case WaitableKind::Event: + CloseFileDescriptor(waitable->event.readFileDescriptor); + CloseFileDescriptor(waitable->event.writeFileDescriptor); + break; + + case WaitableKind::ProcessFileDescriptor: + case WaitableKind::ProcessKqueue: + CloseFileDescriptor(waitable->processFileDescriptor.fileDescriptor); + break; + + case WaitableKind::ProcessPipe: + CloseFileDescriptor(waitable->processPipe.readFileDescriptor); + CloseFileDescriptor(waitable->processPipe.writeFileDescriptor); + break; + } if (waitable->mutexInitialized) { @@ -258,12 +307,16 @@ namespace bool SignalPipeLocked(Waitable* waitable) { + assert(waitable->kind == WaitableKind::Event || waitable->kind == WaitableKind::ProcessPipe); if (waitable->signaled) { return true; } - if (!WriteByte(waitable->writeFileDescriptor, false)) + int writeFileDescriptor = waitable->kind == WaitableKind::Event + ? waitable->event.writeFileDescriptor + : waitable->processPipe.writeFileDescriptor; + if (!WriteByte(writeFileDescriptor, false)) { return false; } @@ -280,6 +333,7 @@ namespace bool ResetPipe(Waitable* waitable) { + assert(waitable->kind == WaitableKind::Event || waitable->kind == WaitableKind::ProcessPipe); minipal::MutexHolder lock(waitable->mutex); if (!waitable->signaled) { @@ -288,9 +342,12 @@ namespace uint8_t buffer[16]; ssize_t result; + int readFileDescriptor = waitable->kind == WaitableKind::Event + ? waitable->event.readFileDescriptor + : waitable->processPipe.readFileDescriptor; do { - result = read(waitable->readFileDescriptor, buffer, sizeof(buffer)); + result = read(readFileDescriptor, buffer, sizeof(buffer)); } while (result > 0 || (result < 0 && errno == EINTR)); if (result < 0 && errno != EAGAIN) @@ -304,13 +361,8 @@ namespace AcquireResult TryAcquirePipe(Waitable* waitable) { + assert(waitable->kind == WaitableKind::Event || waitable->kind == WaitableKind::ProcessPipe); minipal::MutexHolder lock(waitable->mutex); - if (waitable->errorCode != 0) - { - errno = waitable->errorCode; - return AcquireResult::Failed; - } - if (!waitable->signaled) { return AcquireResult::NotReady; @@ -318,7 +370,7 @@ namespace if (waitable->kind == WaitableKind::Event) { - if (!ReadByte(waitable->readFileDescriptor)) + if (!ReadByte(waitable->event.readFileDescriptor)) { return AcquireResult::Failed; } @@ -341,6 +393,7 @@ namespace AcquireResult TryAcquireProcessFileDescriptor(Waitable* waitable) { + assert(waitable->kind == WaitableKind::ProcessFileDescriptor); minipal::MutexHolder lock(waitable->mutex); if (waitable->signaled) { @@ -348,7 +401,7 @@ namespace } pollfd descriptor = {}; - descriptor.fd = waitable->processFileDescriptor; + descriptor.fd = waitable->processFileDescriptor.fileDescriptor; descriptor.events = POLLIN; int result; @@ -367,7 +420,7 @@ namespace return AcquireResult::NotReady; } - ReapChild(waitable->processId); + ReapChild(waitable->processFileDescriptor.processId); waitable->signaled = true; return AcquireResult::Ready; } @@ -375,6 +428,7 @@ namespace #if MINIPAL_WAIT_USES_KQUEUE AcquireResult TryAcquireProcessKqueue(Waitable* waitable) { + assert(waitable->kind == WaitableKind::ProcessKqueue); minipal::MutexHolder lock(waitable->mutex); if (waitable->signaled) { @@ -387,7 +441,7 @@ namespace do { result = kevent( - waitable->processFileDescriptor, + waitable->processFileDescriptor.fileDescriptor, nullptr, 0, &processEvent, @@ -405,7 +459,7 @@ namespace return AcquireResult::NotReady; } - ReapChild(waitable->processId); + ReapChild(waitable->processFileDescriptor.processId); waitable->signaled = true; return AcquireResult::Ready; } @@ -427,10 +481,10 @@ namespace { Waitable* waitable = waitables[handleIndex]; if (waitable->kind == WaitableKind::ProcessKqueue && - waitable->processId == static_cast(events[eventIndex].ident)) + waitable->processFileDescriptor.processId == static_cast(events[eventIndex].ident)) { minipal::MutexHolder lock(waitable->mutex); - ReapChild(waitable->processId); + ReapChild(waitable->processFileDescriptor.processId); waitable->signaled = true; } } @@ -546,15 +600,15 @@ namespace return false; } - void SignalProcessWatcherResult(Waitable* waitable, int errorCode) + void SignalProcessWatcherExit(Waitable* waitable) { + assert(waitable->kind == WaitableKind::ProcessPipe); minipal::MutexHolder lock(waitable->mutex); if (__atomic_load_n(&waitable->publicRefCount, __ATOMIC_ACQUIRE) == 0) { return; } - waitable->errorCode = errorCode; if (SignalPipeLocked(waitable)) { #if defined(MINIPAL_WAIT_TESTS) @@ -566,6 +620,7 @@ namespace void* ProcessWatcher(void* argument) { Waitable* waitable = static_cast(argument); + assert(waitable->kind == WaitableKind::ProcessPipe); #if defined(MINIPAL_WAIT_TESTS) __atomic_add_fetch(&s_processWatcherCount, 1, __ATOMIC_ACQ_REL); if (__atomic_load_n(&s_pauseProcessWatchers, __ATOMIC_ACQUIRE) != 0) @@ -578,29 +633,18 @@ namespace __atomic_sub_fetch(&s_pausedProcessWatcherCount, 1, __ATOMIC_ACQ_REL); } #endif // MINIPAL_WAIT_TESTS - bool exited = false; - int errorCode = 0; while (__atomic_load_n(&waitable->publicRefCount, __ATOMIC_ACQUIRE) != 0) { - exited = HasProcessExited(waitable->processId); + exited = HasProcessExited(waitable->processPipe.processId); if (exited) { break; } - int result; - do - { - result = poll(nullptr, 0, 250); - } while (result < 0 && errno == EINTR); - - if (result < 0) - { - errorCode = errno; - break; - } + timespec sleepDuration = { 0, 250 * 1000 * 1000 }; + nanosleep(&sleepDuration, nullptr); } #if defined(MINIPAL_WAIT_TESTS) @@ -615,9 +659,9 @@ namespace } #endif // MINIPAL_WAIT_TESTS - if (exited || errorCode != 0) + if (exited) { - SignalProcessWatcherResult(waitable, errorCode); + SignalProcessWatcherExit(waitable); } #if defined(MINIPAL_WAIT_TESTS) @@ -660,6 +704,7 @@ namespace Waitable* CreatePipeWaitable(WaitableKind kind, bool initialState) { + assert(kind == WaitableKind::Event || kind == WaitableKind::ProcessPipe); Waitable* waitable = AllocateWaitable(kind); if (waitable == nullptr) { @@ -673,8 +718,17 @@ namespace return nullptr; } - waitable->readFileDescriptor = eventPipe[0]; - waitable->writeFileDescriptor = eventPipe[1]; + if (kind == WaitableKind::Event) + { + waitable->event.readFileDescriptor = eventPipe[0]; + waitable->event.writeFileDescriptor = eventPipe[1]; + } + else + { + assert(kind == WaitableKind::ProcessPipe); + waitable->processPipe.readFileDescriptor = eventPipe[0]; + waitable->processPipe.writeFileDescriptor = eventPipe[1]; + } if (initialState && !SignalPipe(waitable)) { @@ -690,7 +744,7 @@ namespace Waitable* waitable = CreatePipeWaitable(WaitableKind::ProcessPipe, true); if (waitable != nullptr) { - waitable->processId = processId; + waitable->processPipe.processId = processId; ReapChild(processId); } @@ -705,7 +759,7 @@ namespace return nullptr; } - waitable->processId = processId; + waitable->processPipe.processId = processId; if (!StartProcessWatcher(waitable)) { ReleasePublicReference(waitable); @@ -760,8 +814,8 @@ namespace return nullptr; } - waitable->processId = processId; - waitable->processFileDescriptor = processQueue; + waitable->processFileDescriptor.processId = processId; + waitable->processFileDescriptor.fileDescriptor = processQueue; return waitable; } #endif // MINIPAL_WAIT_USES_KQUEUE @@ -779,12 +833,15 @@ namespace switch (waitable->kind) { case WaitableKind::Event: + descriptors[index].fd = waitable->event.readFileDescriptor; + break; + case WaitableKind::ProcessPipe: - descriptors[index].fd = waitable->readFileDescriptor; + descriptors[index].fd = waitable->processPipe.readFileDescriptor; break; case WaitableKind::ProcessFileDescriptor: - descriptors[index].fd = waitable->processFileDescriptor; + descriptors[index].fd = waitable->processFileDescriptor.fileDescriptor; break; default: @@ -883,7 +940,7 @@ namespace { EV_SET( &change, - waitable->processId, + waitable->processFileDescriptor.processId, EVFILT_PROC, EV_ADD | EV_ENABLE | EV_ONESHOT, NOTE_EXIT, @@ -892,9 +949,29 @@ namespace } else { - int fileDescriptor = waitable->kind == WaitableKind::ProcessFileDescriptor - ? waitable->processFileDescriptor - : waitable->readFileDescriptor; + int fileDescriptor; + switch (waitable->kind) + { + case WaitableKind::Event: + fileDescriptor = waitable->event.readFileDescriptor; + break; + + case WaitableKind::ProcessFileDescriptor: + fileDescriptor = waitable->processFileDescriptor.fileDescriptor; + break; + + case WaitableKind::ProcessPipe: + fileDescriptor = waitable->processPipe.readFileDescriptor; + break; + + default: + registrationFailed = true; + break; + } + if (registrationFailed) + { + break; + } EV_SET( &change, fileDescriptor, @@ -910,7 +987,7 @@ namespace if (waitable->kind == WaitableKind::ProcessKqueue && errno == ESRCH) { minipal::MutexHolder lock(waitable->mutex); - ReapChild(waitable->processId); + ReapChild(waitable->processFileDescriptor.processId); waitable->signaled = true; continue; } @@ -1030,8 +1107,8 @@ namespace return nullptr; } - waitable->processId = static_cast(processId); - waitable->processFileDescriptor = processFileDescriptor; + waitable->processFileDescriptor.processId = static_cast(processId); + waitable->processFileDescriptor.fileDescriptor = processFileDescriptor; return waitable; } From 00ba70931c7a7706af399a117ca6b355ed710d88 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 18 Aug 2026 14:13:04 -0700 Subject: [PATCH 08/12] Remove prototype wait tests Delete the standalone debugger condition test and minipal wait test targets. Remove the associated test-only process watcher hooks and compile-time forcing. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/coreclr/debug/di/CMakeLists.txt | 18 - .../dbgtransportsessionconditiontest.cpp | 374 --------- src/coreclr/minipal/CMakeLists.txt | 4 - src/coreclr/minipal/Unix/wait.cpp | 82 +- src/coreclr/minipal/tests/CMakeLists.txt | 37 - src/coreclr/minipal/tests/wait.cpp | 717 ------------------ 6 files changed, 1 insertion(+), 1231 deletions(-) delete mode 100644 src/coreclr/debug/shared/dbgtransportsessionconditiontest.cpp delete mode 100644 src/coreclr/minipal/tests/CMakeLists.txt delete mode 100644 src/coreclr/minipal/tests/wait.cpp diff --git a/src/coreclr/debug/di/CMakeLists.txt b/src/coreclr/debug/di/CMakeLists.txt index 7ae8b8e9fce48a..53f41b9320f760 100644 --- a/src/coreclr/debug/di/CMakeLists.txt +++ b/src/coreclr/debug/di/CMakeLists.txt @@ -79,24 +79,6 @@ set_target_properties(cordbdi PROPERTIES DBI_COMPONENT TRUE) target_precompile_headers(cordbdi PRIVATE stdafx.h) add_dependencies(cordbdi eventing_headers) -add_executable_clr(coreclr_debug_session_condition_tests - EXCLUDE_FROM_ALL - ../shared/dbgtransportsessionconditiontest.cpp -) - -target_link_libraries(coreclr_debug_session_condition_tests PRIVATE coreclrminipal) - -if(CLR_CMAKE_HOST_WIN32) - target_link_libraries(coreclr_debug_session_condition_tests - PRIVATE - kernel32.lib - ${STATIC_MT_CRT_LIB} - ${STATIC_MT_VCRT_LIB} - ) -elseif(CLR_CMAKE_HOST_UNIX) - target_link_libraries(coreclr_debug_session_condition_tests PRIVATE pthread) -endif() - if(CLR_CMAKE_HOST_UNIX) add_library_clr(cordbdi_universal STATIC ${CORDBDI_SOURCES}) set_target_properties(cordbdi_universal PROPERTIES DBI_COMPONENT TRUE) diff --git a/src/coreclr/debug/shared/dbgtransportsessionconditiontest.cpp b/src/coreclr/debug/shared/dbgtransportsessionconditiontest.cpp deleted file mode 100644 index c252266337c6c3..00000000000000 --- a/src/coreclr/debug/shared/dbgtransportsessionconditiontest.cpp +++ /dev/null @@ -1,374 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#include -#include -#include -#include - -#include - -namespace -{ - enum class TestState - { - Opening, - Open, - Closed, - }; - - struct WaiterState - { - minipal_condition_variable* condition; - minipal_mutex* mutex; - TestState* state; - TestState result; - }; - - struct SignalWaiterState - { - minipal_condition_variable* condition; - minipal_nonrecursive_mutex* mutex; - int* readyCount; - int* wakeTokens; - int* wakeCount; - bool* waitFailed; - }; - - bool Check(bool condition, const char* message) - { - if (!condition) - { - fprintf(stderr, "FAILED: %s\n", message); - } - - return condition; - } - - void SleepMilliseconds(uint32_t milliseconds) - { -#ifdef HOST_WINDOWS - Sleep(milliseconds); -#else - timespec delay = {}; - delay.tv_sec = milliseconds / 1000; - delay.tv_nsec = static_cast(milliseconds % 1000) * 1000000; - while (nanosleep(&delay, &delay) != 0 && errno == EINTR); -#endif // HOST_WINDOWS - } - -#ifdef HOST_WINDOWS - DWORD WINAPI WaiterThread(void* argument) -#else - void* WaiterThread(void* argument) -#endif - { - WaiterState* waiter = static_cast(argument); - minipal_mutex_enter(waiter->mutex); - while (*waiter->state == TestState::Opening) - { - minipal_condition_variable_result result = - minipal_condition_variable_wait( - waiter->condition, - waiter->mutex, - MINIPAL_CONDITION_VARIABLE_INFINITE); - if (result == MINIPAL_CONDITION_VARIABLE_FAILED) - { - break; - } - } - waiter->result = *waiter->state; - minipal_mutex_leave(waiter->mutex); - -#ifdef HOST_WINDOWS - return 0; -#else - return nullptr; -#endif // HOST_WINDOWS - } - -#ifdef HOST_WINDOWS - DWORD WINAPI SignalWaiterThread(void* argument) -#else - void* SignalWaiterThread(void* argument) -#endif - { - SignalWaiterState* waiter = static_cast(argument); - minipal_nonrecursive_mutex_enter(waiter->mutex); - (*waiter->readyCount)++; - while (*waiter->wakeTokens == 0) - { - minipal_condition_variable_result result = - minipal_condition_variable_wait_nonrecursive( - waiter->condition, - waiter->mutex, - MINIPAL_CONDITION_VARIABLE_INFINITE); - if (result == MINIPAL_CONDITION_VARIABLE_FAILED) - { - *waiter->waitFailed = true; - break; - } - } - if (*waiter->wakeTokens != 0) - { - (*waiter->wakeTokens)--; - (*waiter->wakeCount)++; - } - minipal_nonrecursive_mutex_leave(waiter->mutex); - -#ifdef HOST_WINDOWS - return 0; -#else - return nullptr; -#endif // HOST_WINDOWS - } - - bool TestSignalCondition() - { - minipal_nonrecursive_mutex mutex; - if (!Check(minipal_nonrecursive_mutex_init(&mutex), "create non-recursive mutex")) - { - return false; - } - - minipal_condition_variable condition; - if (!Check(minipal_condition_variable_init(&condition), "create signal condition")) - { - minipal_nonrecursive_mutex_destroy(&mutex); - return false; - } - - int readyCount = 0; - int wakeTokens = 0; - int wakeCount = 0; - bool waitFailed = false; - SignalWaiterState waiters[] = - { - { &condition, &mutex, &readyCount, &wakeTokens, &wakeCount, &waitFailed }, - { &condition, &mutex, &readyCount, &wakeTokens, &wakeCount, &waitFailed }, - }; - -#ifdef HOST_WINDOWS - HANDLE threads[] = - { - CreateThread(nullptr, 0, SignalWaiterThread, &waiters[0], 0, nullptr), - CreateThread(nullptr, 0, SignalWaiterThread, &waiters[1], 0, nullptr), - }; - bool threadStarted[] = { threads[0] != nullptr, threads[1] != nullptr }; -#else - pthread_t threads[2]; - bool threadStarted[] = - { - pthread_create(&threads[0], nullptr, SignalWaiterThread, &waiters[0]) == 0, - pthread_create(&threads[1], nullptr, SignalWaiterThread, &waiters[1]) == 0, - }; -#endif // HOST_WINDOWS - - int startedCount = (threadStarted[0] ? 1 : 0) + (threadStarted[1] ? 1 : 0); - for (int retry = 0; retry < 2000; retry++) - { - minipal_nonrecursive_mutex_enter(&mutex); - int currentReadyCount = readyCount; - minipal_nonrecursive_mutex_leave(&mutex); - if (currentReadyCount == startedCount) - { - break; - } - SleepMilliseconds(1); - } - - minipal_nonrecursive_mutex_enter(&mutex); - bool waitersReady = readyCount == 2; - wakeTokens = 1; - bool signaled = minipal_condition_variable_signal(&condition); - minipal_nonrecursive_mutex_leave(&mutex); - - for (int retry = 0; retry < 2000; retry++) - { - minipal_nonrecursive_mutex_enter(&mutex); - int currentWakeCount = wakeCount; - minipal_nonrecursive_mutex_leave(&mutex); - if (currentWakeCount == 1) - { - break; - } - SleepMilliseconds(1); - } - - minipal_nonrecursive_mutex_enter(&mutex); - bool oneWaiterWoke = wakeCount == 1; - wakeTokens += startedCount - wakeCount; - bool broadcast = minipal_condition_variable_broadcast(&condition); - minipal_nonrecursive_mutex_leave(&mutex); - - bool success = - Check(startedCount == 2, "start signal waiters") && - Check(waitersReady, "signal waiters are ready") && - Check(signaled, "signal one waiter") && - Check(oneWaiterWoke, "exactly one waiter satisfies the signaled predicate") && - Check(!waitFailed, "condition wait succeeds") && - Check(broadcast, "broadcast remaining waiter"); - - for (int index = 0; index < 2; index++) - { - if (threadStarted[index]) - { -#ifdef HOST_WINDOWS - success = - Check(WaitForSingleObject(threads[index], 5000) == WAIT_OBJECT_0, "join signal waiter") && - success; - CloseHandle(threads[index]); -#else - success = Check(pthread_join(threads[index], nullptr) == 0, "join signal waiter") && success; -#endif // HOST_WINDOWS - } - } - - minipal_nonrecursive_mutex_enter(&mutex); - success = Check(wakeCount == startedCount, "all signal waiters wake") && success; - minipal_nonrecursive_mutex_leave(&mutex); - - minipal_condition_variable_destroy(&condition); - minipal_nonrecursive_mutex_destroy(&mutex); - return success; - } - - bool TestStateCondition() - { - minipal_mutex mutex; - if (!Check(minipal_mutex_init(&mutex), "create state mutex")) - { - return false; - } - - minipal_condition_variable condition; - if (!Check(minipal_condition_variable_init(&condition), "create state condition")) - { - minipal_mutex_destroy(&mutex); - return false; - } - - TestState state = TestState::Opening; - WaiterState waiters[] = - { - { &condition, &mutex, &state, TestState::Opening }, - { &condition, &mutex, &state, TestState::Opening }, - }; - -#ifdef HOST_WINDOWS - HANDLE threads[] = - { - CreateThread(nullptr, 0, WaiterThread, &waiters[0], 0, nullptr), - CreateThread(nullptr, 0, WaiterThread, &waiters[1], 0, nullptr), - }; - bool threadsStarted = threads[0] != nullptr && threads[1] != nullptr; -#else - pthread_t threads[2]; - bool threadStarted[] = - { - pthread_create(&threads[0], nullptr, WaiterThread, &waiters[0]) == 0, - pthread_create(&threads[1], nullptr, WaiterThread, &waiters[1]) == 0, - }; - bool threadsStarted = threadStarted[0] && threadStarted[1]; -#endif // HOST_WINDOWS - - SleepMilliseconds(50); - minipal_mutex_enter(&mutex); - state = TestState::Open; - bool broadcast = minipal_condition_variable_broadcast(&condition); - minipal_mutex_leave(&mutex); - - bool success = - Check(threadsStarted, "start state condition waiters") && - Check(broadcast, "broadcast open state"); - -#ifdef HOST_WINDOWS - for (HANDLE thread : threads) - { - if (thread != nullptr) - { - success = - Check(WaitForSingleObject(thread, 5000) == WAIT_OBJECT_0, "join state condition waiter") && - success; - CloseHandle(thread); - } - } -#else - for (int index = 0; index < 2; index++) - { - if (threadStarted[index]) - { - success = - Check(pthread_join(threads[index], nullptr) == 0, "join state condition waiter") && - success; - } - } -#endif // HOST_WINDOWS - - success = - Check( - waiters[0].result == TestState::Open && waiters[1].result == TestState::Open, - "all waiters observe open state") && - success; - - minipal_mutex_enter(&mutex); - state = TestState::Opening; - minipal_condition_variable_result timeout = - minipal_condition_variable_wait(&condition, &mutex, 50); - minipal_mutex_leave(&mutex); - - success = Check(timeout == MINIPAL_CONDITION_VARIABLE_TIMED_OUT, "state condition times out") && success; - - WaiterState closedWaiter = { &condition, &mutex, &state, TestState::Opening }; -#ifdef HOST_WINDOWS - HANDLE closedThread = CreateThread(nullptr, 0, WaiterThread, &closedWaiter, 0, nullptr); - bool closedThreadStarted = closedThread != nullptr; -#else - pthread_t closedThread; - bool closedThreadStarted = pthread_create(&closedThread, nullptr, WaiterThread, &closedWaiter) == 0; -#endif // HOST_WINDOWS - - SleepMilliseconds(50); - minipal_mutex_enter(&mutex); - state = TestState::Closed; - broadcast = minipal_condition_variable_broadcast(&condition); - minipal_mutex_leave(&mutex); - - success = - Check(closedThreadStarted, "start closed-state waiter") && - Check(broadcast, "broadcast closed state") && - success; - - if (closedThreadStarted) - { -#ifdef HOST_WINDOWS - success = - Check(WaitForSingleObject(closedThread, 5000) == WAIT_OBJECT_0, "join closed-state waiter") && - success; - CloseHandle(closedThread); -#else - success = - Check(pthread_join(closedThread, nullptr) == 0, "join closed-state waiter") && - success; -#endif // HOST_WINDOWS - } - - success = Check(closedWaiter.result == TestState::Closed, "waiter observes closed state") && success; - - minipal_condition_variable_destroy(&condition); - minipal_mutex_destroy(&mutex); - return success; - } -} - -int main() -{ - if (!TestSignalCondition() || - !TestStateCondition()) - { - return 1; - } - - printf("DbgTransport session state condition tests passed.\n"); - return 0; -} diff --git a/src/coreclr/minipal/CMakeLists.txt b/src/coreclr/minipal/CMakeLists.txt index bc316de3968363..298e1d6fba3819 100644 --- a/src/coreclr/minipal/CMakeLists.txt +++ b/src/coreclr/minipal/CMakeLists.txt @@ -5,7 +5,3 @@ if (CLR_CMAKE_HOST_UNIX) else (CLR_CMAKE_HOST_UNIX) add_subdirectory(Windows) endif (CLR_CMAKE_HOST_UNIX) - -if (NOT CLR_CMAKE_TARGET_ARCH_WASM) - add_subdirectory(tests) -endif() diff --git a/src/coreclr/minipal/Unix/wait.cpp b/src/coreclr/minipal/Unix/wait.cpp index 110356c0cc83b0..caf3b5635e7ee6 100644 --- a/src/coreclr/minipal/Unix/wait.cpp +++ b/src/coreclr/minipal/Unix/wait.cpp @@ -32,15 +32,6 @@ namespace { -#if defined(MINIPAL_WAIT_TESTS) - int32_t s_processWatcherCount; - int32_t s_pauseExitedProcessWatchers; - int32_t s_pausedProcessWatcherCount; - int32_t s_pausedExitedProcessWatcherCount; - int32_t s_pauseProcessWatchers; - int32_t s_processWatcherSignalCount; -#endif // MINIPAL_WAIT_TESTS - enum class WaitableKind { Event, @@ -609,30 +600,13 @@ namespace return; } - if (SignalPipeLocked(waitable)) - { -#if defined(MINIPAL_WAIT_TESTS) - __atomic_add_fetch(&s_processWatcherSignalCount, 1, __ATOMIC_ACQ_REL); -#endif // MINIPAL_WAIT_TESTS - } + SignalPipeLocked(waitable); } void* ProcessWatcher(void* argument) { Waitable* waitable = static_cast(argument); assert(waitable->kind == WaitableKind::ProcessPipe); -#if defined(MINIPAL_WAIT_TESTS) - __atomic_add_fetch(&s_processWatcherCount, 1, __ATOMIC_ACQ_REL); - if (__atomic_load_n(&s_pauseProcessWatchers, __ATOMIC_ACQUIRE) != 0) - { - __atomic_add_fetch(&s_pausedProcessWatcherCount, 1, __ATOMIC_ACQ_REL); - while (__atomic_load_n(&s_pauseProcessWatchers, __ATOMIC_ACQUIRE) != 0) - { - poll(nullptr, 0, 1); - } - __atomic_sub_fetch(&s_pausedProcessWatcherCount, 1, __ATOMIC_ACQ_REL); - } -#endif // MINIPAL_WAIT_TESTS bool exited = false; while (__atomic_load_n(&waitable->publicRefCount, __ATOMIC_ACQUIRE) != 0) @@ -647,26 +621,11 @@ namespace nanosleep(&sleepDuration, nullptr); } -#if defined(MINIPAL_WAIT_TESTS) - if (exited && __atomic_load_n(&s_pauseExitedProcessWatchers, __ATOMIC_ACQUIRE) != 0) - { - __atomic_add_fetch(&s_pausedExitedProcessWatcherCount, 1, __ATOMIC_ACQ_REL); - while (__atomic_load_n(&s_pauseExitedProcessWatchers, __ATOMIC_ACQUIRE) != 0) - { - poll(nullptr, 0, 1); - } - __atomic_sub_fetch(&s_pausedExitedProcessWatcherCount, 1, __ATOMIC_ACQ_REL); - } -#endif // MINIPAL_WAIT_TESTS - if (exited) { SignalProcessWatcherExit(waitable); } -#if defined(MINIPAL_WAIT_TESTS) - __atomic_sub_fetch(&s_processWatcherCount, 1, __ATOMIC_ACQ_REL); -#endif // MINIPAL_WAIT_TESTS ReleaseReference(waitable); return nullptr; } @@ -1084,7 +1043,6 @@ namespace return nullptr; } -#if !defined(MINIPAL_WAIT_FORCE_PROCESS_WATCHER) #if defined(TARGET_LINUX) && defined(SYS_pidfd_open) int processFileDescriptor; do @@ -1131,7 +1089,6 @@ namespace return waitable; } #endif // MINIPAL_WAIT_USES_KQUEUE -#endif // !MINIPAL_WAIT_FORCE_PROCESS_WATCHER return CreateProcessWatcherWaitable(static_cast(processId)); } @@ -1151,43 +1108,6 @@ namespace } } -#if defined(MINIPAL_WAIT_TESTS) -extern "C" void minipal_wait_test_pause_process_watchers(bool pause) -{ - __atomic_store_n(&s_pauseProcessWatchers, pause ? 1 : 0, __ATOMIC_RELEASE); -} - -extern "C" void minipal_wait_test_pause_exited_process_watchers(bool pause) -{ - __atomic_store_n(&s_pauseExitedProcessWatchers, pause ? 1 : 0, __ATOMIC_RELEASE); -} - -extern "C" int32_t minipal_wait_test_get_process_watcher_count() -{ - return __atomic_load_n(&s_processWatcherCount, __ATOMIC_ACQUIRE); -} - -extern "C" int32_t minipal_wait_test_get_paused_process_watcher_count() -{ - return __atomic_load_n(&s_pausedProcessWatcherCount, __ATOMIC_ACQUIRE); -} - -extern "C" int32_t minipal_wait_test_get_paused_exited_process_watcher_count() -{ - return __atomic_load_n(&s_pausedExitedProcessWatcherCount, __ATOMIC_ACQUIRE); -} - -extern "C" int32_t minipal_wait_test_get_process_watcher_signal_count() -{ - return __atomic_load_n(&s_processWatcherSignalCount, __ATOMIC_ACQUIRE); -} - -extern "C" void minipal_wait_test_reset_process_watcher_signal_count() -{ - __atomic_store_n(&s_processWatcherSignalCount, 0, __ATOMIC_RELEASE); -} -#endif // MINIPAL_WAIT_TESTS - minipal_wait_handle::minipal_wait_handle(void* handle) : m_handle(handle) { diff --git a/src/coreclr/minipal/tests/CMakeLists.txt b/src/coreclr/minipal/tests/CMakeLists.txt deleted file mode 100644 index fa3e9b21b4f05a..00000000000000 --- a/src/coreclr/minipal/tests/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -add_executable_clr(coreclrminipal_wait_tests - EXCLUDE_FROM_ALL - wait.cpp -) - -target_link_libraries(coreclrminipal_wait_tests - PRIVATE - coreclrminipal -) - -if(CLR_CMAKE_HOST_UNIX) - target_link_libraries(coreclrminipal_wait_tests PRIVATE pthread) - - add_executable_clr(coreclrminipal_process_watcher_tests - EXCLUDE_FROM_ALL - wait.cpp - ../Unix/wait.cpp - ) - - target_compile_definitions(coreclrminipal_process_watcher_tests - PRIVATE - MINIPAL_WAIT_FORCE_PROCESS_WATCHER=1 - MINIPAL_WAIT_TESTS=1 - ) - - if(HAVE_PIPE2) - target_compile_definitions(coreclrminipal_process_watcher_tests PRIVATE HAVE_PIPE2=1) - else() - target_compile_definitions(coreclrminipal_process_watcher_tests PRIVATE HAVE_PIPE2=0) - endif() - - target_link_libraries(coreclrminipal_process_watcher_tests - PRIVATE - minipal - pthread - ) -endif() diff --git a/src/coreclr/minipal/tests/wait.cpp b/src/coreclr/minipal/tests/wait.cpp deleted file mode 100644 index ae2bfebf2ca89f..00000000000000 --- a/src/coreclr/minipal/tests/wait.cpp +++ /dev/null @@ -1,717 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#include -#include -#include -#include -#include - -#ifdef HOST_WINDOWS -#include -#else -#include -#include -#include -#include -#include -#include -#endif - -#include "minipal-wait.h" - -#if defined(MINIPAL_WAIT_TESTS) -extern "C" void minipal_wait_test_pause_process_watchers(bool pause); -extern "C" void minipal_wait_test_pause_exited_process_watchers(bool pause); -extern "C" int32_t minipal_wait_test_get_process_watcher_count(); -extern "C" int32_t minipal_wait_test_get_paused_process_watcher_count(); -extern "C" int32_t minipal_wait_test_get_paused_exited_process_watcher_count(); -extern "C" int32_t minipal_wait_test_get_process_watcher_signal_count(); -extern "C" void minipal_wait_test_reset_process_watcher_signal_count(); -#endif // MINIPAL_WAIT_TESTS - -namespace -{ - struct WaitThreadState - { - minipal_event handle; - uint32_t timeout; - int32_t result; - - WaitThreadState(const minipal_event& waitHandle, uint32_t waitTimeout) - : handle(waitHandle) - , timeout(waitTimeout) - , result(MINIPAL_WAIT_FAILED) - { - } - }; - - bool Check(bool condition, const char* message) - { - if (!condition) - { - fprintf(stderr, "FAILED: %s\n", message); - } - - return condition; - } - - void SleepMilliseconds(uint32_t milliseconds) - { -#ifdef HOST_WINDOWS - Sleep(milliseconds); -#else - timespec delay = {}; - delay.tv_sec = milliseconds / 1000; - delay.tv_nsec = static_cast(milliseconds % 1000) * 1000000; - while (nanosleep(&delay, &delay) != 0 && errno == EINTR); -#endif - } - - uint64_t GetTickMilliseconds() - { -#ifdef HOST_WINDOWS - return GetTickCount64(); -#else - timespec currentTime; - if (clock_gettime(CLOCK_MONOTONIC, ¤tTime) != 0) - { - return 0; - } - - return static_cast(currentTime.tv_sec) * 1000 + - static_cast(currentTime.tv_nsec) / 1000000; -#endif - } - -#if defined(MINIPAL_WAIT_TESTS) - struct ProcessWaitReleaseState - { - minipal_process_wait* process; - int32_t released; - }; - - bool WaitForValue(int32_t (*getValue)(), int32_t expected, uint32_t timeout) - { - uint64_t start = GetTickMilliseconds(); - do - { - if (getValue() == expected) - { - return true; - } - - SleepMilliseconds(1); - } while (GetTickMilliseconds() - start < timeout); - - return getValue() == expected; - } - - void* ReleaseProcessWait(void* argument) - { - ProcessWaitReleaseState* state = static_cast(argument); - delete state->process; - __atomic_store_n(&state->released, 1, __ATOMIC_RELEASE); - return nullptr; - } -#endif // MINIPAL_WAIT_TESTS - -#ifdef HOST_WINDOWS - DWORD WINAPI WaitThread(void* argument) -#else - void* WaitThread(void* argument) -#endif - { - WaitThreadState* state = static_cast(argument); - state->result = minipal_wait_handle::Wait(state->handle, state->timeout); -#ifdef HOST_WINDOWS - return 0; -#else - return nullptr; -#endif - } - - bool TestAutoResetEvent() - { - minipal_event event(false); - minipal_event initiallySignaledEvent(true); - bool success = - Check(event.IsValid() && initiallySignaledEvent.IsValid(), "create auto-reset events") && - Check( - minipal_wait_handle::Wait(initiallySignaledEvent, 0) == 0, - "initially signaled auto-reset event is acquired") && - Check( - minipal_wait_handle::Wait(initiallySignaledEvent, 0) == MINIPAL_WAIT_TIMEOUT, - "initially signaled auto-reset event is consumed") && - Check( - minipal_wait_handle::Wait(event, 0) == MINIPAL_WAIT_TIMEOUT, - "unsignaled auto-reset event times out") && - Check(event.Set(), "set auto-reset event before reset") && - Check(event.Reset(), "reset auto-reset event") && - Check( - minipal_wait_handle::Wait(event, 0) == MINIPAL_WAIT_TIMEOUT, - "reset auto-reset event times out") && - Check(event.Set(), "set auto-reset event") && - Check(event.Set(), "coalesce repeated auto-reset set") && - Check( - minipal_wait_handle::Wait(event, 0) == 0, - "signaled auto-reset event is acquired") && - Check( - minipal_wait_handle::Wait(event, 0) == MINIPAL_WAIT_TIMEOUT, - "auto-reset event is consumed"); - - return success; - } - - bool TestLowestIndexPriority() - { - minipal_event events[3] = - { - minipal_event(false), - minipal_event(false), - minipal_event(false), - }; - const minipal_wait_handle* waitSet[] = { &events[0], &events[1], &events[2] }; - - bool success = - Check(events[0].IsValid() && events[1].IsValid() && events[2].IsValid(), "create priority events") && - Check(events[2].Set(), "set high-index event") && - Check(events[0].Set(), "set low-index event") && - Check( - minipal_wait_handle::Wait(waitSet, 3, 0) == 0, - "lowest signaled index wins") && - Check( - minipal_wait_handle::Wait(waitSet, 3, 0) == 2, - "higher index remains signaled"); - - return success; - } - - bool TestTimeout() - { - minipal_event event(false); - if (!Check(event.IsValid(), "create timeout event")) - { - return false; - } - - uint64_t start = GetTickMilliseconds(); - int32_t result = minipal_wait_handle::Wait(event, 100); - uint64_t elapsed = GetTickMilliseconds() - start; - - bool success = - Check(result == MINIPAL_WAIT_TIMEOUT, "finite wait times out") && - Check(elapsed >= 75, "finite wait observes requested timeout") && - Check(elapsed < 2000, "finite wait does not overrun"); - - return success; - } - - bool TestAutoResetReleasesSingleWaiter() - { - minipal_event event(false); - WaitThreadState states[2] = - { - { event, 2000 }, - { event, 2000 }, - }; - -#ifdef HOST_WINDOWS - HANDLE threads[2] = - { - CreateThread(nullptr, 0, WaitThread, &states[0], 0, nullptr), - CreateThread(nullptr, 0, WaitThread, &states[1], 0, nullptr), - }; - bool threadsStarted = threads[0] != nullptr && threads[1] != nullptr; -#else - pthread_t threads[2]; - bool threadStarted[2] = - { - pthread_create(&threads[0], nullptr, WaitThread, &states[0]) == 0, - pthread_create(&threads[1], nullptr, WaitThread, &states[1]) == 0, - }; - bool threadsStarted = threadStarted[0] && threadStarted[1]; -#endif - - SleepMilliseconds(50); - bool success = - Check(event.IsValid() && states[0].handle.IsValid() && states[1].handle.IsValid(), - "create shared auto-reset event") && - Check(threadsStarted, "start auto-reset waiters") && - Check(event.Set(), "signal one auto-reset waiter"); - -#ifdef HOST_WINDOWS - for (HANDLE thread : threads) - { - if (thread != nullptr) - { - success = - Check(WaitForSingleObject(thread, 5000) == WAIT_OBJECT_0, "join auto-reset waiter") && - success; - CloseHandle(thread); - } - } -#else - for (int index = 0; index < 2; index++) - { - if (threadStarted[index]) - { - success = Check(pthread_join(threads[index], nullptr) == 0, "join auto-reset waiter") && success; - } - } -#endif - - int acquiredCount = (states[0].result == 0 ? 1 : 0) + (states[1].result == 0 ? 1 : 0); - int timeoutCount = - (states[0].result == MINIPAL_WAIT_TIMEOUT ? 1 : 0) + - (states[1].result == MINIPAL_WAIT_TIMEOUT ? 1 : 0); - success = - Check(acquiredCount == 1, "exactly one waiter acquires auto-reset event") && - Check(timeoutCount == 1, "other auto-reset waiter times out") && - success; - - return success; - } - - bool TestDuplicateLifetime() - { - minipal_event* duplicate; - { - minipal_event event(false); - duplicate = new (std::nothrow) minipal_event(event); - } - - bool success = - Check(duplicate != nullptr && duplicate->IsValid(), "duplicate event") && - Check(duplicate->Set(), "set duplicate after original release") && - Check( - minipal_wait_handle::Wait(*duplicate, 0) == 0, - "duplicate keeps event alive"); - - delete duplicate; - return success; - } - - bool TestInvalidWaitSet() - { - minipal_event event(false); - minipal_process_wait invalidProcess(static_cast(0)); - const minipal_wait_handle* handles[] = { &event, nullptr }; - - bool success = - Check(event.IsValid(), "create invalid-wait event") && - Check(!invalidProcess.IsValid(), "reject invalid process identifier") && - Check( - minipal_wait_handle::Wait(invalidProcess, 0) == MINIPAL_WAIT_FAILED, - "reject invalid single wait handle") && - Check( - minipal_wait_handle::Wait(handles, 2, 0) == MINIPAL_WAIT_FAILED, - "reject null wait-set entry") && - Check( - minipal_wait_handle::Wait(nullptr, 1, 0) == MINIPAL_WAIT_FAILED, - "reject null wait set") && - Check( - minipal_wait_handle::Wait(handles, 0, 0) == MINIPAL_WAIT_FAILED, - "reject empty wait set") && - Check( - minipal_wait_handle::Wait(handles, MINIPAL_MAX_WAIT_OBJECTS + 1, 0) == - MINIPAL_WAIT_FAILED, - "reject oversized wait set"); - - return success; - } - - bool TestConcurrentWait() - { - minipal_event event(false); - WaitThreadState state(event, MINIPAL_WAIT_INFINITE); - -#ifdef HOST_WINDOWS - HANDLE thread = CreateThread(nullptr, 0, WaitThread, &state, 0, nullptr); - bool threadStarted = thread != nullptr; -#else - pthread_t thread; - bool threadStarted = pthread_create(&thread, nullptr, WaitThread, &state) == 0; -#endif - - SleepMilliseconds(50); - bool success = - Check(event.IsValid() && state.handle.IsValid(), "create concurrent event") && - Check(threadStarted, "start waiting thread") && - Check(event.Set(), "signal waiting thread"); - - if (threadStarted) - { -#ifdef HOST_WINDOWS - success = - Check(WaitForSingleObject(thread, 5000) == WAIT_OBJECT_0, "join waiting thread") && - success; - CloseHandle(thread); -#else - success = Check(pthread_join(thread, nullptr) == 0, "join waiting thread") && success; -#endif - } - - success = Check(state.result == 0, "waiting thread acquires event") && success; - return success; - } - -#ifndef HOST_WINDOWS - void InterruptSignalHandler(int) - { - } - - bool TestInterruptedWait() - { - struct sigaction action = {}; - action.sa_handler = InterruptSignalHandler; - sigemptyset(&action.sa_mask); - if (!Check(sigaction(SIGUSR1, &action, nullptr) == 0, "install interrupt handler")) - { - return false; - } - - minipal_event event(false); - WaitThreadState state(event, MINIPAL_WAIT_INFINITE); - pthread_t thread; - bool threadStarted = pthread_create(&thread, nullptr, WaitThread, &state) == 0; - - SleepMilliseconds(50); - bool success = - Check(event.IsValid() && state.handle.IsValid(), "create interrupted event") && - Check(threadStarted, "start interrupted waiting thread"); - - if (threadStarted) - { - success = Check(pthread_kill(thread, SIGUSR1) == 0, "interrupt waiting thread") && success; - SleepMilliseconds(50); - success = Check(event.Set(), "signal interrupted wait") && success; - success = Check(pthread_join(thread, nullptr) == 0, "join interrupted waiting thread") && success; - } - - success = Check(state.result == 0, "interrupted wait retries") && success; - return success; - } -#endif - -#if defined(MINIPAL_WAIT_TESTS) - bool TestAbandonedProcessWatcher() - { - pid_t child = fork(); - if (!Check(child >= 0, "fork abandoned process-watcher child")) - { - return false; - } - - if (child == 0) - { - SleepMilliseconds(30000); - _exit(0); - } - - minipal_wait_test_pause_process_watchers(true); - - minipal_process_wait* process = - new (std::nothrow) minipal_process_wait(static_cast(child)); - - bool watcherPaused = - process != nullptr && - process->IsValid() && - WaitForValue(minipal_wait_test_get_paused_process_watcher_count, 1, 2000); - bool success = - Check(process != nullptr && process->IsValid(), "create abandoned process watcher") && - Check(watcherPaused, "pause process watcher"); - - minipal_process_wait* duplicate = watcherPaused - ? new (std::nothrow) minipal_process_wait(*process) - : nullptr; - success = - Check(duplicate != nullptr && duplicate->IsValid(), "duplicate process watcher") && - success; - - if (duplicate != nullptr) - { - delete process; - process = duplicate; - success = - Check( - minipal_wait_test_get_process_watcher_count() == 1, - "process watcher remains while public reference exists") && - success; - } - - ProcessWaitReleaseState state = { process, 0 }; - pthread_t releaseThread; - bool releaseThreadStarted = - watcherPaused && - pthread_create(&releaseThread, nullptr, ReleaseProcessWait, &state) == 0; - success = Check(releaseThreadStarted, "start process-wait release thread") && success; - - bool releasedWithoutWaiting = false; - if (releaseThreadStarted) - { - uint64_t start = GetTickMilliseconds(); - do - { - releasedWithoutWaiting = __atomic_load_n(&state.released, __ATOMIC_ACQUIRE) != 0; - if (!releasedWithoutWaiting) - { - SleepMilliseconds(1); - } - } while (!releasedWithoutWaiting && GetTickMilliseconds() - start < 2000); - } - - success = - Check(releasedWithoutWaiting, "final public release does not join process watcher") && - success; - - minipal_wait_test_pause_process_watchers(false); - - if (releaseThreadStarted) - { - success = Check(pthread_join(releaseThread, nullptr) == 0, "join process-wait release thread") && success; - } - else - { - delete state.process; - } - - success = - Check( - WaitForValue(minipal_wait_test_get_process_watcher_count, 0, 2000), - "abandoned process watcher exits") && - success; - - int status; - pid_t waitResult = waitpid(child, &status, WNOHANG); - success = Check(waitResult == 0, "abandoned process remains running") && success; - if (waitResult == 0) - { - success = Check(kill(child, SIGKILL) == 0, "terminate abandoned process-watcher child") && success; - } - - do - { - waitResult = waitpid(child, &status, 0); - } while (waitResult < 0 && errno == EINTR); - - success = - Check(waitResult == child || (waitResult < 0 && errno == ECHILD), "collect abandoned child process") && - success; - return success; - } - - bool TestAbandonedExitedProcessWatcher() - { - pid_t child = fork(); - if (!Check(child >= 0, "fork exited process-watcher child")) - { - return false; - } - - if (child == 0) - { - SleepMilliseconds(100); - _exit(0); - } - - minipal_wait_test_reset_process_watcher_signal_count(); - minipal_wait_test_pause_exited_process_watchers(true); - - ProcessWaitReleaseState state = - { - new (std::nothrow) minipal_process_wait(static_cast(child)), - 0, - }; - - bool watcherPaused = - state.process != nullptr && - state.process->IsValid() && - WaitForValue(minipal_wait_test_get_paused_exited_process_watcher_count, 1, 2000); - bool success = - Check(state.process != nullptr && state.process->IsValid(), "create exited process watcher") && - Check(watcherPaused, "pause exited process watcher before signaling"); - - pthread_t releaseThread; - bool releaseThreadStarted = - watcherPaused && - pthread_create(&releaseThread, nullptr, ReleaseProcessWait, &state) == 0; - success = Check(releaseThreadStarted, "start exited process-wait release thread") && success; - - bool releasedWithoutWaiting = false; - if (releaseThreadStarted) - { - uint64_t start = GetTickMilliseconds(); - do - { - releasedWithoutWaiting = __atomic_load_n(&state.released, __ATOMIC_ACQUIRE) != 0; - if (!releasedWithoutWaiting) - { - SleepMilliseconds(1); - } - } while (!releasedWithoutWaiting && GetTickMilliseconds() - start < 2000); - } - - success = - Check(releasedWithoutWaiting, "final public release does not join exited process watcher") && - success; - - minipal_wait_test_pause_exited_process_watchers(false); - - if (releaseThreadStarted) - { - success = Check(pthread_join(releaseThread, nullptr) == 0, "join exited process-wait release thread") && success; - } - else - { - delete state.process; - } - - success = - Check( - WaitForValue(minipal_wait_test_get_process_watcher_count, 0, 2000), - "abandoned exited process watcher exits") && - Check( - minipal_wait_test_get_process_watcher_signal_count() == 0, - "abandoned exited process watcher does not signal") && - success; - - int status; - pid_t waitResult; - do - { - waitResult = waitpid(child, &status, 0); - } while (waitResult < 0 && errno == EINTR); - - success = - Check(waitResult == child || (waitResult < 0 && errno == ECHILD), "collect exited child process") && - success; - return success; - } -#endif // MINIPAL_WAIT_TESTS - - bool TestProcessExit(const char* executablePath) - { -#ifdef HOST_WINDOWS - char commandLine[MAX_PATH + 32]; - snprintf(commandLine, sizeof(commandLine), "\"%s\" --child", executablePath); - - STARTUPINFOA startupInfo = {}; - startupInfo.cb = sizeof(startupInfo); - PROCESS_INFORMATION processInfo = {}; - if (!Check( - CreateProcessA( - executablePath, - commandLine, - nullptr, - nullptr, - FALSE, - 0, - nullptr, - nullptr, - &startupInfo, - &processInfo) != FALSE, - "create child process")) - { - return false; - } - - minipal_process_wait process(processInfo.dwProcessId); - minipal_event event(false); - const minipal_wait_handle* handles[] = { &event, &process }; - int32_t result = !process.IsValid() || !event.IsValid() - ? MINIPAL_WAIT_FAILED - : minipal_wait_handle::Wait(handles, 2, 5000); - bool success = - Check(process.IsValid() && event.IsValid(), "create process waitables") && - Check(result == 1, "observe process exit in wait set") && - Check(event.Set(), "set event after process exit") && - Check( - minipal_wait_handle::Wait(handles, 2, 0) == 0, - "event has priority over exited process") && - Check( - minipal_wait_handle::Wait(process, 0) == 0, - "process exit remains signaled"); - - CloseHandle(processInfo.hThread); - CloseHandle(processInfo.hProcess); - return success; -#else - (void)executablePath; - pid_t child = fork(); - if (!Check(child >= 0, "fork child process")) - { - return false; - } - - if (child == 0) - { - SleepMilliseconds(100); - _exit(0); - } - - minipal_process_wait process(static_cast(child)); - minipal_event event(false); - const minipal_wait_handle* handles[] = { &event, &process }; - int32_t result = !process.IsValid() || !event.IsValid() - ? MINIPAL_WAIT_FAILED - : minipal_wait_handle::Wait(handles, 2, 5000); - bool success = - Check(process.IsValid() && event.IsValid(), "create process waitables") && - Check(result == 1, "observe process exit in wait set") && - Check(event.Set(), "set event after process exit") && - Check( - minipal_wait_handle::Wait(handles, 2, 0) == 0, - "event has priority over exited process") && - Check( - minipal_wait_handle::Wait(process, 0) == 0, - "process exit remains signaled"); - - int status; - pid_t waitResult; - do - { - waitResult = waitpid(child, &status, 0); - } while (waitResult < 0 && errno == EINTR); - success = - Check(waitResult == child || (waitResult < 0 && errno == ECHILD), "collect child process") && - success; - return success; -#endif - } -} - -int main(int argc, char** argv) -{ - if (argc == 2 && strcmp(argv[1], "--child") == 0) - { - SleepMilliseconds(100); - return 0; - } - - bool success = - TestAutoResetEvent() && - TestLowestIndexPriority() && - TestTimeout() && - TestDuplicateLifetime() && - TestInvalidWaitSet() && - TestConcurrentWait() && - TestAutoResetReleasesSingleWaiter() && -#ifndef HOST_WINDOWS - TestInterruptedWait() && -#endif - TestProcessExit(argv[0]); - -#if defined(MINIPAL_WAIT_TESTS) - success = TestAbandonedProcessWatcher() && success; - success = TestAbandonedExitedProcessWatcher() && success; -#endif // MINIPAL_WAIT_TESTS - - if (success) - { - printf("All minipal wait tests passed.\n"); - return 0; - } - - return 1; -} From 3ca601b023ea62c47728f4937592b1bae1c75b63 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 18 Aug 2026 16:02:17 -0700 Subject: [PATCH 09/12] Restore debugger process exit polling Use a sticky minipal latch for Unix process exit and restore the debugger-owned poller thread. Remove pidfd and kqueue waits, and restrict the renamed minipal_native_handle wrapper to Windows process and thread handles. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/coreclr/debug/di/dbgtransportmanager.cpp | 87 +- src/coreclr/debug/di/dbgtransportmanager.h | 21 +- src/coreclr/debug/di/dbgtransportpipeline.cpp | 35 +- src/coreclr/debug/di/nativepipeline.h | 8 +- src/coreclr/debug/di/process.cpp | 8 +- src/coreclr/debug/di/remoteeventchannel.cpp | 2 +- src/coreclr/debug/di/rspriv.h | 8 +- src/coreclr/debug/di/shimremotedatatarget.cpp | 2 +- src/coreclr/debug/di/windowspipeline.cpp | 16 +- src/coreclr/debug/ee/debugger.cpp | 2 +- src/coreclr/debug/ee/rcthread.cpp | 20 +- src/coreclr/debug/inc/dbgtransportsession.h | 4 +- .../debug/shared/dbgtransportsession.cpp | 4 +- src/coreclr/minipal/Unix/wait.cpp | 776 ++---------------- src/coreclr/minipal/Windows/wait.cpp | 11 +- src/coreclr/minipal/minipal-wait.h | 30 +- 16 files changed, 237 insertions(+), 797 deletions(-) diff --git a/src/coreclr/debug/di/dbgtransportmanager.cpp b/src/coreclr/debug/di/dbgtransportmanager.cpp index e24a267af05355..110f7991c6c89c 100644 --- a/src/coreclr/debug/di/dbgtransportmanager.cpp +++ b/src/coreclr/debug/di/dbgtransportmanager.cpp @@ -10,10 +10,61 @@ #ifdef HOST_UNIX #include #include +#include +#include +#include #endif // HOST_UNIX DbgTransportTarget g_DbgTransportTarget{}; +#ifdef HOST_UNIX +// Polls the target PID for exit. waitpid is identity-stable for child processes; kill is a +// best-effort fallback for non-children and can race PID reuse. +/* static */ +void *DbgTransportTarget::ProcessExitPollerThread(void *arg) +{ + ProcessEntry *entry = static_cast(arg); + + while (!entry->m_fStopPoller) + { + bool exited = false; + + int status; + pid_t result; + do + { + result = waitpid(entry->m_dwPID, &status, WNOHANG); + } while (result < 0 && errno == EINTR); + + if (result == static_cast(entry->m_dwPID)) + { + exited = WIFEXITED(status) || WIFSIGNALED(status); + } + else if (result < 0 && errno == ECHILD) + { + int killResult; + do + { + killResult = kill(entry->m_dwPID, 0); + } while (killResult < 0 && errno == EINTR); + + exited = killResult < 0 && errno == ESRCH; + } + + if (exited) + { + entry->m_hProcessExited->Set(); + break; + } + + timespec sleepDuration = { 0, 250 * 1000 * 1000 }; + nanosleep(&sleepDuration, nullptr); + } + + return nullptr; +} +#endif // HOST_UNIX + DbgTransportTarget::DbgTransportTarget() : m_pProcessList{} , m_sLock{} @@ -53,7 +104,7 @@ void DbgTransportTarget::Shutdown() // on for process termination. HRESULT DbgTransportTarget::GetTransportForProcess(const ProcessDescriptor *pProcessDescriptor, DbgTransportSession **ppTransport, - minipal_process_wait **ppProcessHandle) + minipal_wait_handle **ppProcessHandle) { RSLockHolder lock(&m_sLock); HRESULT hr = S_OK; @@ -75,9 +126,9 @@ HRESULT DbgTransportTarget::GetTransportForProcess(const ProcessDescriptor *pPr } - // Probe the process to make sure it exists, then create a waitable handle that becomes - // signaled on process exit. On Windows the process handle itself remains available for - // native process operations. + // Probe the process to make sure it exists, then create a waitable that becomes signaled + // on process exit. Windows uses the process handle itself; Unix uses a latch set by + // a poller thread. #ifdef HOST_UNIX if (kill(dwPID, 0) != 0) { @@ -85,7 +136,7 @@ HRESULT DbgTransportTarget::GetTransportForProcess(const ProcessDescriptor *pPr return (errno == ESRCH) ? E_INVALIDARG : E_FAIL; } - minipal_process_wait *pProcessExited = new (nothrow) minipal_process_wait(dwPID); + minipal_latch *pProcessExited = new (nothrow) minipal_latch(); if ((pProcessExited == NULL) || !pProcessExited->IsValid()) { delete pProcessExited; @@ -100,7 +151,7 @@ HRESULT DbgTransportTarget::GetTransportForProcess(const ProcessDescriptor *pPr return HRESULT_FROM_GetLastError(); } - minipal_process_wait *pProcessExited = new (nothrow) minipal_process_wait(hProcess); + minipal_native_handle *pProcessExited = new (nothrow) minipal_native_handle(hProcess); bool allocationFailed = pProcessExited == nullptr; DWORD error = GetLastError(); CloseHandle(hProcess); @@ -114,13 +165,24 @@ HRESULT DbgTransportTarget::GetTransportForProcess(const ProcessDescriptor *pPr newEntry->m_dwPID = dwPID; newEntry->m_hProcessExited = pProcessExited; +#ifdef HOST_UNIX + newEntry->m_fStopPoller = false; + newEntry->m_fPollerStarted = false; + + if (pthread_create(&newEntry->m_pollerThread, nullptr, &ProcessExitPollerThread, newEntry.GetValue()) != 0) + { + transport->Shutdown(); + return E_FAIL; + } + newEntry->m_fPollerStarted = true; +#endif // HOST_UNIX // Initialize it (this immediately starts the remote connection process). hr = transport->Init(*pProcessDescriptor, *pProcessExited); if (FAILED(hr)) { transport->Shutdown(); - // ProcessEntry destructor releases the process waitable. + // ProcessEntry destructor stops the poller and releases the waitable. return hr; } @@ -141,7 +203,7 @@ HRESULT DbgTransportTarget::GetTransportForProcess(const ProcessDescriptor *pPr _ASSERTE(entry->m_hProcessExited->IsValid()); *ppTransport = entry->m_transport; - *ppProcessHandle = new (nothrow) minipal_process_wait(*entry->m_hProcessExited); + *ppProcessHandle = new (nothrow) minipal_wait_handle(*entry->m_hProcessExited); if ((*ppProcessHandle == nullptr) || !(*ppProcessHandle)->IsValid()) { delete *ppProcessHandle; @@ -212,6 +274,15 @@ void DbgTransportTarget::KillProcess(DWORD dwPID) DbgTransportTarget::ProcessEntry::~ProcessEntry() { +#ifdef HOST_UNIX + if (m_fPollerStarted) + { + m_fStopPoller = true; + pthread_join(m_pollerThread, nullptr); + m_fPollerStarted = false; + } +#endif // HOST_UNIX + if (m_hProcessExited != NULL) { delete m_hProcessExited; diff --git a/src/coreclr/debug/di/dbgtransportmanager.h b/src/coreclr/debug/di/dbgtransportmanager.h index 3c2b38f050fbe1..0229d715deafe5 100644 --- a/src/coreclr/debug/di/dbgtransportmanager.h +++ b/src/coreclr/debug/di/dbgtransportmanager.h @@ -7,6 +7,10 @@ #ifdef FEATURE_DBGIPC_TRANSPORT_DI +#ifdef HOST_UNIX +#include +#endif // HOST_UNIX + #include "minipal-wait.h" // TODO: Ideally we'd like to remove this class and don't do any process related book keeping in DBI. @@ -40,7 +44,7 @@ class DbgTransportTarget HRESULT GetTransportForProcess( const ProcessDescriptor *pProcessDescriptor, DbgTransportSession **ppTransport, - minipal_process_wait **ppProcessHandle); + minipal_wait_handle **ppProcessHandle); // Give back a previously acquired transport (if nobody else is using the transport it will close down the // connection at this point). @@ -57,13 +61,26 @@ class DbgTransportTarget { ProcessEntry *m_pNext; // Next entry in the list DWORD m_dwPID; // Process ID for this entry - minipal_process_wait *m_hProcessExited; // Waitable handle that becomes signaled when the process exits +#ifdef HOST_UNIX + minipal_latch *m_hProcessExited; // Latch set when the process exits +#else + minipal_native_handle *m_hProcessExited; // Native process handle +#endif // HOST_UNIX DbgTransportSession *m_transport; // Debugger's connection to the process DWORD m_cProcessRef; // Ref count +#ifdef HOST_UNIX + pthread_t m_pollerThread; // Thread that polls m_dwPID for exit + bool m_fPollerStarted; // True once m_pollerThread has been created + Volatile m_fStopPoller; // Set to true to ask the poller thread to exit +#endif // HOST_UNIX ~ProcessEntry(); }; +#ifdef HOST_UNIX + static void *ProcessExitPollerThread(void *arg); +#endif // HOST_UNIX + ProcessEntry *m_pProcessList; // Head of list of currently alive processes (unsorted) RSLock m_sLock; // Lock protecting read and write access to the target list diff --git a/src/coreclr/debug/di/dbgtransportpipeline.cpp b/src/coreclr/debug/di/dbgtransportpipeline.cpp index bdc4907b718924..53bac3f62d15c3 100644 --- a/src/coreclr/debug/di/dbgtransportpipeline.cpp +++ b/src/coreclr/debug/di/dbgtransportpipeline.cpp @@ -57,11 +57,12 @@ class DbgTransportPipeline : public: DbgTransportPipeline() { - m_fRunning = FALSE; - m_hProcess = NULL; - m_pIPCEvent = reinterpret_cast(m_rgbIPCEventBuffer); - m_pProxy = NULL; - m_pTransport = NULL; + m_fRunning = FALSE; + m_fProcessExitEventPending = FALSE; + m_hProcess = NULL; + m_pIPCEvent = reinterpret_cast(m_rgbIPCEventBuffer); + m_pProxy = NULL; + m_pTransport = NULL; _ASSERTE(!IsTransportRunning()); } @@ -91,7 +92,7 @@ class DbgTransportPipeline : ); // Return a handle which will be signaled when the debuggee process terminates. - virtual minipal_process_wait *GetProcessHandle(); + virtual minipal_wait_handle *GetProcessHandle(); // Terminate the debuggee process. virtual BOOL TerminateProcess(UINT32 exitCode); @@ -113,6 +114,8 @@ class DbgTransportPipeline : // clean up all resources void Dispose() { + m_fProcessExitEventPending = FALSE; + if (m_hProcess != NULL) { delete m_hProcess; @@ -132,10 +135,11 @@ class DbgTransportPipeline : } BOOL m_fRunning; + BOOL m_fProcessExitEventPending; DWORD m_dwProcessId; // This waitable is only valid for waiting on process termination. - minipal_process_wait *m_hProcess; + minipal_wait_handle *m_hProcess; DbgTransportTarget * m_pProxy; DbgTransportSession * m_pTransport; @@ -200,6 +204,7 @@ HRESULT DbgTransportPipeline::DebugActiveProcess(MachineInfo machineInfo, const if (SUCCEEDED(hr)) { m_dwProcessId = processDescriptor.m_Pid; + m_fProcessExitEventPending = FALSE; m_fRunning = TRUE; } else @@ -266,9 +271,8 @@ BOOL DbgTransportPipeline::WaitForDebugEvent(DEBUG_EVENT * pEvent, DWORD dwTimeo pEvent->dwThreadId = 0; // On Windows this is the first thread created in the process. pEvent->u.ExitProcess.dwExitCode = 0; // This is not passed back to us by the transport. - // Once the process termination event is signaled, we cannot send or receive any events. - // So we mark the transport as not running anymore. - m_fRunning = FALSE; + // The shim will continue this synthesized event before asking for another one. + m_fProcessExitEventPending = TRUE; return TRUE; } else @@ -285,6 +289,13 @@ BOOL DbgTransportPipeline::ContinueDebugEvent( DWORD dwContinueStatus ) { + if (m_fProcessExitEventPending) + { + m_fProcessExitEventPending = FALSE; + m_fRunning = FALSE; + return TRUE; + } + if (!IsTransportRunning()) { return FALSE; @@ -295,7 +306,7 @@ BOOL DbgTransportPipeline::ContinueDebugEvent( } // Return a handle which will be signaled when the debuggee process terminates. -minipal_process_wait *DbgTransportPipeline::GetProcessHandle() +minipal_wait_handle *DbgTransportPipeline::GetProcessHandle() { // The handle returned here is only valid for waiting on process termination. // See code:INativeEventPipeline::GetProcessHandle. @@ -304,7 +315,7 @@ minipal_process_wait *DbgTransportPipeline::GetProcessHandle() return nullptr; } - minipal_process_wait *processHandle = new (nothrow) minipal_process_wait(*m_hProcess); + minipal_wait_handle *processHandle = new (nothrow) minipal_wait_handle(*m_hProcess); if ((processHandle != nullptr) && !processHandle->IsValid()) { delete processHandle; diff --git a/src/coreclr/debug/di/nativepipeline.h b/src/coreclr/debug/di/nativepipeline.h index 8c5709bf51f19f..6ee85875b21119 100644 --- a/src/coreclr/debug/di/nativepipeline.h +++ b/src/coreclr/debug/di/nativepipeline.h @@ -109,12 +109,12 @@ class INativeEventPipeline // handle for the debuggee process (see below) // // Notes: - // Handles are a Windows-specific concept. On Unix, the returned value is an internal opaque minipal - // waitable that is only valid for debugger-internal process-termination waits. It must not be exposed - // through ICorDebugProcess::GetHandle. + // Handles are a Windows-specific concept. On Unix, the returned value is a minipal latch that is only + // valid for debugger-internal process-termination waits. It must not be exposed through + // ICorDebugProcess::GetHandle. // - virtual minipal_process_wait *GetProcessHandle() = 0; + virtual minipal_wait_handle *GetProcessHandle() = 0; // // Terminate the debuggee process. diff --git a/src/coreclr/debug/di/process.cpp b/src/coreclr/debug/di/process.cpp index a80fecf3743e7e..23deed415a36d1 100644 --- a/src/coreclr/debug/di/process.cpp +++ b/src/coreclr/debug/di/process.cpp @@ -9339,7 +9339,7 @@ HRESULT CordbRCEventThread::SendIPCEvent(CordbProcess* process, } else { - minipal_process_wait *pLSProcess = process->UnsafeGetProcessWaitHandle(); + minipal_wait_handle *pLSProcess = process->UnsafeGetProcessWaitHandle(); // We take locks to ensure that the CordbProcess object is still alive, // even if the OS process exited. @@ -9391,8 +9391,8 @@ HRESULT CordbRCEventThread::SendIPCEvent(CordbProcess* process, // follow up with an exit. // This includes when we've dispatch Native events, and it includes the AsyncBreak sent to get us from a // win32 frozen state to a synchronized state). - minipal_process_wait *pHelperThreadWait = nullptr; #ifdef HOST_WINDOWS + minipal_native_handle *pHelperThreadWait = nullptr; HANDLE hHelperThread = NULL; if (process->IsStopped()) { @@ -9401,13 +9401,15 @@ HRESULT CordbRCEventThread::SendIPCEvent(CordbProcess* process, if (hHelperThread != NULL) { - pHelperThreadWait = new (nothrow) minipal_process_wait(hHelperThread); + pHelperThreadWait = new (nothrow) minipal_native_handle(hHelperThread); if ((pHelperThreadWait == nullptr) || !pHelperThreadWait->IsValid()) { delete pHelperThreadWait; return E_OUTOFMEMORY; } } +#else + minipal_wait_handle *pHelperThreadWait = nullptr; #endif // In a tie, wait-any gives priority to the handle earlier in the array. diff --git a/src/coreclr/debug/di/remoteeventchannel.cpp b/src/coreclr/debug/di/remoteeventchannel.cpp index a72ccb946fd997..dbdfa3f5652878 100644 --- a/src/coreclr/debug/di/remoteeventchannel.cpp +++ b/src/coreclr/debug/di/remoteeventchannel.cpp @@ -95,7 +95,7 @@ HRESULT NewEventChannelForThisPlatform(CORDB_ADDRESS pLeftSideDCB, { // @dbgtodo Mac - Consider moving all of the transport logic to one place. // Perhaps add a new function on DbgTransportManager. - minipal_process_wait *processWaitHandle = nullptr; + minipal_wait_handle *processWaitHandle = nullptr; HRESULT hr = E_FAIL; RemoteEventChannel * pEventChannel = NULL; diff --git a/src/coreclr/debug/di/rspriv.h b/src/coreclr/debug/di/rspriv.h index 7f33deb190095e..7f5a2ee4f7a3f5 100644 --- a/src/coreclr/debug/di/rspriv.h +++ b/src/coreclr/debug/di/rspriv.h @@ -3693,9 +3693,9 @@ class CordbProcess : RSSmartPtr m_cordb; private: - // Process-exit waitable. On Windows this is an OS process handle. On Unix it is an opaque - // minipal object that is valid only with the minipal wait APIs and must not be exposed to clients. - minipal_process_wait *m_handle; + // Process-exit waitable. On Windows this wraps an OS process handle. On Unix it is a minipal latch + // that is valid only with the minipal wait APIs and must not be exposed to clients. + minipal_wait_handle *m_handle; // Process descriptor - holds PID and App group ID for Mac debugging ProcessDescriptor m_processDescriptor; @@ -3711,7 +3711,7 @@ class CordbProcess : #endif } - minipal_process_wait *UnsafeGetProcessWaitHandle() + minipal_wait_handle *UnsafeGetProcessWaitHandle() { return m_handle; } diff --git a/src/coreclr/debug/di/shimremotedatatarget.cpp b/src/coreclr/debug/di/shimremotedatatarget.cpp index 126ac61731dc41..8fcab27f05686c 100644 --- a/src/coreclr/debug/di/shimremotedatatarget.cpp +++ b/src/coreclr/debug/di/shimremotedatatarget.cpp @@ -190,7 +190,7 @@ HRESULT BuildPlatformSpecificDataTarget(MachineInfo machineInfo, const ProcessDescriptor * pProcessDescriptor, ShimDataTarget ** ppDataTarget) { - minipal_process_wait *processWaitHandle = nullptr; + minipal_wait_handle *processWaitHandle = nullptr; HRESULT hr = E_FAIL; ShimRemoteDataTarget * pRemoteDataTarget = NULL; diff --git a/src/coreclr/debug/di/windowspipeline.cpp b/src/coreclr/debug/di/windowspipeline.cpp index 39c7ff2bf45816..cab5e46039aa43 100644 --- a/src/coreclr/debug/di/windowspipeline.cpp +++ b/src/coreclr/debug/di/windowspipeline.cpp @@ -71,7 +71,7 @@ class WindowsNativePipeline : ); // Return a handle for the debuggee process. - virtual minipal_process_wait *GetProcessHandle(); + virtual minipal_wait_handle *GetProcessHandle(); // Terminate the debuggee process. virtual BOOL TerminateProcess(UINT32 exitCode); @@ -156,7 +156,7 @@ BOOL WindowsNativePipeline::ContinueDebugEvent( } // Return a handle for the debuggee process. -minipal_process_wait *WindowsNativePipeline::GetProcessHandle() +minipal_wait_handle *WindowsNativePipeline::GetProcessHandle() { _ASSERTE(m_dwProcessId != 0); @@ -174,10 +174,18 @@ minipal_process_wait *WindowsNativePipeline::GetProcessHandle() return nullptr; } - minipal_process_wait *waitHandle = new (nothrow) minipal_process_wait(processHandle); + minipal_native_handle nativeHandle(processHandle); DWORD error = GetLastError(); CloseHandle(processHandle); + if (!nativeHandle.IsValid()) + { + SetLastError(error); + return nullptr; + } + + minipal_wait_handle *waitHandle = new (nothrow) minipal_wait_handle(nativeHandle); + DWORD duplicateError = GetLastError(); if (waitHandle == nullptr) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); @@ -186,7 +194,7 @@ minipal_process_wait *WindowsNativePipeline::GetProcessHandle() { delete waitHandle; waitHandle = nullptr; - SetLastError(error); + SetLastError(duplicateError); } return waitHandle; diff --git a/src/coreclr/debug/ee/debugger.cpp b/src/coreclr/debug/ee/debugger.cpp index 229f3b27a2ed39..b4d1a943a4655c 100644 --- a/src/coreclr/debug/ee/debugger.cpp +++ b/src/coreclr/debug/ee/debugger.cpp @@ -6825,7 +6825,7 @@ HRESULT Debugger::LaunchJitDebuggerAndNativeAttach(Thread * pThread, EXCEPTION_P LOG((LF_CORDB, LL_INFO10000, "D::LJDANA: waiting on m_exUnmanagedAttachEvent and debugger's process handle\n")); minipal_event unmanagedAttachEvent(GetUnmanagedAttachEvent()); - minipal_process_wait debuggerProcess(processInfo.hProcess); + minipal_native_handle debuggerProcess(processInfo.hProcess); const minipal_wait_handle *waitSet[] = { &unmanagedAttachEvent, &debuggerProcess }; // Let the helper thread do the attach logic for us and wait for the diff --git a/src/coreclr/debug/ee/rcthread.cpp b/src/coreclr/debug/ee/rcthread.cpp index 00934353fc48c0..3384a6fa1b2d63 100644 --- a/src/coreclr/debug/ee/rcthread.cpp +++ b/src/coreclr/debug/ee/rcthread.cpp @@ -863,7 +863,9 @@ void DebuggerRCThread::MainLoop() const minipal_wait_handle *waitSet[DRCT_COUNT_FINAL]; #if !defined(FEATURE_DBGIPC_TRANSPORT_VM) minipal_event rightSideEventAvailable(m_pDCB->m_rightSideEventAvailable.ImportToLocalProcess()); - minipal_process_wait *debuggerProcess = nullptr; +#ifdef HOST_WINDOWS + minipal_native_handle *debuggerProcess = nullptr; +#endif // HOST_WINDOWS #endif #ifdef _DEBUG @@ -891,13 +893,13 @@ void DebuggerRCThread::MainLoop() { LOG((LF_CORDB, LL_INFO1000, "DRCT::ML: waiting for event.\n")); -#if !defined(FEATURE_DBGIPC_TRANSPORT_VM) +#if !defined(FEATURE_DBGIPC_TRANSPORT_VM) && defined(HOST_WINDOWS) // If there is a debugger attached, wait on its handle, too... if ((cWaitCount == DRCT_COUNT_INITIAL) && m_pDCB->m_rightSideProcessHandle.ImportToLocalProcess() != NULL) { _ASSERTE((cWaitCount + 1) == DRCT_COUNT_FINAL); - debuggerProcess = new (nothrow) minipal_process_wait( + debuggerProcess = new (nothrow) minipal_native_handle( m_pDCB->m_rightSideProcessHandle.ImportToLocalProcess()); if ((debuggerProcess == nullptr) || !debuggerProcess->IsValid()) { @@ -907,14 +909,14 @@ void DebuggerRCThread::MainLoop() waitSet[DRCT_DEBUGGER_EVENT] = debuggerProcess; cWaitCount = DRCT_COUNT_FINAL; } -#endif // !FEATURE_DBGIPC_TRANSPORT_VM +#endif // !defined(FEATURE_DBGIPC_TRANSPORT_VM) && defined(HOST_WINDOWS) if (m_fDetachRightSide) { m_fDetachRightSide = false; -#if !defined(FEATURE_DBGIPC_TRANSPORT_VM) +#if !defined(FEATURE_DBGIPC_TRANSPORT_VM) && defined(HOST_WINDOWS) _ASSERTE(cWaitCount == DRCT_COUNT_FINAL); _ASSERTE((cWaitCount - 1) == DRCT_COUNT_INITIAL); @@ -922,7 +924,7 @@ void DebuggerRCThread::MainLoop() debuggerProcess = nullptr; waitSet[DRCT_DEBUGGER_EVENT] = nullptr; cWaitCount = DRCT_COUNT_INITIAL; -#endif // !FEATURE_DBGIPC_TRANSPORT_VM +#endif // !defined(FEATURE_DBGIPC_TRANSPORT_VM) && defined(HOST_WINDOWS) } // Wait for an event from the Right Side. @@ -1082,9 +1084,9 @@ void DebuggerRCThread::MainLoop() } } -#if !defined(FEATURE_DBGIPC_TRANSPORT_VM) +#if !defined(FEATURE_DBGIPC_TRANSPORT_VM) && defined(HOST_WINDOWS) delete debuggerProcess; -#endif +#endif // !defined(FEATURE_DBGIPC_TRANSPORT_VM) && defined(HOST_WINDOWS) STRESS_LOG0(LF_CORDB, LL_INFO1000, "DRCT::ML:: Exiting.\n"); } @@ -1732,7 +1734,7 @@ void DebuggerRCThread::DoFavor(FAVORCALLBACK fp, void * pData) #endif }; #ifdef HOST_WINDOWS - minipal_process_wait helperThread(m_thread); + minipal_native_handle helperThread(m_thread); waitSet[1] = &helperThread; #endif diff --git a/src/coreclr/debug/inc/dbgtransportsession.h b/src/coreclr/debug/inc/dbgtransportsession.h index 03764e750afc76..a34123777a165f 100644 --- a/src/coreclr/debug/inc/dbgtransportsession.h +++ b/src/coreclr/debug/inc/dbgtransportsession.h @@ -367,7 +367,7 @@ class DbgTransportSession // requires the addresses of a couple of runtime data structures to service certain debugger requests that // may be delivered once the session is established. #ifdef RIGHT_SIDE_COMPILE - HRESULT Init(const ProcessDescriptor& pd, const minipal_process_wait& processExited); + HRESULT Init(const ProcessDescriptor& pd, const minipal_wait_handle& processExited); #else HRESULT Init(DebuggerIPCControlBlock * pDCB); #endif // RIGHT_SIDE_COMPILE @@ -665,7 +665,7 @@ class DbgTransportSession // On the RS the transport thread needs to know the IP address and port number to Connect() to. ProcessDescriptor m_pd; // Descriptor of a process we're talking to. - minipal_process_wait *m_hProcessExited; // wait which will be signaled when the debuggee is terminated + minipal_wait_handle *m_hProcessExited; // wait which will be signaled when the debuggee is terminated bool m_fDebuggerAttached; #endif diff --git a/src/coreclr/debug/shared/dbgtransportsession.cpp b/src/coreclr/debug/shared/dbgtransportsession.cpp index a9c7ba859df802..c6b2871fdfec2c 100644 --- a/src/coreclr/debug/shared/dbgtransportsession.cpp +++ b/src/coreclr/debug/shared/dbgtransportsession.cpp @@ -85,7 +85,7 @@ DbgTransportSession::~DbgTransportSession() // addresses of a couple of runtime data structures to service certain debugger requests that may be delivered // once the session is established. #ifdef RIGHT_SIDE_COMPILE -HRESULT DbgTransportSession::Init(const ProcessDescriptor& pd, const minipal_process_wait& processExited) +HRESULT DbgTransportSession::Init(const ProcessDescriptor& pd, const minipal_wait_handle& processExited) #else // RIGHT_SIDE_COMPILE HRESULT DbgTransportSession::Init(DebuggerIPCControlBlock *pDCB) #endif // RIGHT_SIDE_COMPILE @@ -112,7 +112,7 @@ HRESULT DbgTransportSession::Init(DebuggerIPCControlBlock *pDCB) return E_FAIL; m_pd = pd; - m_hProcessExited = new (nothrow) minipal_process_wait(processExited); + m_hProcessExited = new (nothrow) minipal_wait_handle(processExited); if ((m_hProcessExited == nullptr) || !m_hProcessExited->IsValid()) { delete m_hProcessExited; diff --git a/src/coreclr/minipal/Unix/wait.cpp b/src/coreclr/minipal/Unix/wait.cpp index caf3b5635e7ee6..f12577a6d0016f 100644 --- a/src/coreclr/minipal/Unix/wait.cpp +++ b/src/coreclr/minipal/Unix/wait.cpp @@ -7,39 +7,16 @@ #include #include #include -#include -#include #include -#include -#include #include #include -#if defined(TARGET_LINUX) -#include -#endif // TARGET_LINUX - -#if defined(TARGET_APPLE) || defined(TARGET_FREEBSD) || defined(TARGET_NETBSD) || defined(TARGET_OPENBSD) -#include -#define MINIPAL_WAIT_USES_KQUEUE 1 -#else -#define MINIPAL_WAIT_USES_KQUEUE 0 -#endif // kqueue platforms - #include #include "minipal-wait.h" namespace { - enum class WaitableKind - { - Event, - ProcessFileDescriptor, - ProcessKqueue, - ProcessPipe, - }; - enum class AcquireResult { Failed, @@ -49,33 +26,13 @@ namespace struct Waitable { - int32_t refCount; - int32_t publicRefCount; - WaitableKind kind; minipal_mutex mutex; + int32_t refCount; + int readFileDescriptor; + int writeFileDescriptor; bool mutexInitialized; + bool resetOnWait; bool signaled; - union - { - struct - { - int readFileDescriptor; - int writeFileDescriptor; - } event; - - struct - { - int fileDescriptor; - pid_t processId; - } processFileDescriptor; - - struct - { - int readFileDescriptor; - int writeFileDescriptor; - pid_t processId; - } processPipe; - }; }; void CloseFileDescriptor(int fileDescriptor) @@ -172,7 +129,7 @@ namespace return true; } - Waitable* AllocateWaitable(WaitableKind kind) + Waitable* AllocateWaitable(bool resetOnWait) { Waitable* waitable = new (std::nothrow) Waitable; if (waitable == nullptr) @@ -181,30 +138,11 @@ namespace } waitable->refCount = 1; - waitable->publicRefCount = 1; - waitable->kind = kind; waitable->mutexInitialized = false; + waitable->resetOnWait = resetOnWait; waitable->signaled = false; - - switch (kind) - { - case WaitableKind::Event: - waitable->event.readFileDescriptor = -1; - waitable->event.writeFileDescriptor = -1; - break; - - case WaitableKind::ProcessFileDescriptor: - case WaitableKind::ProcessKqueue: - waitable->processFileDescriptor.fileDescriptor = -1; - waitable->processFileDescriptor.processId = 0; - break; - - case WaitableKind::ProcessPipe: - waitable->processPipe.readFileDescriptor = -1; - waitable->processPipe.writeFileDescriptor = -1; - waitable->processPipe.processId = 0; - break; - } + waitable->readFileDescriptor = -1; + waitable->writeFileDescriptor = -1; if (!minipal_mutex_init(&waitable->mutex)) { @@ -219,25 +157,9 @@ namespace void DestroyWaitable(Waitable* waitable) { assert(__atomic_load_n(&waitable->refCount, __ATOMIC_RELAXED) == 0); - assert(__atomic_load_n(&waitable->publicRefCount, __ATOMIC_RELAXED) == 0); - switch (waitable->kind) - { - case WaitableKind::Event: - CloseFileDescriptor(waitable->event.readFileDescriptor); - CloseFileDescriptor(waitable->event.writeFileDescriptor); - break; - - case WaitableKind::ProcessFileDescriptor: - case WaitableKind::ProcessKqueue: - CloseFileDescriptor(waitable->processFileDescriptor.fileDescriptor); - break; - - case WaitableKind::ProcessPipe: - CloseFileDescriptor(waitable->processPipe.readFileDescriptor); - CloseFileDescriptor(waitable->processPipe.writeFileDescriptor); - break; - } + CloseFileDescriptor(waitable->readFileDescriptor); + CloseFileDescriptor(waitable->writeFileDescriptor); if (waitable->mutexInitialized) { @@ -260,7 +182,7 @@ namespace } } - bool WriteByte(int fileDescriptor, bool allowFullPipe) + bool WriteByte(int fileDescriptor) { uint8_t value = 1; ssize_t result; @@ -269,19 +191,7 @@ namespace result = write(fileDescriptor, &value, sizeof(value)); } while (result < 0 && errno == EINTR); - return result == sizeof(value) || (allowFullPipe && result < 0 && errno == EAGAIN); - } - - void ReleasePublicReference(Waitable* waitable) - { - { - minipal::MutexHolder lock(waitable->mutex); - int32_t publicRefCount = - __atomic_sub_fetch(&waitable->publicRefCount, 1, __ATOMIC_ACQ_REL); - assert(publicRefCount >= 0); - } - - ReleaseReference(waitable); + return result == sizeof(value); } bool ReadByte(int fileDescriptor) @@ -296,18 +206,14 @@ namespace return result == sizeof(value); } - bool SignalPipeLocked(Waitable* waitable) + bool SignalEventLocked(Waitable* waitable) { - assert(waitable->kind == WaitableKind::Event || waitable->kind == WaitableKind::ProcessPipe); if (waitable->signaled) { return true; } - int writeFileDescriptor = waitable->kind == WaitableKind::Event - ? waitable->event.writeFileDescriptor - : waitable->processPipe.writeFileDescriptor; - if (!WriteByte(writeFileDescriptor, false)) + if (!WriteByte(waitable->writeFileDescriptor)) { return false; } @@ -316,15 +222,15 @@ namespace return true; } - bool SignalPipe(Waitable* waitable) + bool SignalEvent(Waitable* waitable) { minipal::MutexHolder lock(waitable->mutex); - return SignalPipeLocked(waitable); + return SignalEventLocked(waitable); } - bool ResetPipe(Waitable* waitable) + bool ResetEvent(Waitable* waitable) { - assert(waitable->kind == WaitableKind::Event || waitable->kind == WaitableKind::ProcessPipe); + assert(waitable->resetOnWait); minipal::MutexHolder lock(waitable->mutex); if (!waitable->signaled) { @@ -333,12 +239,9 @@ namespace uint8_t buffer[16]; ssize_t result; - int readFileDescriptor = waitable->kind == WaitableKind::Event - ? waitable->event.readFileDescriptor - : waitable->processPipe.readFileDescriptor; do { - result = read(readFileDescriptor, buffer, sizeof(buffer)); + result = read(waitable->readFileDescriptor, buffer, sizeof(buffer)); } while (result > 0 || (result < 0 && errno == EINTR)); if (result < 0 && errno != EAGAIN) @@ -350,160 +253,27 @@ namespace return true; } - AcquireResult TryAcquirePipe(Waitable* waitable) + AcquireResult TryAcquire(Waitable* waitable) { - assert(waitable->kind == WaitableKind::Event || waitable->kind == WaitableKind::ProcessPipe); minipal::MutexHolder lock(waitable->mutex); if (!waitable->signaled) { return AcquireResult::NotReady; } - if (waitable->kind == WaitableKind::Event) - { - if (!ReadByte(waitable->event.readFileDescriptor)) - { - return AcquireResult::Failed; - } - - waitable->signaled = false; - } - - return AcquireResult::Ready; - } - - void ReapChild(pid_t processId) - { - int status; - pid_t result; - do - { - result = waitpid(processId, &status, WNOHANG); - } while (result < 0 && errno == EINTR); - } - - AcquireResult TryAcquireProcessFileDescriptor(Waitable* waitable) - { - assert(waitable->kind == WaitableKind::ProcessFileDescriptor); - minipal::MutexHolder lock(waitable->mutex); - if (waitable->signaled) - { - return AcquireResult::Ready; - } - - pollfd descriptor = {}; - descriptor.fd = waitable->processFileDescriptor.fileDescriptor; - descriptor.events = POLLIN; - - int result; - do - { - result = poll(&descriptor, 1, 0); - } while (result < 0 && errno == EINTR); - - if (result < 0 || (descriptor.revents & POLLNVAL) != 0) - { - return AcquireResult::Failed; - } - - if (result == 0) - { - return AcquireResult::NotReady; - } - - ReapChild(waitable->processFileDescriptor.processId); - waitable->signaled = true; - return AcquireResult::Ready; - } - -#if MINIPAL_WAIT_USES_KQUEUE - AcquireResult TryAcquireProcessKqueue(Waitable* waitable) - { - assert(waitable->kind == WaitableKind::ProcessKqueue); - minipal::MutexHolder lock(waitable->mutex); - if (waitable->signaled) - { - return AcquireResult::Ready; - } - - struct kevent processEvent; - timespec timeout = {}; - int result; - do - { - result = kevent( - waitable->processFileDescriptor.fileDescriptor, - nullptr, - 0, - &processEvent, - 1, - &timeout); - } while (result < 0 && errno == EINTR); - - if (result < 0) + if (waitable->resetOnWait && + !ReadByte(waitable->readFileDescriptor)) { return AcquireResult::Failed; } - if (result == 0) + if (waitable->resetOnWait) { - return AcquireResult::NotReady; + waitable->signaled = false; } - - ReapChild(waitable->processFileDescriptor.processId); - waitable->signaled = true; return AcquireResult::Ready; } - void MarkExitedProcesses( - Waitable* const* waitables, - uint32_t count, - const struct kevent* events, - int eventCount) - { - for (int eventIndex = 0; eventIndex < eventCount; eventIndex++) - { - if (events[eventIndex].filter != EVFILT_PROC) - { - continue; - } - - for (uint32_t handleIndex = 0; handleIndex < count; handleIndex++) - { - Waitable* waitable = waitables[handleIndex]; - if (waitable->kind == WaitableKind::ProcessKqueue && - waitable->processFileDescriptor.processId == static_cast(events[eventIndex].ident)) - { - minipal::MutexHolder lock(waitable->mutex); - ReapChild(waitable->processFileDescriptor.processId); - waitable->signaled = true; - } - } - } - } -#endif // MINIPAL_WAIT_USES_KQUEUE - - AcquireResult TryAcquire(Waitable* waitable) - { - switch (waitable->kind) - { - case WaitableKind::Event: - case WaitableKind::ProcessPipe: - return TryAcquirePipe(waitable); - - case WaitableKind::ProcessFileDescriptor: - return TryAcquireProcessFileDescriptor(waitable); - -#if MINIPAL_WAIT_USES_KQUEUE - case WaitableKind::ProcessKqueue: - return TryAcquireProcessKqueue(waitable); -#endif // MINIPAL_WAIT_USES_KQUEUE - - default: - return AcquireResult::Failed; - } - } - int32_t TryAcquireAny(Waitable* const* waitables, uint32_t count) { for (uint32_t index = 0; index < count; index++) @@ -557,114 +327,9 @@ namespace : static_cast(remainingMilliseconds); } - bool HasProcessExited(pid_t processId) - { - int status; - pid_t result; - do - { - result = waitpid(processId, &status, WNOHANG); - } while (result < 0 && errno == EINTR); - - if (result == processId) - { - return true; - } - - if (result == 0) - { - return false; - } - - if (result < 0 && errno == ECHILD) - { - // No identity-stable primitive is available on this fallback path, so PID reuse can race this probe. - int killResult; - do - { - killResult = kill(processId, 0); - } while (killResult < 0 && errno == EINTR); - - return killResult < 0 && errno == ESRCH; - } - - return false; - } - - void SignalProcessWatcherExit(Waitable* waitable) - { - assert(waitable->kind == WaitableKind::ProcessPipe); - minipal::MutexHolder lock(waitable->mutex); - if (__atomic_load_n(&waitable->publicRefCount, __ATOMIC_ACQUIRE) == 0) - { - return; - } - - SignalPipeLocked(waitable); - } - - void* ProcessWatcher(void* argument) - { - Waitable* waitable = static_cast(argument); - assert(waitable->kind == WaitableKind::ProcessPipe); - bool exited = false; - - while (__atomic_load_n(&waitable->publicRefCount, __ATOMIC_ACQUIRE) != 0) - { - exited = HasProcessExited(waitable->processPipe.processId); - if (exited) - { - break; - } - - timespec sleepDuration = { 0, 250 * 1000 * 1000 }; - nanosleep(&sleepDuration, nullptr); - } - - if (exited) - { - SignalProcessWatcherExit(waitable); - } - - ReleaseReference(waitable); - return nullptr; - } - - bool StartProcessWatcher(Waitable* waitable) + Waitable* CreateWaitable(bool initialState, bool resetOnWait) { - pthread_attr_t attributes; - if (pthread_attr_init(&attributes) != 0) - { - return false; - } - - int result = pthread_attr_setdetachstate(&attributes, PTHREAD_CREATE_DETACHED); - if (result == 0) - { - pthread_t watcherThread; - AddReference(waitable); - result = pthread_create( - &watcherThread, - &attributes, - ProcessWatcher, - waitable); - - if (result != 0) - { - ReleaseReference(waitable); - } - } - - int destroyResult = pthread_attr_destroy(&attributes); - assert(destroyResult == 0); - (void)destroyResult; - return result == 0; - } - - Waitable* CreatePipeWaitable(WaitableKind kind, bool initialState) - { - assert(kind == WaitableKind::Event || kind == WaitableKind::ProcessPipe); - Waitable* waitable = AllocateWaitable(kind); + Waitable* waitable = AllocateWaitable(resetOnWait); if (waitable == nullptr) { return nullptr; @@ -673,112 +338,22 @@ namespace int eventPipe[2]; if (!CreatePipe(eventPipe)) { - ReleasePublicReference(waitable); - return nullptr; - } - - if (kind == WaitableKind::Event) - { - waitable->event.readFileDescriptor = eventPipe[0]; - waitable->event.writeFileDescriptor = eventPipe[1]; - } - else - { - assert(kind == WaitableKind::ProcessPipe); - waitable->processPipe.readFileDescriptor = eventPipe[0]; - waitable->processPipe.writeFileDescriptor = eventPipe[1]; - } - - if (initialState && !SignalPipe(waitable)) - { - ReleasePublicReference(waitable); + ReleaseReference(waitable); return nullptr; } - return waitable; - } - - Waitable* CreateSignaledProcessWaitable(pid_t processId) - { - Waitable* waitable = CreatePipeWaitable(WaitableKind::ProcessPipe, true); - if (waitable != nullptr) - { - waitable->processPipe.processId = processId; - ReapChild(processId); - } - - return waitable; - } - - Waitable* CreateProcessWatcherWaitable(pid_t processId) - { - Waitable* waitable = CreatePipeWaitable(WaitableKind::ProcessPipe, false); - if (waitable == nullptr) - { - return nullptr; - } + waitable->readFileDescriptor = eventPipe[0]; + waitable->writeFileDescriptor = eventPipe[1]; - waitable->processPipe.processId = processId; - if (!StartProcessWatcher(waitable)) + if (initialState && !SignalEvent(waitable)) { - ReleasePublicReference(waitable); + ReleaseReference(waitable); return nullptr; } return waitable; } -#if MINIPAL_WAIT_USES_KQUEUE - Waitable* CreateKqueueProcessWaitable(pid_t processId) - { - int processQueue = kqueue(); - if (processQueue < 0) - { - return nullptr; - } - - if (!SetCloseOnExec(processQueue)) - { - CloseFileDescriptor(processQueue); - return nullptr; - } - - struct kevent change; - EV_SET( - &change, - processId, - EVFILT_PROC, - EV_ADD | EV_ENABLE | EV_ONESHOT, - NOTE_EXIT, - 0, - nullptr); - - if (kevent(processQueue, &change, 1, nullptr, 0, nullptr) < 0) - { - int error = errno; - CloseFileDescriptor(processQueue); - if (error == ESRCH) - { - return CreateSignaledProcessWaitable(processId); - } - - errno = error; - return nullptr; - } - - Waitable* waitable = AllocateWaitable(WaitableKind::ProcessKqueue); - if (waitable == nullptr) - { - CloseFileDescriptor(processQueue); - return nullptr; - } - - waitable->processFileDescriptor.processId = processId; - waitable->processFileDescriptor.fileDescriptor = processQueue; - return waitable; - } -#endif // MINIPAL_WAIT_USES_KQUEUE - int32_t WaitWithPoll( Waitable* const* waitables, uint32_t count, @@ -788,26 +363,7 @@ namespace pollfd descriptors[MINIPAL_MAX_WAIT_OBJECTS] = {}; for (uint32_t index = 0; index < count; index++) { - Waitable* waitable = waitables[index]; - switch (waitable->kind) - { - case WaitableKind::Event: - descriptors[index].fd = waitable->event.readFileDescriptor; - break; - - case WaitableKind::ProcessPipe: - descriptors[index].fd = waitable->processPipe.readFileDescriptor; - break; - - case WaitableKind::ProcessFileDescriptor: - descriptors[index].fd = waitable->processFileDescriptor.fileDescriptor; - break; - - default: - errno = EINVAL; - return MINIPAL_WAIT_FAILED; - } - + descriptors[index].fd = waitables[index]->readFileDescriptor; descriptors[index].events = POLLIN; } @@ -863,236 +419,6 @@ namespace } } -#if MINIPAL_WAIT_USES_KQUEUE - int32_t WaitWithKqueue( - Waitable* const* waitables, - uint32_t count, - uint32_t timeout, - uint64_t deadline) - { - while (true) - { - int32_t acquired = TryAcquireAny(waitables, count); - if (acquired != MINIPAL_WAIT_TIMEOUT || timeout == 0) - { - return acquired; - } - - int waitQueue = kqueue(); - if (waitQueue < 0) - { - return MINIPAL_WAIT_FAILED; - } - - if (!SetCloseOnExec(waitQueue)) - { - CloseFileDescriptor(waitQueue); - return MINIPAL_WAIT_FAILED; - } - - bool registrationFailed = false; - for (uint32_t index = 0; index < count; index++) - { - Waitable* waitable = waitables[index]; - struct kevent change; - if (waitable->kind == WaitableKind::ProcessKqueue) - { - EV_SET( - &change, - waitable->processFileDescriptor.processId, - EVFILT_PROC, - EV_ADD | EV_ENABLE | EV_ONESHOT, - NOTE_EXIT, - 0, - nullptr); - } - else - { - int fileDescriptor; - switch (waitable->kind) - { - case WaitableKind::Event: - fileDescriptor = waitable->event.readFileDescriptor; - break; - - case WaitableKind::ProcessFileDescriptor: - fileDescriptor = waitable->processFileDescriptor.fileDescriptor; - break; - - case WaitableKind::ProcessPipe: - fileDescriptor = waitable->processPipe.readFileDescriptor; - break; - - default: - registrationFailed = true; - break; - } - if (registrationFailed) - { - break; - } - EV_SET( - &change, - fileDescriptor, - EVFILT_READ, - EV_ADD | EV_ENABLE, - 0, - 0, - nullptr); - } - - if (kevent(waitQueue, &change, 1, nullptr, 0, nullptr) < 0) - { - if (waitable->kind == WaitableKind::ProcessKqueue && errno == ESRCH) - { - minipal::MutexHolder lock(waitable->mutex); - ReapChild(waitable->processFileDescriptor.processId); - waitable->signaled = true; - continue; - } - - registrationFailed = true; - break; - } - } - - if (registrationFailed) - { - CloseFileDescriptor(waitQueue); - return MINIPAL_WAIT_FAILED; - } - - acquired = TryAcquireAny(waitables, count); - if (acquired != MINIPAL_WAIT_TIMEOUT) - { - CloseFileDescriptor(waitQueue); - return acquired; - } - - timespec remainingTime; - timespec* waitTimeout = nullptr; - if (timeout != MINIPAL_WAIT_INFINITE) - { - uint64_t remaining; - if (!GetRemainingNanoseconds(deadline, &remaining)) - { - CloseFileDescriptor(waitQueue); - return MINIPAL_WAIT_FAILED; - } - - if (remaining == 0) - { - CloseFileDescriptor(waitQueue); - return MINIPAL_WAIT_TIMEOUT; - } - - remainingTime.tv_sec = static_cast(remaining / 1000000000); - remainingTime.tv_nsec = static_cast(remaining % 1000000000); - waitTimeout = &remainingTime; - } - - struct kevent events[MINIPAL_MAX_WAIT_OBJECTS]; - int result; - do - { - result = kevent(waitQueue, nullptr, 0, events, count, waitTimeout); - } while (result < 0 && errno == EINTR && timeout == MINIPAL_WAIT_INFINITE); - - if (result > 0) - { - for (int eventIndex = 0; eventIndex < result; eventIndex++) - { - if ((events[eventIndex].flags & EV_ERROR) != 0 && events[eventIndex].data != 0) - { - errno = static_cast(events[eventIndex].data); - CloseFileDescriptor(waitQueue); - return MINIPAL_WAIT_FAILED; - } - } - - MarkExitedProcesses(waitables, count, events, result); - } - - int error = errno; - CloseFileDescriptor(waitQueue); - - if (result == 0) - { - return MINIPAL_WAIT_TIMEOUT; - } - - if (result < 0) - { - if (error == EINTR) - { - continue; - } - - errno = error; - return MINIPAL_WAIT_FAILED; - } - } - } -#endif // MINIPAL_WAIT_USES_KQUEUE - - Waitable* CreateProcessWaitable(uint32_t processId) - { - if (processId == 0 || processId > static_cast(INT_MAX)) - { - errno = EINVAL; - return nullptr; - } - -#if defined(TARGET_LINUX) && defined(SYS_pidfd_open) - int processFileDescriptor; - do - { - processFileDescriptor = static_cast(syscall(SYS_pidfd_open, processId, 0)); - } while (processFileDescriptor < 0 && errno == EINTR); - - if (processFileDescriptor >= 0) - { - if (!SetCloseOnExec(processFileDescriptor)) - { - CloseFileDescriptor(processFileDescriptor); - return nullptr; - } - - Waitable* waitable = AllocateWaitable(WaitableKind::ProcessFileDescriptor); - if (waitable == nullptr) - { - CloseFileDescriptor(processFileDescriptor); - return nullptr; - } - - waitable->processFileDescriptor.processId = static_cast(processId); - waitable->processFileDescriptor.fileDescriptor = processFileDescriptor; - return waitable; - } - - if (errno == ENOSYS) - { - // The syscall can be present in the build headers but absent from the running kernel. - return CreateProcessWatcherWaitable(static_cast(processId)); - } - - if (errno == ESRCH) - { - return CreateSignaledProcessWaitable(static_cast(processId)); - } -#endif // TARGET_LINUX && SYS_pidfd_open - -#if MINIPAL_WAIT_USES_KQUEUE - Waitable* waitable = CreateKqueueProcessWaitable(static_cast(processId)); - if (waitable != nullptr) - { - return waitable; - } -#endif // MINIPAL_WAIT_USES_KQUEUE - - return CreateProcessWatcherWaitable(static_cast(processId)); - } - void* DuplicateWaitable(void* handle) { if (handle == nullptr) @@ -1102,7 +428,6 @@ namespace } Waitable* waitable = static_cast(handle); - __atomic_add_fetch(&waitable->publicRefCount, 1, __ATOMIC_RELAXED); AddReference(waitable); return waitable; } @@ -1122,12 +447,12 @@ minipal_wait_handle::~minipal_wait_handle() { if (m_handle != nullptr) { - ReleasePublicReference(static_cast(m_handle)); + ReleaseReference(static_cast(m_handle)); } } minipal_event::minipal_event(bool initialState) - : minipal_wait_handle(CreatePipeWaitable(WaitableKind::Event, initialState)) + : minipal_wait_handle(CreateWaitable(initialState, true)) { } @@ -1139,14 +464,7 @@ bool minipal_event::Set() return false; } - Waitable* waitable = static_cast(GetWaitable()); - if (waitable->kind != WaitableKind::Event) - { - errno = EINVAL; - return false; - } - - return SignalPipe(waitable); + return SignalEvent(static_cast(GetWaitable())); } bool minipal_event::Reset() @@ -1157,19 +475,23 @@ bool minipal_event::Reset() return false; } - Waitable* waitable = static_cast(GetWaitable()); - if (waitable->kind != WaitableKind::Event) + return ResetEvent(static_cast(GetWaitable())); +} + +minipal_latch::minipal_latch() + : minipal_wait_handle(CreateWaitable(false, false)) +{ +} + +bool minipal_latch::Set() +{ + if (!IsValid()) { errno = EINVAL; return false; } - return ResetPipe(waitable); -} - -minipal_process_wait::minipal_process_wait(uint32_t processId) - : minipal_wait_handle(CreateProcessWaitable(processId)) -{ + return SignalEvent(static_cast(GetWaitable())); } int32_t minipal_wait_handle::Wait( @@ -1207,9 +529,5 @@ int32_t minipal_wait_handle::Wait( deadline = currentTime + static_cast(timeout) * 1000000; } -#if MINIPAL_WAIT_USES_KQUEUE - return WaitWithKqueue(waitables, count, timeout, deadline); -#else return WaitWithPoll(waitables, count, timeout, deadline); -#endif } diff --git a/src/coreclr/minipal/Windows/wait.cpp b/src/coreclr/minipal/Windows/wait.cpp index e0cc00f57cd63c..9150b4c25402f8 100644 --- a/src/coreclr/minipal/Windows/wait.cpp +++ b/src/coreclr/minipal/Windows/wait.cpp @@ -70,12 +70,17 @@ bool minipal_event::Reset() return ResetEvent(GetRawHandle()) != FALSE; } -minipal_process_wait::minipal_process_wait(uint32_t processId) - : minipal_wait_handle(OpenProcess(SYNCHRONIZE, FALSE, processId)) +minipal_latch::minipal_latch() + : minipal_wait_handle(CreateEventW(nullptr, TRUE, FALSE, nullptr)) { } -minipal_process_wait::minipal_process_wait(HANDLE handle) +bool minipal_latch::Set() +{ + return SetEvent(GetRawHandle()) != FALSE; +} + +minipal_native_handle::minipal_native_handle(HANDLE handle) : minipal_wait_handle(DuplicateNativeHandle(handle)) { } diff --git a/src/coreclr/minipal/minipal-wait.h b/src/coreclr/minipal/minipal-wait.h index 921adb749a7dbf..b3d8ff3bdd7220 100644 --- a/src/coreclr/minipal/minipal-wait.h +++ b/src/coreclr/minipal/minipal-wait.h @@ -42,15 +42,16 @@ struct minipal_wait_handle uint32_t count, uint32_t timeout); + minipal_wait_handle(const minipal_wait_handle& handle); + minipal_wait_handle& operator=(const minipal_wait_handle& handle) = delete; + virtual ~minipal_wait_handle(); + protected: #ifdef HOST_WINDOWS explicit minipal_wait_handle(HANDLE handle); #else explicit minipal_wait_handle(void* handle); #endif - minipal_wait_handle(const minipal_wait_handle& handle); - minipal_wait_handle& operator=(const minipal_wait_handle& handle) = delete; - ~minipal_wait_handle(); #ifndef HOST_WINDOWS void* GetWaitable() const @@ -82,18 +83,23 @@ struct minipal_event final : minipal_wait_handle bool Reset(); }; -struct minipal_process_wait final : minipal_wait_handle +struct minipal_latch final : minipal_wait_handle { - // Process waits remain signaled after exit. On Unix, observing a child process exit may reap it, - // matching the debugger's existing behavior. - explicit minipal_process_wait(uint32_t processId); + minipal_latch(); + minipal_latch(const minipal_latch& latch) = default; + minipal_latch& operator=(const minipal_latch& latch) = delete; + + bool Set(); +}; #ifdef HOST_WINDOWS - // Duplicates an existing native process or thread handle. - explicit minipal_process_wait(HANDLE handle); -#endif - minipal_process_wait(const minipal_process_wait& processWait) = default; - minipal_process_wait& operator=(const minipal_process_wait& processWait) = delete; +struct minipal_native_handle final : minipal_wait_handle +{ + // Duplicates an existing native handle. + explicit minipal_native_handle(HANDLE handle); + minipal_native_handle(const minipal_native_handle& handle) = default; + minipal_native_handle& operator=(const minipal_native_handle& handle) = delete; }; +#endif // HOST_WINDOWS #endif // __MINIPAL_WAIT_H__ From 729a83392ab32dd14ad550aafe7765a2c37fd0de Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 19 Aug 2026 11:41:15 -0700 Subject: [PATCH 10/12] Move debugger waits into the debug PAL Relocate the cross-platform wait implementation and header under the debugger PAL, rename the types to match debugger conventions, and transfer their build ownership from coreclr minipal. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/coreclr/debug/debug-pal/CMakeLists.txt | 26 ++++- .../Unix => debug/debug-pal/unix}/wait.cpp | 62 +++++----- .../Windows => debug/debug-pal/win}/wait.cpp | 50 ++++---- src/coreclr/debug/di/dbgtransportmanager.cpp | 29 ++--- src/coreclr/debug/di/dbgtransportmanager.h | 8 +- src/coreclr/debug/di/dbgtransportpipeline.cpp | 12 +- src/coreclr/debug/di/eventchannel.h | 4 +- src/coreclr/debug/di/localeventchannel.cpp | 8 +- src/coreclr/debug/di/nativepipeline.h | 2 +- src/coreclr/debug/di/process.cpp | 68 +++++------ src/coreclr/debug/di/remoteeventchannel.cpp | 6 +- src/coreclr/debug/di/rspriv.h | 12 +- src/coreclr/debug/di/shimremotedatatarget.cpp | 2 +- src/coreclr/debug/di/windowspipeline.cpp | 8 +- src/coreclr/debug/ee/debugger.cpp | 14 +-- src/coreclr/debug/ee/debugger.h | 14 +-- src/coreclr/debug/ee/rcthread.cpp | 36 +++--- src/coreclr/debug/inc/dbgtransportsession.h | 14 +-- src/coreclr/debug/inc/debugwait.h | 108 ++++++++++++++++++ .../debug/shared/dbgtransportsession.cpp | 28 ++--- src/coreclr/dlls/mscordbi/CMakeLists.txt | 1 - src/coreclr/minipal/Unix/CMakeLists.txt | 10 -- src/coreclr/minipal/Windows/CMakeLists.txt | 1 - src/coreclr/minipal/minipal-wait.h | 105 ----------------- 24 files changed, 321 insertions(+), 307 deletions(-) rename src/coreclr/{minipal/Unix => debug/debug-pal/unix}/wait.cpp (88%) rename src/coreclr/{minipal/Windows => debug/debug-pal/win}/wait.cpp (59%) create mode 100644 src/coreclr/debug/inc/debugwait.h delete mode 100644 src/coreclr/minipal/minipal-wait.h diff --git a/src/coreclr/debug/debug-pal/CMakeLists.txt b/src/coreclr/debug/debug-pal/CMakeLists.txt index 475fa62d2c0f1e..59b18e2c91ddb8 100644 --- a/src/coreclr/debug/debug-pal/CMakeLists.txt +++ b/src/coreclr/debug/debug-pal/CMakeLists.txt @@ -1,5 +1,6 @@ include_directories(../inc) include_directories(../../pal/inc) +include_directories(${CLR_SRC_NATIVE_DIR}) add_definitions(-DFEATURE_CORECLR) add_definitions(-DFEATURE_PERFTRACING) @@ -11,16 +12,18 @@ if(CLR_CMAKE_HOST_WIN32) if(CLR_CMAKE_TARGET_WIN32) set(DEBUG_PAL_REFEREENCE_DIAGNOSTICSERVER ON) - set(TWO_WAY_PIPE_SOURCES + set(DEBUG_PAL_SOURCES ${EVENTPIPE_PAL_SOURCES} win/twowaypipe.cpp win/processdescriptor.cpp ) else(CLR_CMAKE_TARGET_WIN32) - set(TWO_WAY_PIPE_SOURCES + set(DEBUG_PAL_SOURCES dummy/twowaypipe.cpp ) endif(CLR_CMAKE_TARGET_WIN32) + + list(APPEND DEBUG_PAL_SOURCES win/wait.cpp) endif(CLR_CMAKE_HOST_WIN32) if(CLR_CMAKE_HOST_UNIX) @@ -36,15 +39,30 @@ if(CLR_CMAKE_HOST_UNIX) include_directories(../../inc) # for volatile.h - set(TWO_WAY_PIPE_SOURCES + set(DEBUG_PAL_SOURCES ${EVENTPIPE_PAL_SOURCES} unix/twowaypipe.cpp unix/processdescriptor.cpp ) + if (NOT CLR_CMAKE_TARGET_ARCH_WASM) + list(APPEND DEBUG_PAL_SOURCES unix/wait.cpp) + # Strict POSIX mode hides pipe2 on BSD even when the platform supports it. + set_property(SOURCE unix/wait.cpp APPEND PROPERTY COMPILE_OPTIONS -U_POSIX_C_SOURCE) + endif() + endif(CLR_CMAKE_HOST_UNIX) -add_library_clr(debug-pal OBJECT ${TWO_WAY_PIPE_SOURCES}) +add_library_clr(debug-pal OBJECT ${DEBUG_PAL_SOURCES}) +target_link_libraries(debug-pal PRIVATE minipal) + +if(CLR_CMAKE_HOST_UNIX) + if(HAVE_PIPE2) + target_compile_definitions(debug-pal PRIVATE HAVE_PIPE2=1) + else() + target_compile_definitions(debug-pal PRIVATE HAVE_PIPE2=0) + endif() +endif(CLR_CMAKE_HOST_UNIX) if (DEBUG_PAL_REFEREENCE_DIAGNOSTICSERVER) target_link_libraries(debug-pal PRIVATE dn-diagnosticserver-pal) diff --git a/src/coreclr/minipal/Unix/wait.cpp b/src/coreclr/debug/debug-pal/unix/wait.cpp similarity index 88% rename from src/coreclr/minipal/Unix/wait.cpp rename to src/coreclr/debug/debug-pal/unix/wait.cpp index f12577a6d0016f..2829a882bb6b07 100644 --- a/src/coreclr/minipal/Unix/wait.cpp +++ b/src/coreclr/debug/debug-pal/unix/wait.cpp @@ -13,10 +13,12 @@ #include -#include "minipal-wait.h" +#include "debugwait.h" namespace { + constexpr uint32_t MaxWaitHandles = 64; + enum class AcquireResult { Failed, @@ -281,7 +283,7 @@ namespace AcquireResult result = TryAcquire(waitables[index]); if (result == AcquireResult::Failed) { - return MINIPAL_WAIT_FAILED; + return WaitHandle::Failed; } if (result == AcquireResult::Ready) @@ -290,7 +292,7 @@ namespace } } - return MINIPAL_WAIT_TIMEOUT; + return WaitHandle::Timeout; } bool GetMonotonicTime(uint64_t* nanoseconds) @@ -360,7 +362,7 @@ namespace uint32_t timeout, uint64_t deadline) { - pollfd descriptors[MINIPAL_MAX_WAIT_OBJECTS] = {}; + pollfd descriptors[MaxWaitHandles] = {}; for (uint32_t index = 0; index < count; index++) { descriptors[index].fd = waitables[index]->readFileDescriptor; @@ -370,23 +372,23 @@ namespace while (true) { int32_t acquired = TryAcquireAny(waitables, count); - if (acquired != MINIPAL_WAIT_TIMEOUT || timeout == 0) + if (acquired != WaitHandle::Timeout || timeout == 0) { return acquired; } int pollTimeout = -1; - if (timeout != MINIPAL_WAIT_INFINITE) + if (timeout != WaitHandle::Infinite) { uint64_t remaining; if (!GetRemainingNanoseconds(deadline, &remaining)) { - return MINIPAL_WAIT_FAILED; + return WaitHandle::Failed; } if (remaining == 0) { - return MINIPAL_WAIT_TIMEOUT; + return WaitHandle::Timeout; } pollTimeout = GetPollTimeout(remaining); @@ -395,7 +397,7 @@ namespace int result = poll(descriptors, count, pollTimeout); if (result == 0) { - return MINIPAL_WAIT_TIMEOUT; + return WaitHandle::Timeout; } if (result < 0) @@ -405,7 +407,7 @@ namespace continue; } - return MINIPAL_WAIT_FAILED; + return WaitHandle::Failed; } for (uint32_t index = 0; index < count; index++) @@ -413,7 +415,7 @@ namespace if ((descriptors[index].revents & POLLNVAL) != 0) { errno = EBADF; - return MINIPAL_WAIT_FAILED; + return WaitHandle::Failed; } } } @@ -433,17 +435,17 @@ namespace } } -minipal_wait_handle::minipal_wait_handle(void* handle) +WaitHandle::WaitHandle(void* handle) : m_handle(handle) { } -minipal_wait_handle::minipal_wait_handle(const minipal_wait_handle& handle) - : minipal_wait_handle(DuplicateWaitable(handle.m_handle)) +WaitHandle::WaitHandle(const WaitHandle& handle) + : WaitHandle(DuplicateWaitable(handle.m_handle)) { } -minipal_wait_handle::~minipal_wait_handle() +WaitHandle::~WaitHandle() { if (m_handle != nullptr) { @@ -451,12 +453,12 @@ minipal_wait_handle::~minipal_wait_handle() } } -minipal_event::minipal_event(bool initialState) - : minipal_wait_handle(CreateWaitable(initialState, true)) +WaitEvent::WaitEvent(bool initialState) + : WaitHandle(CreateWaitable(initialState, true)) { } -bool minipal_event::Set() +bool WaitEvent::Set() { if (!IsValid()) { @@ -467,7 +469,7 @@ bool minipal_event::Set() return SignalEvent(static_cast(GetWaitable())); } -bool minipal_event::Reset() +bool WaitEvent::Reset() { if (!IsValid()) { @@ -478,12 +480,12 @@ bool minipal_event::Reset() return ResetEvent(static_cast(GetWaitable())); } -minipal_latch::minipal_latch() - : minipal_wait_handle(CreateWaitable(false, false)) +WaitLatch::WaitLatch() + : WaitHandle(CreateWaitable(false, false)) { } -bool minipal_latch::Set() +bool WaitLatch::Set() { if (!IsValid()) { @@ -494,36 +496,36 @@ bool minipal_latch::Set() return SignalEvent(static_cast(GetWaitable())); } -int32_t minipal_wait_handle::Wait( - const minipal_wait_handle* const* handles, +int32_t WaitHandle::Wait( + const WaitHandle* const* handles, uint32_t count, uint32_t timeout) { - if (handles == nullptr || count == 0 || count > MINIPAL_MAX_WAIT_OBJECTS) + if (handles == nullptr || count == 0 || count > MaxWaitHandles) { errno = EINVAL; - return MINIPAL_WAIT_FAILED; + return Failed; } - Waitable* waitables[MINIPAL_MAX_WAIT_OBJECTS]; + Waitable* waitables[MaxWaitHandles]; for (uint32_t index = 0; index < count; index++) { if (handles[index] == nullptr || !handles[index]->IsValid()) { errno = EINVAL; - return MINIPAL_WAIT_FAILED; + return Failed; } waitables[index] = static_cast(handles[index]->m_handle); } uint64_t deadline = 0; - if (timeout != 0 && timeout != MINIPAL_WAIT_INFINITE) + if (timeout != 0 && timeout != Infinite) { uint64_t currentTime; if (!GetMonotonicTime(¤tTime)) { - return MINIPAL_WAIT_FAILED; + return Failed; } deadline = currentTime + static_cast(timeout) * 1000000; diff --git a/src/coreclr/minipal/Windows/wait.cpp b/src/coreclr/debug/debug-pal/win/wait.cpp similarity index 59% rename from src/coreclr/minipal/Windows/wait.cpp rename to src/coreclr/debug/debug-pal/win/wait.cpp index 9150b4c25402f8..1f3d88de9b2852 100644 --- a/src/coreclr/minipal/Windows/wait.cpp +++ b/src/coreclr/debug/debug-pal/win/wait.cpp @@ -3,10 +3,12 @@ #include -#include "minipal-wait.h" +#include "debugwait.h" namespace { + constexpr uint32_t MaxWaitHandles = 64; + HANDLE DuplicateNativeHandle(HANDLE handle) { if (handle == nullptr) @@ -32,17 +34,17 @@ namespace } } -minipal_wait_handle::minipal_wait_handle(HANDLE handle) +WaitHandle::WaitHandle(HANDLE handle) : m_handle(handle) { } -minipal_wait_handle::minipal_wait_handle(const minipal_wait_handle& handle) - : minipal_wait_handle(DuplicateNativeHandle(handle.m_handle)) +WaitHandle::WaitHandle(const WaitHandle& handle) + : WaitHandle(DuplicateNativeHandle(handle.m_handle)) { } -minipal_wait_handle::~minipal_wait_handle() +WaitHandle::~WaitHandle() { if (m_handle != nullptr) { @@ -50,59 +52,59 @@ minipal_wait_handle::~minipal_wait_handle() } } -minipal_event::minipal_event(bool initialState) - : minipal_wait_handle(CreateEventW(nullptr, FALSE, initialState, nullptr)) +WaitEvent::WaitEvent(bool initialState) + : WaitHandle(CreateEventW(nullptr, FALSE, initialState, nullptr)) { } -minipal_event::minipal_event(HANDLE handle) - : minipal_wait_handle(DuplicateNativeHandle(handle)) +WaitEvent::WaitEvent(HANDLE handle) + : WaitHandle(DuplicateNativeHandle(handle)) { } -bool minipal_event::Set() +bool WaitEvent::Set() { return SetEvent(GetRawHandle()) != FALSE; } -bool minipal_event::Reset() +bool WaitEvent::Reset() { return ResetEvent(GetRawHandle()) != FALSE; } -minipal_latch::minipal_latch() - : minipal_wait_handle(CreateEventW(nullptr, TRUE, FALSE, nullptr)) +WaitLatch::WaitLatch() + : WaitHandle(CreateEventW(nullptr, TRUE, FALSE, nullptr)) { } -bool minipal_latch::Set() +bool WaitLatch::Set() { return SetEvent(GetRawHandle()) != FALSE; } -minipal_native_handle::minipal_native_handle(HANDLE handle) - : minipal_wait_handle(DuplicateNativeHandle(handle)) +NativeHandle::NativeHandle(HANDLE handle) + : WaitHandle(DuplicateNativeHandle(handle)) { } -int32_t minipal_wait_handle::Wait( - const minipal_wait_handle* const* handles, +int32_t WaitHandle::Wait( + const WaitHandle* const* handles, uint32_t count, uint32_t timeout) { - if (handles == nullptr || count == 0 || count > MINIPAL_MAX_WAIT_OBJECTS) + if (handles == nullptr || count == 0 || count > MaxWaitHandles) { SetLastError(ERROR_INVALID_PARAMETER); - return MINIPAL_WAIT_FAILED; + return Failed; } - HANDLE nativeHandles[MINIPAL_MAX_WAIT_OBJECTS]; + HANDLE nativeHandles[MaxWaitHandles]; for (uint32_t index = 0; index < count; index++) { if (handles[index] == nullptr || !handles[index]->IsValid()) { SetLastError(ERROR_INVALID_HANDLE); - return MINIPAL_WAIT_FAILED; + return Failed; } nativeHandles[index] = handles[index]->m_handle; @@ -122,8 +124,8 @@ int32_t minipal_wait_handle::Wait( if (result == WAIT_TIMEOUT) { - return MINIPAL_WAIT_TIMEOUT; + return Timeout; } - return MINIPAL_WAIT_FAILED; + return Failed; } diff --git a/src/coreclr/debug/di/dbgtransportmanager.cpp b/src/coreclr/debug/di/dbgtransportmanager.cpp index 110f7991c6c89c..52141464515c30 100644 --- a/src/coreclr/debug/di/dbgtransportmanager.cpp +++ b/src/coreclr/debug/di/dbgtransportmanager.cpp @@ -11,13 +11,15 @@ #include #include #include -#include #include #endif // HOST_UNIX DbgTransportTarget g_DbgTransportTarget{}; #ifdef HOST_UNIX +// Polling interval for the per-process exit poller thread. +static const useconds_t s_processExitPollIntervalUsec = 250 * 1000; + // Polls the target PID for exit. waitpid is identity-stable for child processes; kill is a // best-effort fallback for non-children and can race PID reuse. /* static */ @@ -30,25 +32,25 @@ void *DbgTransportTarget::ProcessExitPollerThread(void *arg) bool exited = false; int status; - pid_t result; + pid_t r; do { - result = waitpid(entry->m_dwPID, &status, WNOHANG); - } while (result < 0 && errno == EINTR); + r = waitpid(entry->m_dwPID, &status, WNOHANG); + } while (r == -1 && errno == EINTR); - if (result == static_cast(entry->m_dwPID)) + if (r == (pid_t)entry->m_dwPID) { exited = WIFEXITED(status) || WIFSIGNALED(status); } - else if (result < 0 && errno == ECHILD) + else if (r == -1 && errno == ECHILD) { int killResult; do { killResult = kill(entry->m_dwPID, 0); - } while (killResult < 0 && errno == EINTR); + } while (killResult == -1 && errno == EINTR); - exited = killResult < 0 && errno == ESRCH; + exited = killResult == -1 && errno == ESRCH; } if (exited) @@ -57,8 +59,7 @@ void *DbgTransportTarget::ProcessExitPollerThread(void *arg) break; } - timespec sleepDuration = { 0, 250 * 1000 * 1000 }; - nanosleep(&sleepDuration, nullptr); + usleep(s_processExitPollIntervalUsec); } return nullptr; @@ -104,7 +105,7 @@ void DbgTransportTarget::Shutdown() // on for process termination. HRESULT DbgTransportTarget::GetTransportForProcess(const ProcessDescriptor *pProcessDescriptor, DbgTransportSession **ppTransport, - minipal_wait_handle **ppProcessHandle) + WaitHandle **ppProcessHandle) { RSLockHolder lock(&m_sLock); HRESULT hr = S_OK; @@ -136,7 +137,7 @@ HRESULT DbgTransportTarget::GetTransportForProcess(const ProcessDescriptor *pPr return (errno == ESRCH) ? E_INVALIDARG : E_FAIL; } - minipal_latch *pProcessExited = new (nothrow) minipal_latch(); + WaitLatch *pProcessExited = new (nothrow) WaitLatch(); if ((pProcessExited == NULL) || !pProcessExited->IsValid()) { delete pProcessExited; @@ -151,7 +152,7 @@ HRESULT DbgTransportTarget::GetTransportForProcess(const ProcessDescriptor *pPr return HRESULT_FROM_GetLastError(); } - minipal_native_handle *pProcessExited = new (nothrow) minipal_native_handle(hProcess); + NativeHandle *pProcessExited = new (nothrow) NativeHandle(hProcess); bool allocationFailed = pProcessExited == nullptr; DWORD error = GetLastError(); CloseHandle(hProcess); @@ -203,7 +204,7 @@ HRESULT DbgTransportTarget::GetTransportForProcess(const ProcessDescriptor *pPr _ASSERTE(entry->m_hProcessExited->IsValid()); *ppTransport = entry->m_transport; - *ppProcessHandle = new (nothrow) minipal_wait_handle(*entry->m_hProcessExited); + *ppProcessHandle = new (nothrow) WaitHandle(*entry->m_hProcessExited); if ((*ppProcessHandle == nullptr) || !(*ppProcessHandle)->IsValid()) { delete *ppProcessHandle; diff --git a/src/coreclr/debug/di/dbgtransportmanager.h b/src/coreclr/debug/di/dbgtransportmanager.h index 0229d715deafe5..114b2246aa58d5 100644 --- a/src/coreclr/debug/di/dbgtransportmanager.h +++ b/src/coreclr/debug/di/dbgtransportmanager.h @@ -11,7 +11,7 @@ #include #endif // HOST_UNIX -#include "minipal-wait.h" +#include "debugwait.h" // TODO: Ideally we'd like to remove this class and don't do any process related book keeping in DBI. @@ -44,7 +44,7 @@ class DbgTransportTarget HRESULT GetTransportForProcess( const ProcessDescriptor *pProcessDescriptor, DbgTransportSession **ppTransport, - minipal_wait_handle **ppProcessHandle); + WaitHandle **ppProcessHandle); // Give back a previously acquired transport (if nobody else is using the transport it will close down the // connection at this point). @@ -62,9 +62,9 @@ class DbgTransportTarget ProcessEntry *m_pNext; // Next entry in the list DWORD m_dwPID; // Process ID for this entry #ifdef HOST_UNIX - minipal_latch *m_hProcessExited; // Latch set when the process exits + WaitLatch *m_hProcessExited; // Latch set when the process exits #else - minipal_native_handle *m_hProcessExited; // Native process handle + NativeHandle *m_hProcessExited; // Native process handle #endif // HOST_UNIX DbgTransportSession *m_transport; // Debugger's connection to the process DWORD m_cProcessRef; // Ref count diff --git a/src/coreclr/debug/di/dbgtransportpipeline.cpp b/src/coreclr/debug/di/dbgtransportpipeline.cpp index 53bac3f62d15c3..ddb64cf78a3683 100644 --- a/src/coreclr/debug/di/dbgtransportpipeline.cpp +++ b/src/coreclr/debug/di/dbgtransportpipeline.cpp @@ -92,7 +92,7 @@ class DbgTransportPipeline : ); // Return a handle which will be signaled when the debuggee process terminates. - virtual minipal_wait_handle *GetProcessHandle(); + virtual WaitHandle *GetProcessHandle(); // Terminate the debuggee process. virtual BOOL TerminateProcess(UINT32 exitCode); @@ -139,7 +139,7 @@ class DbgTransportPipeline : DWORD m_dwProcessId; // This waitable is only valid for waiting on process termination. - minipal_wait_handle *m_hProcess; + WaitHandle *m_hProcess; DbgTransportTarget * m_pProxy; DbgTransportSession * m_pTransport; @@ -234,12 +234,12 @@ BOOL DbgTransportPipeline::WaitForDebugEvent(DEBUG_EVENT * pEvent, DWORD dwTimeo // We need to wait for a debug event from the transport and the process termination event. // On Windows, process termination is communicated via a debug event as well, but that's not true for // the Mac debugging transport. - const minipal_wait_handle *waitSet[] = { + const WaitHandle *waitSet[] = { m_pTransport->GetDebugEventReadyEvent(), m_hProcess }; - int32_t waitResult = minipal_wait_handle::Wait(waitSet, ARRAY_SIZE(waitSet), dwTimeout); + int32_t waitResult = WaitHandle::Wait(waitSet, ARRAY_SIZE(waitSet), dwTimeout); if (waitResult == 0) { @@ -306,7 +306,7 @@ BOOL DbgTransportPipeline::ContinueDebugEvent( } // Return a handle which will be signaled when the debuggee process terminates. -minipal_wait_handle *DbgTransportPipeline::GetProcessHandle() +WaitHandle *DbgTransportPipeline::GetProcessHandle() { // The handle returned here is only valid for waiting on process termination. // See code:INativeEventPipeline::GetProcessHandle. @@ -315,7 +315,7 @@ minipal_wait_handle *DbgTransportPipeline::GetProcessHandle() return nullptr; } - minipal_wait_handle *processHandle = new (nothrow) minipal_wait_handle(*m_hProcess); + WaitHandle *processHandle = new (nothrow) WaitHandle(*m_hProcess); if ((processHandle != nullptr) && !processHandle->IsValid()) { delete processHandle; diff --git a/src/coreclr/debug/di/eventchannel.h b/src/coreclr/debug/di/eventchannel.h index 2c572dbe45f4e3..9163a36c48e4f7 100644 --- a/src/coreclr/debug/di/eventchannel.h +++ b/src/coreclr/debug/di/eventchannel.h @@ -12,7 +12,7 @@ #ifndef _EVENT_CHANNEL_H_ #define _EVENT_CHANNEL_H_ -#include "minipal-wait.h" +#include "debugwait.h" //--------------------------------------------------------------------------------------- // @@ -162,7 +162,7 @@ class IEventChannel // NeedToWaitForAck() returns true after sending an IPC event to the LS // - virtual minipal_event *GetRightSideEventAckHandle() = 0; + virtual WaitEvent *GetRightSideEventAckHandle() = 0; // // After sending an event to the LS and determining that we need to wait for the LS's acknowledgement, diff --git a/src/coreclr/debug/di/localeventchannel.cpp b/src/coreclr/debug/di/localeventchannel.cpp index 9f0f6c7a7f15a0..6145f6cab76ff2 100644 --- a/src/coreclr/debug/di/localeventchannel.cpp +++ b/src/coreclr/debug/di/localeventchannel.cpp @@ -51,7 +51,7 @@ class LocalEventChannel : public IEventChannel virtual BOOL NeedToWaitForAck(DebuggerIPCEvent * pEvent); // Get a handle to wait on after sending an IPC event to the LS. The caller should call NeedToWaitForAck() - virtual minipal_event *GetRightSideEventAckHandle(); + virtual WaitEvent *GetRightSideEventAckHandle(); // Clean up the state if the wait for an acknowledgement is unsuccessful. virtual void ClearEventForLeftSide(); @@ -100,7 +100,7 @@ class LocalEventChannel : public IEventChannel // used by the LS to signal that the event is read HANDLE m_rightSideEventRead; - minipal_event *m_rightSideEventReadWaitHandle; + WaitEvent *m_rightSideEventReadWaitHandle; // handle of the debuggee process HANDLE m_hTargetProc; @@ -192,7 +192,7 @@ HRESULT LocalEventChannel::Init(HANDLE hTargetProc) IfFailRet(DuplicateHandleToLocalProcess(&m_rightSideEventRead, &m_pDCBBuffer->m_rightSideEventRead)); - m_rightSideEventReadWaitHandle = new (nothrow) minipal_event(m_rightSideEventRead); + m_rightSideEventReadWaitHandle = new (nothrow) WaitEvent(m_rightSideEventRead); if (m_rightSideEventReadWaitHandle == nullptr) { return E_OUTOFMEMORY; @@ -312,7 +312,7 @@ BOOL LocalEventChannel::NeedToWaitForAck(DebuggerIPCEvent * pEvent) // Get a handle to wait on after sending an IPC event to the LS. The caller should call NeedToWaitForAck() // // virtual -minipal_event *LocalEventChannel::GetRightSideEventAckHandle() +WaitEvent *LocalEventChannel::GetRightSideEventAckHandle() { return m_rightSideEventReadWaitHandle; } diff --git a/src/coreclr/debug/di/nativepipeline.h b/src/coreclr/debug/di/nativepipeline.h index 6ee85875b21119..5454ce6e6484db 100644 --- a/src/coreclr/debug/di/nativepipeline.h +++ b/src/coreclr/debug/di/nativepipeline.h @@ -114,7 +114,7 @@ class INativeEventPipeline // ICorDebugProcess::GetHandle. // - virtual minipal_wait_handle *GetProcessHandle() = 0; + virtual WaitHandle *GetProcessHandle() = 0; // // Terminate the debuggee process. diff --git a/src/coreclr/debug/di/process.cpp b/src/coreclr/debug/di/process.cpp index 23deed415a36d1..21ddf8b5f37383 100644 --- a/src/coreclr/debug/di/process.cpp +++ b/src/coreclr/debug/di/process.cpp @@ -961,7 +961,7 @@ CordbProcess::CordbProcess(ULONG64 clrInstanceId, CordbHashTable m_steppers; // Closed in ~CordbProcess // Deleted in ~CordbProcess - minipal_event *m_leftSideEventAvailable; + WaitEvent *m_leftSideEventAvailable; // Closed in CloseIPCEventHandles called from ~CordbProcess HANDLE m_leftSideEventRead; @@ -1073,15 +1073,15 @@ HRESULT ShimProcess::DebugActiveProcess( // being 'managed attached' if(!pShim->m_fIsInteropDebugging) { - minipal_event terminatingEvent(pShim->m_terminatingEvent); - minipal_event markAttachPendingEvent(pShim->m_markAttachPendingEvent); - const minipal_wait_handle *waitSet[] = { &terminatingEvent, &markAttachPendingEvent }; + WaitEvent terminatingEvent(pShim->m_terminatingEvent); + WaitEvent markAttachPendingEvent(pShim->m_markAttachPendingEvent); + const WaitHandle *waitSet[] = { &terminatingEvent, &markAttachPendingEvent }; // Wait for the completion of marking pending attach bit or debugger detaching - minipal_wait_handle::Wait( + WaitHandle::Wait( waitSet, ARRAY_SIZE(waitSet), - MINIPAL_WAIT_INFINITE); + WaitHandle::Infinite); } #endif //!FEATURE_DBGIPC_TRANSPORT_DI } @@ -1620,7 +1620,7 @@ HRESULT CordbProcess::Init() // signal existing RS infrastructure. Eventually get rid of LSEA, LSER completely. // - m_leftSideEventAvailable = new (nothrow) minipal_event(false); + m_leftSideEventAvailable = new (nothrow) WaitEvent(false); if ((m_leftSideEventAvailable == nullptr) || !m_leftSideEventAvailable->IsValid()) { delete m_leftSideEventAvailable; @@ -3013,8 +3013,8 @@ void CordbProcess::DetachShim() IfFailThrow(hr); #ifdef OUT_OF_PROCESS_SETTHREADCONTEXT - minipal_event detachSetThreadContextNeededEvent(m_detachSetThreadContextNeededEvent); - const minipal_wait_handle *waitSet[] = { + WaitEvent detachSetThreadContextNeededEvent(m_detachSetThreadContextNeededEvent); + const WaitHandle *waitSet[] = { &detachSetThreadContextNeededEvent, // Signaled on every debug event after the first SendCanDetach request UnsafeGetProcessWaitHandle() // Signaled when the process exits }; @@ -3027,7 +3027,7 @@ void CordbProcess::DetachShim() } while (!fDetachComplete) { - int32_t waitResult = minipal_wait_handle::Wait( + int32_t waitResult = WaitHandle::Wait( waitSet, ARRAY_SIZE(waitSet), DETACH_WAIT_TIMEOUT_MS); @@ -3049,7 +3049,7 @@ void CordbProcess::DetachShim() // We timed out waiting for debug events, indicating the process is idle. // Simply detach as if it had succeeded. - _ASSERTE(waitResult == MINIPAL_WAIT_TIMEOUT); + _ASSERTE(waitResult == WaitHandle::Timeout); CONSISTENCY_CHECK_MSGF(false, ("Timeout while waiting for detach to complete")); fDetachComplete = true; @@ -7944,7 +7944,7 @@ HRESULT CordbProcess::StartSyncFromWin32Stop(BOOL * pfAsyncBreakSent) bool CordbProcess::CheckIfLSExited() { // Check by waiting on the handle with no timeout. - if (minipal_wait_handle::Wait(*m_handle, 0) == 0) + if (WaitHandle::Wait(*m_handle, 0) == 0) { Lock(); m_terminated = true; @@ -8772,7 +8772,7 @@ HRESULT CordbRCEventThread::Init() if (m_cordb == NULL) return E_INVALIDARG; - m_threadControlEvent = new (nothrow) minipal_event(false); + m_threadControlEvent = new (nothrow) WaitEvent(false); if ((m_threadControlEvent == nullptr) || !m_threadControlEvent->IsValid()) { @@ -9339,7 +9339,7 @@ HRESULT CordbRCEventThread::SendIPCEvent(CordbProcess* process, } else { - minipal_wait_handle *pLSProcess = process->UnsafeGetProcessWaitHandle(); + WaitHandle *pLSProcess = process->UnsafeGetProcessWaitHandle(); // We take locks to ensure that the CordbProcess object is still alive, // even if the OS process exited. @@ -9353,7 +9353,7 @@ HRESULT CordbRCEventThread::SendIPCEvent(CordbProcess* process, // There is a race here - we can't rely on any check above SendEventToLeftSide // to tell us whether the process has exited yet. // Check for that case and return an accurate hresult. - int32_t waitResult = minipal_wait_handle::Wait(*pLSProcess, 0); + int32_t waitResult = WaitHandle::Wait(*pLSProcess, 0); if (waitResult == 0) { return CORDBG_E_PROCESS_TERMINATED; @@ -9392,7 +9392,7 @@ HRESULT CordbRCEventThread::SendIPCEvent(CordbProcess* process, // This includes when we've dispatch Native events, and it includes the AsyncBreak sent to get us from a // win32 frozen state to a synchronized state). #ifdef HOST_WINDOWS - minipal_native_handle *pHelperThreadWait = nullptr; + NativeHandle *pHelperThreadWait = nullptr; HANDLE hHelperThread = NULL; if (process->IsStopped()) { @@ -9401,7 +9401,7 @@ HRESULT CordbRCEventThread::SendIPCEvent(CordbProcess* process, if (hHelperThread != NULL) { - pHelperThreadWait = new (nothrow) minipal_native_handle(hHelperThread); + pHelperThreadWait = new (nothrow) NativeHandle(hHelperThread); if ((pHelperThreadWait == nullptr) || !pHelperThreadWait->IsValid()) { delete pHelperThreadWait; @@ -9409,11 +9409,11 @@ HRESULT CordbRCEventThread::SendIPCEvent(CordbProcess* process, } } #else - minipal_wait_handle *pHelperThreadWait = nullptr; + WaitHandle *pHelperThreadWait = nullptr; #endif // In a tie, wait-any gives priority to the handle earlier in the array. - const minipal_wait_handle *waitSet[] = { + const WaitHandle *waitSet[] = { process->GetEventChannel()->GetRightSideEventAckHandle(), pLSProcess, pHelperThreadWait @@ -9426,9 +9426,9 @@ HRESULT CordbRCEventThread::SendIPCEvent(CordbProcess* process, do { - waitResult = minipal_wait_handle::Wait(waitSet, cWaitSet, CordbGetWaitTimeout()); + waitResult = WaitHandle::Wait(waitSet, cWaitSet, CordbGetWaitTimeout()); // If we timeout because we're waiting for an uncontinued OOB event, we need to just keep waiting. - } while ((waitResult == MINIPAL_WAIT_TIMEOUT) && process->IsWaitingForOOBEvent()); + } while ((waitResult == WaitHandle::Timeout) && process->IsWaitingForOOBEvent()); delete pHelperThreadWait; @@ -9485,7 +9485,7 @@ HRESULT CordbRCEventThread::SendIPCEvent(CordbProcess* process, // If we timed out/failed, check the left side to see if it is in the unrecoverable error mode. If it is, // return the HR from the left side that caused the error. Otherwise, return that we timed out and that // we don't really know why. - HRESULT realHR = waitResult == MINIPAL_WAIT_FAILED ? E_FAIL : ErrWrapper(CORDBG_E_TIMEOUT); + HRESULT realHR = waitResult == WaitHandle::Failed ? E_FAIL : ErrWrapper(CORDBG_E_TIMEOUT); hr = process->CheckForUnrecoverableError(); @@ -9712,7 +9712,7 @@ void CordbRCEventThread::ProcessStateChanged() //--------------------------------------------------------------------------------------- void CordbRCEventThread::ThreadProc() { - const minipal_wait_handle *waitSet[MAXIMUM_WAIT_OBJECTS]; + const WaitHandle *waitSet[MAXIMUM_WAIT_OBJECTS]; CordbProcess * rgProcessSet[MAXIMUM_WAIT_OBJECTS]; unsigned int waitCount; @@ -9729,9 +9729,9 @@ void CordbRCEventThread::ThreadProc() while (m_run) { - int32_t waitResult = minipal_wait_handle::Wait(waitSet, waitCount, 2000); + int32_t waitResult = WaitHandle::Wait(waitSet, waitCount, 2000); - if (waitResult == MINIPAL_WAIT_FAILED) + if (waitResult == WaitHandle::Failed) { STRESS_LOG0(LF_CORDB, LL_INFO10000, "CordbRCEventThread::ThreadProc wait failed\n"); } @@ -10054,7 +10054,7 @@ HRESULT CordbRCEventThread::WaitForIPCEventFromProcess(CordbProcess * pProcess, do { _ASSERTE(!pProcess->ThreadHoldsProcessLock()); - waitResult = minipal_wait_handle::Wait( + waitResult = WaitHandle::Wait( *pProcess->m_leftSideEventAvailable, CordbGetWaitTimeout()); @@ -10063,7 +10063,7 @@ HRESULT CordbRCEventThread::WaitForIPCEventFromProcess(CordbProcess * pProcess, return CORDBG_E_PROCESS_TERMINATED; } // If we timeout because we're waiting for an uncontinued OOB event, we need to just keep waiting. - } while ((waitResult == MINIPAL_WAIT_TIMEOUT) && pProcess->IsWaitingForOOBEvent()); + } while ((waitResult == WaitHandle::Timeout) && pProcess->IsWaitingForOOBEvent()); @@ -10089,7 +10089,7 @@ HRESULT CordbRCEventThread::WaitForIPCEventFromProcess(CordbProcess * pProcess, return hr; } - else if (waitResult == MINIPAL_WAIT_TIMEOUT) + else if (waitResult == WaitHandle::Timeout) { // // If we timed out, check the left side to see if it is in the @@ -10111,7 +10111,7 @@ HRESULT CordbRCEventThread::WaitForIPCEventFromProcess(CordbProcess * pProcess, } else { - _ASSERTE(waitResult == MINIPAL_WAIT_FAILED); + _ASSERTE(waitResult == WaitHandle::Failed); hr = E_FAIL; @@ -10254,7 +10254,7 @@ HRESULT CordbWin32EventThread::Init() m_sendToWin32EventThreadMutex.Init("Win32-Send lock", RSLock::cLockFlat, RSLock::LL_WIN32_SEND_LOCK); - m_threadControlEvent = new (nothrow) minipal_event(false); + m_threadControlEvent = new (nothrow) WaitEvent(false); if ((m_threadControlEvent == nullptr) || !m_threadControlEvent->IsValid()) { delete m_threadControlEvent; @@ -11205,7 +11205,7 @@ void CordbWin32EventThread::Win32EventLoop() unsigned int cWaitCount = 1; - const minipal_wait_handle *waitSet[2]; + const WaitHandle *waitSet[2]; waitSet[0] = m_threadControlEvent; @@ -11277,9 +11277,9 @@ void CordbWin32EventThread::Win32EventLoop() // See if any process that we aren't attached to as the Win32 debugger have exited. (Note: this is a // polling action if we are also waiting for Win32 debugger events. We're also looking at the thread // control event here, too, to see if we're supposed to do something, like attach. - int32_t waitResult = minipal_wait_handle::Wait(waitSet, cWaitCount, dwWaitTimeout); + int32_t waitResult = WaitHandle::Wait(waitSet, cWaitCount, dwWaitTimeout); - _ASSERTE((waitResult == MINIPAL_WAIT_TIMEOUT) || + _ASSERTE((waitResult == WaitHandle::Timeout) || (waitResult >= 0 && static_cast(waitResult) < cWaitCount)); if (!m_run) @@ -11294,7 +11294,7 @@ void CordbWin32EventThread::Win32EventLoop() // If we haven't timed out, or if it wasn't the thread control event // that was set, then a process has // exited... - if ((waitResult != MINIPAL_WAIT_TIMEOUT) && (waitResult != 0)) + if ((waitResult != WaitHandle::Timeout) && (waitResult != 0)) { // Grab the process that exited. _ASSERTE(waitResult == 1); diff --git a/src/coreclr/debug/di/remoteeventchannel.cpp b/src/coreclr/debug/di/remoteeventchannel.cpp index dbdfa3f5652878..23d0302f9eff34 100644 --- a/src/coreclr/debug/di/remoteeventchannel.cpp +++ b/src/coreclr/debug/di/remoteeventchannel.cpp @@ -52,7 +52,7 @@ class RemoteEventChannel : public IEventChannel virtual BOOL NeedToWaitForAck(DebuggerIPCEvent * pEvent); // Get a handle to wait on after sending an IPC event to the LS. The caller should call NeedToWaitForAck() - virtual minipal_event *GetRightSideEventAckHandle(); + virtual WaitEvent *GetRightSideEventAckHandle(); // Clean up the state if the wait for an acknowledgement is unsuccessful. virtual void ClearEventForLeftSide(); @@ -95,7 +95,7 @@ HRESULT NewEventChannelForThisPlatform(CORDB_ADDRESS pLeftSideDCB, { // @dbgtodo Mac - Consider moving all of the transport logic to one place. // Perhaps add a new function on DbgTransportManager. - minipal_wait_handle *processWaitHandle = nullptr; + WaitHandle *processWaitHandle = nullptr; HRESULT hr = E_FAIL; RemoteEventChannel * pEventChannel = NULL; @@ -271,7 +271,7 @@ BOOL RemoteEventChannel::NeedToWaitForAck(DebuggerIPCEvent * pEvent) // Get a handle to wait on after sending an IPC event to the LS. The caller should call NeedToWaitForAck() // // virtual -minipal_event *RemoteEventChannel::GetRightSideEventAckHandle() +WaitEvent *RemoteEventChannel::GetRightSideEventAckHandle() { // Delegate to the transport which does the real work. return m_pTransport->GetIPCEventReadyEvent(); diff --git a/src/coreclr/debug/di/rspriv.h b/src/coreclr/debug/di/rspriv.h index 7f5a2ee4f7a3f5..ce7cf1a28b1b2a 100644 --- a/src/coreclr/debug/di/rspriv.h +++ b/src/coreclr/debug/di/rspriv.h @@ -15,7 +15,7 @@ #include #include -#include "minipal-wait.h" +#include "debugwait.h" #include @@ -3695,7 +3695,7 @@ class CordbProcess : private: // Process-exit waitable. On Windows this wraps an OS process handle. On Unix it is a minipal latch // that is valid only with the minipal wait APIs and must not be exposed to clients. - minipal_wait_handle *m_handle; + WaitHandle *m_handle; // Process descriptor - holds PID and App group ID for Mac debugging ProcessDescriptor m_processDescriptor; @@ -3711,7 +3711,7 @@ class CordbProcess : #endif } - minipal_wait_handle *UnsafeGetProcessWaitHandle() + WaitHandle *UnsafeGetProcessWaitHandle() { return m_handle; } @@ -3874,7 +3874,7 @@ class CordbProcess : DebuggerIPCRuntimeOffsets m_runtimeOffsets; - minipal_event *m_leftSideEventAvailable; + WaitEvent *m_leftSideEventAvailable; HANDLE m_leftSideEventRead; #if defined(FEATURE_INTEROP_DEBUGGING) HANDLE m_leftSideUnmanagedWaitEvent; @@ -10127,7 +10127,7 @@ class CordbWin32EventThread HANDLE m_thread; DWORD m_threadId; - minipal_event *m_threadControlEvent; + WaitEvent *m_threadControlEvent; HANDLE m_actionTakenEvent; BOOL m_run; @@ -10301,7 +10301,7 @@ class CordbRCEventThread HANDLE m_thread; DWORD m_threadId; BOOL m_run; - minipal_event *m_threadControlEvent; + WaitEvent *m_threadControlEvent; BOOL m_processStateChanged; }; diff --git a/src/coreclr/debug/di/shimremotedatatarget.cpp b/src/coreclr/debug/di/shimremotedatatarget.cpp index 8fcab27f05686c..2690de4a5b3a42 100644 --- a/src/coreclr/debug/di/shimremotedatatarget.cpp +++ b/src/coreclr/debug/di/shimremotedatatarget.cpp @@ -190,7 +190,7 @@ HRESULT BuildPlatformSpecificDataTarget(MachineInfo machineInfo, const ProcessDescriptor * pProcessDescriptor, ShimDataTarget ** ppDataTarget) { - minipal_wait_handle *processWaitHandle = nullptr; + WaitHandle *processWaitHandle = nullptr; HRESULT hr = E_FAIL; ShimRemoteDataTarget * pRemoteDataTarget = NULL; diff --git a/src/coreclr/debug/di/windowspipeline.cpp b/src/coreclr/debug/di/windowspipeline.cpp index cab5e46039aa43..cfe81085b3b737 100644 --- a/src/coreclr/debug/di/windowspipeline.cpp +++ b/src/coreclr/debug/di/windowspipeline.cpp @@ -71,7 +71,7 @@ class WindowsNativePipeline : ); // Return a handle for the debuggee process. - virtual minipal_wait_handle *GetProcessHandle(); + virtual WaitHandle *GetProcessHandle(); // Terminate the debuggee process. virtual BOOL TerminateProcess(UINT32 exitCode); @@ -156,7 +156,7 @@ BOOL WindowsNativePipeline::ContinueDebugEvent( } // Return a handle for the debuggee process. -minipal_wait_handle *WindowsNativePipeline::GetProcessHandle() +WaitHandle *WindowsNativePipeline::GetProcessHandle() { _ASSERTE(m_dwProcessId != 0); @@ -174,7 +174,7 @@ minipal_wait_handle *WindowsNativePipeline::GetProcessHandle() return nullptr; } - minipal_native_handle nativeHandle(processHandle); + NativeHandle nativeHandle(processHandle); DWORD error = GetLastError(); CloseHandle(processHandle); @@ -184,7 +184,7 @@ minipal_wait_handle *WindowsNativePipeline::GetProcessHandle() return nullptr; } - minipal_wait_handle *waitHandle = new (nothrow) minipal_wait_handle(nativeHandle); + WaitHandle *waitHandle = new (nothrow) WaitHandle(nativeHandle); DWORD duplicateError = GetLastError(); if (waitHandle == nullptr) { diff --git a/src/coreclr/debug/ee/debugger.cpp b/src/coreclr/debug/ee/debugger.cpp index b4d1a943a4655c..9cbbba51090972 100644 --- a/src/coreclr/debug/ee/debugger.cpp +++ b/src/coreclr/debug/ee/debugger.cpp @@ -2078,7 +2078,7 @@ void HelperThreadFavor::Init() CONTRACTL_END; // Create events for managing favors. - m_FavorReadEvent = new (nothrow) minipal_event(false); + m_FavorReadEvent = new (nothrow) WaitEvent(false); if ((m_FavorReadEvent == nullptr) || !m_FavorReadEvent->IsValid()) { delete m_FavorReadEvent; @@ -2086,7 +2086,7 @@ void HelperThreadFavor::Init() ThrowOutOfMemory(); } - m_FavorAvailableEvent = new (nothrow) minipal_event(false); + m_FavorAvailableEvent = new (nothrow) WaitEvent(false); if ((m_FavorAvailableEvent == nullptr) || !m_FavorAvailableEvent->IsValid()) { delete m_FavorAvailableEvent; @@ -6824,9 +6824,9 @@ HRESULT Debugger::LaunchJitDebuggerAndNativeAttach(Thread * pThread, EXCEPTION_P } LOG((LF_CORDB, LL_INFO10000, "D::LJDANA: waiting on m_exUnmanagedAttachEvent and debugger's process handle\n")); - minipal_event unmanagedAttachEvent(GetUnmanagedAttachEvent()); - minipal_native_handle debuggerProcess(processInfo.hProcess); - const minipal_wait_handle *waitSet[] = { &unmanagedAttachEvent, &debuggerProcess }; + WaitEvent unmanagedAttachEvent(GetUnmanagedAttachEvent()); + NativeHandle debuggerProcess(processInfo.hProcess); + const WaitHandle *waitSet[] = { &unmanagedAttachEvent, &debuggerProcess }; // Let the helper thread do the attach logic for us and wait for the // attach event. Must release the lock before blocking on a wait. @@ -6834,10 +6834,10 @@ HRESULT Debugger::LaunchJitDebuggerAndNativeAttach(Thread * pThread, EXCEPTION_P // Wait for one or the other to be set. Multiple threads could be waiting here. // The events are manual events, so when they go high, all threads will be released. - int32_t waitResult = minipal_wait_handle::Wait( + int32_t waitResult = WaitHandle::Wait( waitSet, ARRAY_SIZE(waitSet), - MINIPAL_WAIT_INFINITE); + WaitHandle::Infinite); // We no long need to keep handles to the debugger process. CloseHandle(processInfo.hProcess); diff --git a/src/coreclr/debug/ee/debugger.h b/src/coreclr/debug/ee/debugger.h index 41b0eb55928d72..3adb7ff084804f 100644 --- a/src/coreclr/debug/ee/debugger.h +++ b/src/coreclr/debug/ee/debugger.h @@ -36,7 +36,7 @@ #include "eedbginterface.h" #include "dbginterface.h" #include "corhost.h" -#include "minipal-wait.h" +#include "debugwait.h" #include "corjit.h" @@ -649,10 +649,10 @@ class HelperThreadFavor // that blew its stack FAVORCALLBACK m_fpFavor; void *m_pFavorData; - minipal_event *m_FavorReadEvent; + WaitEvent *m_FavorReadEvent; Crst m_FavorLock; - minipal_event *m_FavorAvailableEvent; + WaitEvent *m_FavorAvailableEvent; }; @@ -866,8 +866,8 @@ class DebuggerRCThread } Crst * GetFavorLock() { return &m_favorData.m_FavorLock; } - minipal_event *GetFavorReadEvent() { return m_favorData.m_FavorReadEvent; } - minipal_event *GetFavorAvailableEvent() { return m_favorData.m_FavorAvailableEvent; } + WaitEvent *GetFavorReadEvent() { return m_favorData.m_FavorReadEvent; } + WaitEvent *GetFavorAvailableEvent() { return m_favorData.m_FavorAvailableEvent; } HelperThreadFavor m_favorData; @@ -895,8 +895,8 @@ class DebuggerRCThread Volatile m_helperThreadRunning; bool m_run; - minipal_event *m_threadControlEvent; - minipal_event *m_helperThreadExitedEvent; + WaitEvent *m_threadControlEvent; + WaitEvent *m_helperThreadExitedEvent; HANDLE m_helperThreadCanGoEvent; bool m_rgfInitRuntimeOffsets[IPC_TARGET_COUNT]; bool m_fDetachRightSide; diff --git a/src/coreclr/debug/ee/rcthread.cpp b/src/coreclr/debug/ee/rcthread.cpp index 3384a6fa1b2d63..26fcdcb1628aea 100644 --- a/src/coreclr/debug/ee/rcthread.cpp +++ b/src/coreclr/debug/ee/rcthread.cpp @@ -276,7 +276,7 @@ HRESULT DebuggerRCThread::Init(void) // Create the thread control event. - m_threadControlEvent = new (nothrow) minipal_event(false); + m_threadControlEvent = new (nothrow) WaitEvent(false); if ((m_threadControlEvent == nullptr) || !m_threadControlEvent->IsValid()) { delete m_threadControlEvent; @@ -285,7 +285,7 @@ HRESULT DebuggerRCThread::Init(void) } // Track liveness separately so this auto-reset event is only an exit notification. - m_helperThreadExitedEvent = new (nothrow) minipal_event(false); + m_helperThreadExitedEvent = new (nothrow) WaitEvent(false); if ((m_helperThreadExitedEvent == nullptr) || !m_helperThreadExitedEvent->IsValid()) { delete m_helperThreadExitedEvent; @@ -586,7 +586,7 @@ void DebuggerRCThread::ThreadProc(void) struct HelperThreadExitSignal { Volatile &Running; - minipal_event *Event; + WaitEvent *Event; ~HelperThreadExitSignal() { @@ -860,11 +860,11 @@ void DebuggerRCThread::MainLoop() // threads doing helper duty. CantStopHolder cantStopHolder; - const minipal_wait_handle *waitSet[DRCT_COUNT_FINAL]; + const WaitHandle *waitSet[DRCT_COUNT_FINAL]; #if !defined(FEATURE_DBGIPC_TRANSPORT_VM) - minipal_event rightSideEventAvailable(m_pDCB->m_rightSideEventAvailable.ImportToLocalProcess()); + WaitEvent rightSideEventAvailable(m_pDCB->m_rightSideEventAvailable.ImportToLocalProcess()); #ifdef HOST_WINDOWS - minipal_native_handle *debuggerProcess = nullptr; + NativeHandle *debuggerProcess = nullptr; #endif // HOST_WINDOWS #endif @@ -899,7 +899,7 @@ void DebuggerRCThread::MainLoop() m_pDCB->m_rightSideProcessHandle.ImportToLocalProcess() != NULL) { _ASSERTE((cWaitCount + 1) == DRCT_COUNT_FINAL); - debuggerProcess = new (nothrow) minipal_native_handle( + debuggerProcess = new (nothrow) NativeHandle( m_pDCB->m_rightSideProcessHandle.ImportToLocalProcess()); if ((debuggerProcess == nullptr) || !debuggerProcess->IsValid()) { @@ -928,7 +928,7 @@ void DebuggerRCThread::MainLoop() } // Wait for an event from the Right Side. - int32_t waitResult = minipal_wait_handle::Wait(waitSet, cWaitCount, dwWaitTimeout); + int32_t waitResult = WaitHandle::Wait(waitSet, cWaitCount, dwWaitTimeout); if (!m_run) { @@ -1014,7 +1014,7 @@ void DebuggerRCThread::MainLoop() // dbgLockHolder goes out of scope - implicit Release // tsl goes out of scope - implicit Release } - else if (waitResult == MINIPAL_WAIT_TIMEOUT) + else if (waitResult == WaitHandle::Timeout) { LWaitTimedOut: @@ -1128,9 +1128,9 @@ void DebuggerRCThread::TemporaryHelperThreadMainLoop() // threads doing helper duty. CantStopHolder cantStopHolder; - const minipal_wait_handle *waitSet[DRCT_COUNT_FINAL]; + const WaitHandle *waitSet[DRCT_COUNT_FINAL]; #if !defined(FEATURE_DBGIPC_TRANSPORT_VM) - minipal_event rightSideEventAvailable(m_pDCB->m_rightSideEventAvailable.ImportToLocalProcess()); + WaitEvent rightSideEventAvailable(m_pDCB->m_rightSideEventAvailable.ImportToLocalProcess()); #endif #ifdef _DEBUG @@ -1155,7 +1155,7 @@ void DebuggerRCThread::TemporaryHelperThreadMainLoop() LOG((LF_CORDB, LL_INFO1000, "DRCT::ML: waiting for event.\n")); // Wait for an event from the Right Side. - int32_t waitResult = minipal_wait_handle::Wait(waitSet, cWaitCount, dwWaitTimeout); + int32_t waitResult = WaitHandle::Wait(waitSet, cWaitCount, dwWaitTimeout); if (!m_run) { @@ -1218,7 +1218,7 @@ void DebuggerRCThread::TemporaryHelperThreadMainLoop() // goto LWaitTimedOut; } - else if (waitResult == MINIPAL_WAIT_TIMEOUT) + else if (waitResult == WaitHandle::Timeout) { LWaitTimedOut: @@ -1724,7 +1724,7 @@ void DebuggerRCThread::DoFavor(FAVORCALLBACK fp, void * pData) // or totally executed the favor but not yet signaled the FavorReadEvent. We don't // know what it did, so we don't know what we can do; so we're in an unstable state. - const minipal_wait_handle *waitSet[] = { + const WaitHandle *waitSet[] = { GetFavorReadEvent(), #ifdef HOST_WINDOWS // Preserve detection of abnormal helper termination through the native thread handle. @@ -1734,7 +1734,7 @@ void DebuggerRCThread::DoFavor(FAVORCALLBACK fp, void * pData) #endif }; #ifdef HOST_WINDOWS - minipal_native_handle helperThread(m_thread); + NativeHandle helperThread(m_thread); waitSet[1] = &helperThread; #endif @@ -1744,10 +1744,10 @@ void DebuggerRCThread::DoFavor(FAVORCALLBACK fp, void * pData) // preemptive mode before waiting for the favor to complete (see Dev11 72349). GCX_PREEMP(); - int32_t waitResult = minipal_wait_handle::Wait( + int32_t waitResult = WaitHandle::Wait( waitSet, ARRAY_SIZE(waitSet), - MINIPAL_WAIT_INFINITE); + WaitHandle::Infinite); if (waitResult == 0) { @@ -1761,7 +1761,7 @@ void DebuggerRCThread::DoFavor(FAVORCALLBACK fp, void * pData) // Since we have no timeout, we shouldn't be able to get an error on the wait, // but just in case ... - _ASSERTE(waitResult != MINIPAL_WAIT_FAILED); + _ASSERTE(waitResult != WaitHandle::Failed); _ASSERTE((waitResult == 1) || !"DoFavor - unexpected wait result"); // Thread exited without doing favor, so execute it on our thread. diff --git a/src/coreclr/debug/inc/dbgtransportsession.h b/src/coreclr/debug/inc/dbgtransportsession.h index a34123777a165f..d3377a93c5d5b2 100644 --- a/src/coreclr/debug/inc/dbgtransportsession.h +++ b/src/coreclr/debug/inc/dbgtransportsession.h @@ -13,7 +13,7 @@ #include #include #include -#include "minipal-wait.h" +#include "debugwait.h" #if defined(FEATURE_DBGIPC_TRANSPORT_VM) || defined(FEATURE_DBGIPC_TRANSPORT_DI) @@ -367,7 +367,7 @@ class DbgTransportSession // requires the addresses of a couple of runtime data structures to service certain debugger requests that // may be delivered once the session is established. #ifdef RIGHT_SIDE_COMPILE - HRESULT Init(const ProcessDescriptor& pd, const minipal_wait_handle& processExited); + HRESULT Init(const ProcessDescriptor& pd, const WaitHandle& processExited); #else HRESULT Init(DebuggerIPCControlBlock * pDCB); #endif // RIGHT_SIDE_COMPILE @@ -433,8 +433,8 @@ class DbgTransportSession // Retrieves the auto-reset handle which is signalled by the session each time a new event is received // from the other side. - minipal_event *GetIPCEventReadyEvent(); - minipal_event *GetDebugEventReadyEvent(); + WaitEvent *GetIPCEventReadyEvent(); + WaitEvent *GetDebugEventReadyEvent(); // Copies the last event received from the other side into the provided buffer. This should only be called // (once) after the event returned from GetIPCEventReadyEvent()/GetDebugEventReadyEvent() has been signalled. @@ -569,7 +569,7 @@ class DbgTransportSession MessageHeader m_sHeader; // Inline message header PBYTE m_pbDataBlock; // Pointer to optional message data block (or NULL) DWORD m_cbDataBlock; // Count of bytes in above block if it's non-NULL - minipal_event *m_hReplyEvent; // Optional event to signal if this message is replied to (or NULL) + WaitEvent *m_hReplyEvent; // Optional event to signal if this message is replied to (or NULL) PBYTE m_pbReplyBlock; // Optional buffer to place data block from reply into (or NULL) DWORD m_cbReplyBlock; // Size in bytes of the above buffer if it is non-NULL Message *m_pOrigMessage; // Used when we need to find the original message from a copy @@ -665,7 +665,7 @@ class DbgTransportSession // On the RS the transport thread needs to know the IP address and port number to Connect() to. ProcessDescriptor m_pd; // Descriptor of a process we're talking to. - minipal_wait_handle *m_hProcessExited; // wait which will be signaled when the debuggee is terminated + WaitHandle *m_hProcessExited; // wait which will be signaled when the debuggee is terminated bool m_fDebuggerAttached; #endif @@ -690,7 +690,7 @@ class DbgTransportSession DWORD m_cValidEventBuffers; // Number of events that actually contain data DWORD m_idxEventBufferHead; // Index of the first valid event DWORD m_idxEventBufferTail; // Index of the first invalid event - minipal_event *m_rghEventReadyEvent[IPCET_Max]; // The event signalled when a new event arrives + WaitEvent *m_rghEventReadyEvent[IPCET_Max]; // The event signalled when a new event arrives #ifndef RIGHT_SIDE_COMPILE // The LS requires the addresses of a couple of runtime data structures in order to service MT_GetDCB etc. diff --git a/src/coreclr/debug/inc/debugwait.h b/src/coreclr/debug/inc/debugwait.h new file mode 100644 index 00000000000000..e18e8f294ea818 --- /dev/null +++ b/src/coreclr/debug/inc/debugwait.h @@ -0,0 +1,108 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef _DEBUGWAIT_H_ +#define _DEBUGWAIT_H_ + +#include + +#ifdef HOST_WINDOWS +#include +#endif + +class WaitHandle +{ +public: + static constexpr uint32_t Infinite = UINT32_MAX; + static constexpr int32_t Timeout = -1; + static constexpr int32_t Failed = -2; + + bool IsValid() const + { + return m_handle != nullptr; + } + +#ifdef HOST_WINDOWS + HANDLE GetRawHandle() const + { + return m_handle; + } +#endif + + // Waits return the zero-based index of the acquired handle, or one of the negative results above. + // Handles must remain valid until the wait returns. + static int32_t Wait(const WaitHandle& handle, uint32_t timeout) + { + const WaitHandle* handles[] = { &handle }; + return Wait(handles, 1, timeout); + } + + static int32_t Wait( + const WaitHandle* const* handles, + uint32_t count, + uint32_t timeout); + + WaitHandle(const WaitHandle& handle); + WaitHandle& operator=(const WaitHandle& handle) = delete; + virtual ~WaitHandle(); + +protected: +#ifdef HOST_WINDOWS + explicit WaitHandle(HANDLE handle); +#else + explicit WaitHandle(void* handle); +#endif + +#ifndef HOST_WINDOWS + void* GetWaitable() const + { + return m_handle; + } +#endif + +private: +#ifdef HOST_WINDOWS + HANDLE m_handle; +#else + void* m_handle; +#endif +}; + +class WaitEvent final : public WaitHandle +{ +public: + explicit WaitEvent(bool initialState); + +#ifdef HOST_WINDOWS + // Duplicates an existing native event handle. + explicit WaitEvent(HANDLE handle); +#endif + WaitEvent(const WaitEvent& event) = default; + WaitEvent& operator=(const WaitEvent& event) = delete; + + bool Set(); + bool Reset(); +}; + +class WaitLatch final : public WaitHandle +{ +public: + WaitLatch(); + WaitLatch(const WaitLatch& latch) = default; + WaitLatch& operator=(const WaitLatch& latch) = delete; + + bool Set(); +}; + +#ifdef HOST_WINDOWS +class NativeHandle final : public WaitHandle +{ +public: + // Duplicates an existing native handle. + explicit NativeHandle(HANDLE handle); + NativeHandle(const NativeHandle& handle) = default; + NativeHandle& operator=(const NativeHandle& handle) = delete; +}; +#endif // HOST_WINDOWS + +#endif // _DEBUGWAIT_H_ diff --git a/src/coreclr/debug/shared/dbgtransportsession.cpp b/src/coreclr/debug/shared/dbgtransportsession.cpp index c6b2871fdfec2c..c2a3578d4adee9 100644 --- a/src/coreclr/debug/shared/dbgtransportsession.cpp +++ b/src/coreclr/debug/shared/dbgtransportsession.cpp @@ -85,7 +85,7 @@ DbgTransportSession::~DbgTransportSession() // addresses of a couple of runtime data structures to service certain debugger requests that may be delivered // once the session is established. #ifdef RIGHT_SIDE_COMPILE -HRESULT DbgTransportSession::Init(const ProcessDescriptor& pd, const minipal_wait_handle& processExited) +HRESULT DbgTransportSession::Init(const ProcessDescriptor& pd, const WaitHandle& processExited) #else // RIGHT_SIDE_COMPILE HRESULT DbgTransportSession::Init(DebuggerIPCControlBlock *pDCB) #endif // RIGHT_SIDE_COMPILE @@ -112,7 +112,7 @@ HRESULT DbgTransportSession::Init(DebuggerIPCControlBlock *pDCB) return E_FAIL; m_pd = pd; - m_hProcessExited = new (nothrow) minipal_wait_handle(processExited); + m_hProcessExited = new (nothrow) WaitHandle(processExited); if ((m_hProcessExited == nullptr) || !m_hProcessExited->IsValid()) { delete m_hProcessExited; @@ -140,7 +140,7 @@ HRESULT DbgTransportSession::Init(DebuggerIPCControlBlock *pDCB) if (m_pEventBuffers == NULL) return E_OUTOFMEMORY; - m_rghEventReadyEvent[IPCET_OldStyle] = new (nothrow) minipal_event(false); + m_rghEventReadyEvent[IPCET_OldStyle] = new (nothrow) WaitEvent(false); if ((m_rghEventReadyEvent[IPCET_OldStyle] == nullptr) || !m_rghEventReadyEvent[IPCET_OldStyle]->IsValid()) { @@ -149,7 +149,7 @@ HRESULT DbgTransportSession::Init(DebuggerIPCControlBlock *pDCB) return E_OUTOFMEMORY; } - m_rghEventReadyEvent[IPCET_DebugEvent] = new (nothrow) minipal_event(false); + m_rghEventReadyEvent[IPCET_DebugEvent] = new (nothrow) WaitEvent(false); if ((m_rghEventReadyEvent[IPCET_DebugEvent] == nullptr) || !m_rghEventReadyEvent[IPCET_DebugEvent]->IsValid()) { @@ -379,14 +379,14 @@ HRESULT DbgTransportSession::SendDebugEvent(DebuggerIPCEvent * pEvent) // Retrieves the auto-reset handle which is signalled by the session each time a new event is received from // the other side. -minipal_event *DbgTransportSession::GetIPCEventReadyEvent() +WaitEvent *DbgTransportSession::GetIPCEventReadyEvent() { return m_rghEventReadyEvent[IPCET_OldStyle]; } // Retrieves the auto-reset handle which is signalled by the session each time a new event (disguised as a // debug event) is received from the other side. -minipal_event *DbgTransportSession::GetDebugEventReadyEvent() +WaitEvent *DbgTransportSession::GetDebugEventReadyEvent() { return m_rghEventReadyEvent[IPCET_DebugEvent]; } @@ -730,7 +730,7 @@ HRESULT DbgTransportSession::SendMessage(Message *pMessage, bool fWaitsForReply) HRESULT DbgTransportSession::SendRequestMessageAndWait(Message *pMessage) { // Allocate event to wait for reply on. - pMessage->m_hReplyEvent = new (nothrow) minipal_event(false); + pMessage->m_hReplyEvent = new (nothrow) WaitEvent(false); if ((pMessage->m_hReplyEvent == nullptr) || !pMessage->m_hReplyEvent->IsValid()) { delete pMessage->m_hReplyEvent; @@ -741,7 +741,7 @@ HRESULT DbgTransportSession::SendRequestMessageAndWait(Message *pMessage) // Acquire a second owned reference to the event because both this thread and the message pumping // thread may release their references at the same time. This thread owns hReplyEvent while the // message pumping thread owns the reference on the message. - minipal_event hReplyEvent(*pMessage->m_hReplyEvent); + WaitEvent hReplyEvent(*pMessage->m_hReplyEvent); if (!hReplyEvent.IsValid()) { delete pMessage->m_hReplyEvent; @@ -766,15 +766,15 @@ HRESULT DbgTransportSession::SendRequestMessageAndWait(Message *pMessage) // Wait for a reply (by the time this event is signalled the message header will have been overwritten by // the reply and any output buffer provided will have been filled in). #if defined(RIGHT_SIDE_COMPILE) - const minipal_wait_handle *rgEvents[] = { &hReplyEvent, m_hProcessExited }; + const WaitHandle *rgEvents[] = { &hReplyEvent, m_hProcessExited }; #else // !RIGHT_SIDE_COMPILE - const minipal_wait_handle *rgEvents[] = { &hReplyEvent }; + const WaitHandle *rgEvents[] = { &hReplyEvent }; #endif // RIGHT_SIDE_COMPILE - int32_t waitResult = minipal_wait_handle::Wait( + int32_t waitResult = WaitHandle::Wait( rgEvents, ARRAY_SIZE(rgEvents), - MINIPAL_WAIT_INFINITE); + WaitHandle::Infinite); if (waitResult == 0) { @@ -804,7 +804,7 @@ HRESULT DbgTransportSession::SendRequestMessageAndWait(Message *pMessage) // Fortunately, in this case, we know the message pumping thread is going to signal the event. if (pOriginalMessage == NULL) { - minipal_wait_handle::Wait(hReplyEvent, MINIPAL_WAIT_INFINITE); + WaitHandle::Wait(hReplyEvent, WaitHandle::Infinite); } else { @@ -1082,7 +1082,7 @@ void DbgTransportSession::SignalReplyEvent(Message * pMessage) // Make a local copy of the event handle. As soon as we signal the event, the thread blocked waiting on // the reply may wake up and trash the message. See code:DbgTransportSession::SendRequestMessageAndWait() // for more info. - minipal_event *pReplyEvent = pMessage->m_hReplyEvent; + WaitEvent *pReplyEvent = pMessage->m_hReplyEvent; _ASSERTE(pReplyEvent != nullptr); pMessage->m_hReplyEvent = nullptr; diff --git a/src/coreclr/dlls/mscordbi/CMakeLists.txt b/src/coreclr/dlls/mscordbi/CMakeLists.txt index e53f0b0de126cf..a1b7147ff4be3e 100644 --- a/src/coreclr/dlls/mscordbi/CMakeLists.txt +++ b/src/coreclr/dlls/mscordbi/CMakeLists.txt @@ -67,7 +67,6 @@ if(CLR_CMAKE_HOST_UNIX) endif(CLR_CMAKE_HOST_UNIX) set(COREDBI_LIBRARIES - coreclrminipal debug-pal cordbdi dbgutil diff --git a/src/coreclr/minipal/Unix/CMakeLists.txt b/src/coreclr/minipal/Unix/CMakeLists.txt index ba6aa8472b206f..969c502323ea79 100644 --- a/src/coreclr/minipal/Unix/CMakeLists.txt +++ b/src/coreclr/minipal/Unix/CMakeLists.txt @@ -5,10 +5,6 @@ set(SOURCES memory.cpp ) -if (NOT CLR_CMAKE_TARGET_ARCH_WASM) - list(APPEND SOURCES wait.cpp) -endif() - add_library(coreclrminipal_objects OBJECT ${SOURCES} @@ -21,12 +17,6 @@ add_library(coreclrminipal target_link_libraries(coreclrminipal PRIVATE minipal) -if (HAVE_PIPE2) - target_compile_definitions(coreclrminipal_objects PRIVATE HAVE_PIPE2=1) -else() - target_compile_definitions(coreclrminipal_objects PRIVATE HAVE_PIPE2=0) -endif() - if (CLR_CMAKE_TARGET_ARCH_WASM) install_clr(TARGETS coreclrminipal DESTINATIONS sharedFramework COMPONENT runtime) endif (CLR_CMAKE_TARGET_ARCH_WASM) diff --git a/src/coreclr/minipal/Windows/CMakeLists.txt b/src/coreclr/minipal/Windows/CMakeLists.txt index 49e9d109e280a9..fa6ed5bd8a4b82 100644 --- a/src/coreclr/minipal/Windows/CMakeLists.txt +++ b/src/coreclr/minipal/Windows/CMakeLists.txt @@ -3,7 +3,6 @@ set(SOURCES dn-u16.cpp dn-stdio.cpp memory.cpp - wait.cpp ) add_library(coreclrminipal STATIC diff --git a/src/coreclr/minipal/minipal-wait.h b/src/coreclr/minipal/minipal-wait.h deleted file mode 100644 index b3d8ff3bdd7220..00000000000000 --- a/src/coreclr/minipal/minipal-wait.h +++ /dev/null @@ -1,105 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#ifndef __MINIPAL_WAIT_H__ -#define __MINIPAL_WAIT_H__ - -#include - -#ifdef HOST_WINDOWS -#include -#endif - -constexpr uint32_t MINIPAL_MAX_WAIT_OBJECTS = 64; -constexpr uint32_t MINIPAL_WAIT_INFINITE = UINT32_MAX; -constexpr int32_t MINIPAL_WAIT_TIMEOUT = -1; -constexpr int32_t MINIPAL_WAIT_FAILED = -2; - -struct minipal_wait_handle -{ - bool IsValid() const - { - return m_handle != nullptr; - } - -#ifdef HOST_WINDOWS - HANDLE GetRawHandle() const - { - return m_handle; - } -#endif - - // Waits return the zero-based index of the acquired handle, or one of the negative results above. - // Handles must remain valid until the wait returns. - static int32_t Wait(const minipal_wait_handle& handle, uint32_t timeout) - { - const minipal_wait_handle* handles[] = { &handle }; - return Wait(handles, 1, timeout); - } - - static int32_t Wait( - const minipal_wait_handle* const* handles, - uint32_t count, - uint32_t timeout); - - minipal_wait_handle(const minipal_wait_handle& handle); - minipal_wait_handle& operator=(const minipal_wait_handle& handle) = delete; - virtual ~minipal_wait_handle(); - -protected: -#ifdef HOST_WINDOWS - explicit minipal_wait_handle(HANDLE handle); -#else - explicit minipal_wait_handle(void* handle); -#endif - -#ifndef HOST_WINDOWS - void* GetWaitable() const - { - return m_handle; - } -#endif - -private: -#ifdef HOST_WINDOWS - HANDLE m_handle; -#else - void* m_handle; -#endif -}; - -struct minipal_event final : minipal_wait_handle -{ - explicit minipal_event(bool initialState); - -#ifdef HOST_WINDOWS - // Duplicates an existing native event handle. - explicit minipal_event(HANDLE handle); -#endif - minipal_event(const minipal_event& event) = default; - minipal_event& operator=(const minipal_event& event) = delete; - - bool Set(); - bool Reset(); -}; - -struct minipal_latch final : minipal_wait_handle -{ - minipal_latch(); - minipal_latch(const minipal_latch& latch) = default; - minipal_latch& operator=(const minipal_latch& latch) = delete; - - bool Set(); -}; - -#ifdef HOST_WINDOWS -struct minipal_native_handle final : minipal_wait_handle -{ - // Duplicates an existing native handle. - explicit minipal_native_handle(HANDLE handle); - minipal_native_handle(const minipal_native_handle& handle) = default; - minipal_native_handle& operator=(const minipal_native_handle& handle) = delete; -}; -#endif // HOST_WINDOWS - -#endif // __MINIPAL_WAIT_H__ From 141787566599e101b71bc83c6a225e58b822a303 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 19 Aug 2026 13:37:22 -0700 Subject: [PATCH 11/12] Harden debugger wait initialization Reject accidental Unix PAL handle conversions to WaitEvent and explicitly initialize transport-session teardown guards and owned pointers. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/coreclr/debug/inc/dbgtransportsession.h | 14 +++++++------- src/coreclr/debug/inc/debugwait.h | 5 +++++ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/coreclr/debug/inc/dbgtransportsession.h b/src/coreclr/debug/inc/dbgtransportsession.h index d3377a93c5d5b2..d349fb47cc61c5 100644 --- a/src/coreclr/debug/inc/dbgtransportsession.h +++ b/src/coreclr/debug/inc/dbgtransportsession.h @@ -596,7 +596,7 @@ class DbgTransportSession LONG m_ref; // Some flags used to record how far we got in Init() (used for cleanup in Shutdown()). - bool m_fInitStateLock; + bool m_fInitStateLock = false; // Protocol version. This consists of two parts. The major version is incremented on incompatible protocol // updates. That is, a session between left and right sides that cannot use a protocol with the exact same @@ -651,21 +651,21 @@ class DbgTransportSession #ifdef RIGHT_SIDE_COMPILE // Notified whenever the session reaches a state that resolves WaitForSessionToOpen(). minipal_condition_variable m_sessionStateCondition; - bool m_fInitSessionStateCondition; + bool m_fInitSessionStateCondition = false; #endif // RIGHT_SIDE_COMPILE // Thread responsible for initial Connect()/Accept() on a low level transport connection and // subsequently for all message reception on that connection. Any error will cause the thread to reset // back into the Connect()/Accept() phase (along with the resulting session state change). - HANDLE m_hTransportThread; + HANDLE m_hTransportThread = NULL; - IDebugChannel* m_channel; + IDebugChannel* m_channel = NULL; #ifdef RIGHT_SIDE_COMPILE // On the RS the transport thread needs to know the IP address and port number to Connect() to. ProcessDescriptor m_pd; // Descriptor of a process we're talking to. - WaitHandle *m_hProcessExited; // wait which will be signaled when the debuggee is terminated + WaitHandle *m_hProcessExited = NULL; // wait which will be signaled when the debuggee is terminated bool m_fDebuggerAttached; #endif @@ -685,12 +685,12 @@ class DbgTransportSession // at any one time). The buffer is a circular array: clients read from the buffer at head index which is // followed by some number of valid buffers (wrapping around to the start of the array if necessary). New // events are added after these (and grow the array if the tail would touch the head otherwise). - DbgEventBufferEntry * m_pEventBuffers; // Pointer to array of incoming debugger events + DbgEventBufferEntry * m_pEventBuffers = NULL; // Pointer to array of incoming debugger events DWORD m_cEventBuffers; // Size of the array above (in events) DWORD m_cValidEventBuffers; // Number of events that actually contain data DWORD m_idxEventBufferHead; // Index of the first valid event DWORD m_idxEventBufferTail; // Index of the first invalid event - WaitEvent *m_rghEventReadyEvent[IPCET_Max]; // The event signalled when a new event arrives + WaitEvent *m_rghEventReadyEvent[IPCET_Max] = {}; // The event signalled when a new event arrives #ifndef RIGHT_SIDE_COMPILE // The LS requires the addresses of a couple of runtime data structures in order to service MT_GetDCB etc. diff --git a/src/coreclr/debug/inc/debugwait.h b/src/coreclr/debug/inc/debugwait.h index e18e8f294ea818..0e9d51f409fe71 100644 --- a/src/coreclr/debug/inc/debugwait.h +++ b/src/coreclr/debug/inc/debugwait.h @@ -76,6 +76,11 @@ class WaitEvent final : public WaitHandle #ifdef HOST_WINDOWS // Duplicates an existing native event handle. explicit WaitEvent(HANDLE handle); +#else + // A non-Windows waitable is a debug PAL primitive, not a PAL HANDLE, so an existing + // handle can't be imported. Reject pointers rather than letting them silently select + // the initial state constructor and create an unrelated event. + explicit WaitEvent(void* handle) = delete; #endif WaitEvent(const WaitEvent& event) = default; WaitEvent& operator=(const WaitEvent& event) = delete; From 64cc0505e34e3ca903f1c3897974833bc1450308 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 20 Aug 2026 11:18:07 -0700 Subject: [PATCH 12/12] Apply suggestions from code review Co-authored-by: Noah Falk --- src/coreclr/debug/di/nativepipeline.h | 2 +- src/coreclr/debug/di/rspriv.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/coreclr/debug/di/nativepipeline.h b/src/coreclr/debug/di/nativepipeline.h index 5454ce6e6484db..516e172aee557b 100644 --- a/src/coreclr/debug/di/nativepipeline.h +++ b/src/coreclr/debug/di/nativepipeline.h @@ -109,7 +109,7 @@ class INativeEventPipeline // handle for the debuggee process (see below) // // Notes: - // Handles are a Windows-specific concept. On Unix, the returned value is a minipal latch that is only + // Handles are a Windows-specific concept. On Unix, the returned value is a debug-pal latch that is only // valid for debugger-internal process-termination waits. It must not be exposed through // ICorDebugProcess::GetHandle. // diff --git a/src/coreclr/debug/di/rspriv.h b/src/coreclr/debug/di/rspriv.h index ce7cf1a28b1b2a..f8011e14d564a8 100644 --- a/src/coreclr/debug/di/rspriv.h +++ b/src/coreclr/debug/di/rspriv.h @@ -3693,8 +3693,8 @@ class CordbProcess : RSSmartPtr m_cordb; private: - // Process-exit waitable. On Windows this wraps an OS process handle. On Unix it is a minipal latch - // that is valid only with the minipal wait APIs and must not be exposed to clients. + // Process-exit waitable. On Windows this wraps an OS process handle. On Unix it is a debug-pal latch + // that is valid only with the debug-pal wait APIs and must not be exposed to clients. WaitHandle *m_handle; // Process descriptor - holds PID and App group ID for Mac debugging