diff options
Diffstat (limited to 'src/pal/src/synchmgr/wait.cpp')
-rw-r--r-- | src/pal/src/synchmgr/wait.cpp | 711 |
1 files changed, 711 insertions, 0 deletions
diff --git a/src/pal/src/synchmgr/wait.cpp b/src/pal/src/synchmgr/wait.cpp new file mode 100644 index 0000000000..9c4fe3a9ca --- /dev/null +++ b/src/pal/src/synchmgr/wait.cpp @@ -0,0 +1,711 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*++ + + + +Module Name: + + wait.cpp + +Abstract: + + Implementation of waiting functions as described in + the WIN32 API + +Revision History: + + + +--*/ + +#include "pal/thread.hpp" +#include "pal/synchobjects.hpp" +#include "pal/malloc.hpp" +#include "pal/dbgmsg.h" + +SET_DEFAULT_DEBUG_CHANNEL(SYNC); + +#define MAXIMUM_STACK_WAITOBJ_ARRAY_SIZE (MAXIMUM_WAIT_OBJECTS / 4) + +using namespace CorUnix; + +static PalObjectTypeId sg_rgWaitObjectsIds[] = + { + otiAutoResetEvent, + otiManualResetEvent, + otiMutex, + otiNamedMutex, + otiSemaphore, + otiProcess, + otiThread + }; +static CAllowedObjectTypes sg_aotWaitObject(sg_rgWaitObjectsIds, + sizeof(sg_rgWaitObjectsIds)/sizeof(sg_rgWaitObjectsIds[0])); + +/*++ +Function: + WaitForSingleObject + +See MSDN doc. +--*/ +DWORD +PALAPI +WaitForSingleObject(IN HANDLE hHandle, + IN DWORD dwMilliseconds) +{ + DWORD dwRet; + + PERF_ENTRY(WaitForSingleObject); + ENTRY("WaitForSingleObject(hHandle=%p, dwMilliseconds=%u)\n", + hHandle, dwMilliseconds); + + CPalThread * pThread = InternalGetCurrentThread(); + + dwRet = InternalWaitForMultipleObjectsEx(pThread, 1, &hHandle, FALSE, + dwMilliseconds, FALSE); + + LOGEXIT("WaitForSingleObject returns DWORD %u\n", dwRet); + PERF_EXIT(WaitForSingleObject); + return dwRet; +} + + +/*++ +Function: + WaitForSingleObjectEx + +See MSDN doc. +--*/ +DWORD +PALAPI +WaitForSingleObjectEx(IN HANDLE hHandle, + IN DWORD dwMilliseconds, + IN BOOL bAlertable) +{ + DWORD dwRet; + + PERF_ENTRY(WaitForSingleObjectEx); + ENTRY("WaitForSingleObjectEx(hHandle=%p, dwMilliseconds=%u, bAlertable=%s)\n", + hHandle, dwMilliseconds, bAlertable ? "TRUE" : "FALSE"); + + CPalThread * pThread = InternalGetCurrentThread(); + + dwRet = InternalWaitForMultipleObjectsEx(pThread, 1, &hHandle, FALSE, + dwMilliseconds, bAlertable); + + LOGEXIT("WaitForSingleObjectEx returns DWORD %u\n", dwRet); + PERF_EXIT(WaitForSingleObjectEx); + return dwRet; +} + + +/*++ +Function: + WaitForMultipleObjects + +See MSDN doc. + +--*/ +DWORD +PALAPI +WaitForMultipleObjects(IN DWORD nCount, + IN CONST HANDLE *lpHandles, + IN BOOL bWaitAll, + IN DWORD dwMilliseconds) +{ + DWORD dwRet; + + PERF_ENTRY(WaitForMultipleObjects); + ENTRY("WaitForMultipleObjects(nCount=%d, lpHandles=%p," + " bWaitAll=%d, dwMilliseconds=%u)\n", + nCount, lpHandles, bWaitAll, dwMilliseconds); + + CPalThread * pThread = InternalGetCurrentThread(); + + dwRet = InternalWaitForMultipleObjectsEx(pThread, nCount, lpHandles, + bWaitAll, dwMilliseconds, FALSE); + + LOGEXIT("WaitForMultipleObjects returns DWORD %u\n", dwRet); + PERF_EXIT(WaitForMultipleObjects); + return dwRet; +} + +/*++ +Function: + WaitForMultipleObjectsEx + +See MSDN doc for info about this function. +--*/ +DWORD +PALAPI +WaitForMultipleObjectsEx(IN DWORD nCount, + IN CONST HANDLE *lpHandles, + IN BOOL bWaitAll, + IN DWORD dwMilliseconds, + IN BOOL bAlertable) +{ + DWORD dwRet; + + PERF_ENTRY(WaitForMultipleObjectsEx); + ENTRY("WaitForMultipleObjectsEx(nCount=%d, lpHandles=%p," + " bWaitAll=%d, dwMilliseconds=%u, bAlertable=d)\n", + nCount, lpHandles, bWaitAll, dwMilliseconds, bAlertable); + + CPalThread * pThread = InternalGetCurrentThread(); + + dwRet = InternalWaitForMultipleObjectsEx(pThread, nCount, lpHandles, bWaitAll, + dwMilliseconds, bAlertable); + + LOGEXIT("WaitForMultipleObjectsEx returns DWORD %u\n", dwRet); + PERF_EXIT(WaitForMultipleObjectsEx); + return dwRet; +} + +/*++ +Function: + Sleep + +See MSDN doc. +--*/ +VOID +PALAPI +Sleep(IN DWORD dwMilliseconds) +{ + PERF_ENTRY(Sleep); + ENTRY("Sleep(dwMilliseconds=%u)\n", dwMilliseconds); + + CPalThread * pThread = InternalGetCurrentThread(); + + DWORD internalSleepRet = InternalSleepEx(pThread, dwMilliseconds, FALSE); + + if (internalSleepRet != 0) + { + ERROR("Sleep(dwMilliseconds=%u) failed [error=%u]\n", dwMilliseconds, internalSleepRet); + pThread->SetLastError(internalSleepRet); + } + + LOGEXIT("Sleep returns VOID\n"); + PERF_EXIT(Sleep); +} + + +/*++ +Function: + SleepEx + +See MSDN doc. +--*/ +DWORD +PALAPI +SleepEx(IN DWORD dwMilliseconds, + IN BOOL bAlertable) +{ + DWORD dwRet; + + PERF_ENTRY(SleepEx); + ENTRY("SleepEx(dwMilliseconds=%u, bAlertable=%d)\n", dwMilliseconds, bAlertable); + + CPalThread * pThread = InternalGetCurrentThread(); + + dwRet = InternalSleepEx(pThread, dwMilliseconds, bAlertable); + + LOGEXIT("SleepEx returns DWORD %u\n", dwRet); + PERF_EXIT(SleepEx); + + return dwRet; +} + +/*++ +Function: + QueueUserAPC + +See MSDN doc. +--*/ +DWORD +PALAPI +QueueUserAPC( + PAPCFUNC pfnAPC, + HANDLE hThread, + ULONG_PTR dwData) +{ + CPalThread * pCurrentThread = NULL; + CPalThread * pTargetThread = NULL; + IPalObject * pTargetThreadObject = NULL; + PAL_ERROR palErr; + DWORD dwRet; + + PERF_ENTRY(QueueUserAPC); + ENTRY("QueueUserAPC(pfnAPC=%p, hThread=%p, dwData=%#x)\n", + pfnAPC, hThread, dwData); + + /* NOTE: Windows does not check the validity of pfnAPC, even if it is + NULL. It just does an access violation later on when the APC call + is attempted */ + + pCurrentThread = InternalGetCurrentThread(); + + palErr = InternalGetThreadDataFromHandle( + pCurrentThread, + hThread, + 0, // THREAD_SET_CONTEXT + &pTargetThread, + &pTargetThreadObject + ); + + if (NO_ERROR != palErr) + { + ERROR("Unable to obtain thread data for handle %p (error %x)!\n", + hThread, palErr); + goto QueueUserAPC_exit; + } + + + palErr = g_pSynchronizationManager->QueueUserAPC(pCurrentThread, pTargetThread, + pfnAPC, dwData); + +QueueUserAPC_exit: + if (pTargetThreadObject) + { + pTargetThreadObject->ReleaseReference(pCurrentThread); + } + + dwRet = (NO_ERROR == palErr) ? 1 : 0; + + LOGEXIT("QueueUserAPC returns DWORD %d\n", dwRet); + PERF_EXIT(QueueUserAPC); + return dwRet; +} + +DWORD CorUnix::InternalWaitForMultipleObjectsEx( + CPalThread * pThread, + DWORD nCount, + CONST HANDLE *lpHandles, + BOOL bWaitAll, + DWORD dwMilliseconds, + BOOL bAlertable) +{ + DWORD dwRet = WAIT_FAILED; + PAL_ERROR palErr = NO_ERROR; + int i, iSignaledObjCount, iSignaledObjIndex = -1; + bool fWAll = (bool)bWaitAll, fNeedToBlock = false; + bool fAbandoned = false; + WaitType wtWaitType; + + IPalObject * pIPalObjStackArray[MAXIMUM_STACK_WAITOBJ_ARRAY_SIZE] = { NULL }; + ISynchWaitController * pISyncStackArray[MAXIMUM_STACK_WAITOBJ_ARRAY_SIZE] = { NULL }; + IPalObject ** ppIPalObjs = pIPalObjStackArray; + ISynchWaitController ** ppISyncWaitCtrlrs = pISyncStackArray; + + if ((nCount == 0) || (nCount > MAXIMUM_WAIT_OBJECTS)) + { + ppIPalObjs = NULL; // make delete at the end safe + ppISyncWaitCtrlrs = NULL; // make delete at the end safe + ERROR("Invalid object count=%d [range: 1 to %d]\n", + nCount, MAXIMUM_WAIT_OBJECTS) + pThread->SetLastError(ERROR_INVALID_PARAMETER); + goto WFMOExIntExit; + } + else if (nCount == 1) + { + fWAll = false; // makes no difference when nCount is 1 + wtWaitType = SingleObject; + } + else + { + wtWaitType = fWAll ? MultipleObjectsWaitAll : MultipleObjectsWaitOne; + if (nCount > MAXIMUM_STACK_WAITOBJ_ARRAY_SIZE) + { + ppIPalObjs = InternalNewArray<IPalObject*>(nCount); + ppISyncWaitCtrlrs = InternalNewArray<ISynchWaitController*>(nCount); + if ((NULL == ppIPalObjs) || (NULL == ppISyncWaitCtrlrs)) + { + ERROR("Out of memory allocating internal structures\n"); + pThread->SetLastError(ERROR_NOT_ENOUGH_MEMORY); + goto WFMOExIntExit; + } + } + } + + palErr = g_pObjectManager->ReferenceMultipleObjectsByHandleArray(pThread, + (VOID **)lpHandles, + nCount, + &sg_aotWaitObject, + SYNCHRONIZE, + ppIPalObjs); + if (NO_ERROR != palErr) + { + ERROR("Unable to obtain object for some or all of the handles [error=%u]\n", + palErr); + if (palErr == ERROR_INVALID_HANDLE) + pThread->SetLastError(ERROR_INVALID_HANDLE); + else + pThread->SetLastError(ERROR_INTERNAL_ERROR); + goto WFMOExIntExit; + } + + if (nCount > 1) + { + // Check for any cross-process sync objects. "Wait for any" and "wait for all" operations are not supported on + // cross-process sync objects in the PAL. + for (DWORD i = 0; i < nCount; ++i) + { + if (ppIPalObjs[i]->GetObjectType()->GetId() == otiNamedMutex) + { + ERROR("Attempt to wait for any or all handles including a cross-process sync object", ERROR_NOT_SUPPORTED); + pThread->SetLastError(ERROR_NOT_SUPPORTED); + goto WFMOExIntCleanup; + } + } + } + else if (ppIPalObjs[0]->GetObjectType()->GetId() == otiNamedMutex) + { + SharedMemoryProcessDataHeader *processDataHeader = + SharedMemoryProcessDataHeader::PalObject_GetProcessDataHeader(ppIPalObjs[0]); + _ASSERTE(processDataHeader != nullptr); + try + { + MutexTryAcquireLockResult tryAcquireLockResult = + static_cast<NamedMutexProcessData *>(processDataHeader->GetData())->TryAcquireLock(dwMilliseconds); + switch (tryAcquireLockResult) + { + case MutexTryAcquireLockResult::AcquiredLock: + dwRet = WAIT_OBJECT_0; + break; + + case MutexTryAcquireLockResult::AcquiredLockButMutexWasAbandoned: + dwRet = WAIT_ABANDONED_0; + break; + + case MutexTryAcquireLockResult::TimedOut: + dwRet = WAIT_TIMEOUT; + break; + + default: + _ASSERTE(false); + break; + } + } + catch (SharedMemoryException ex) + { + pThread->SetLastError(ex.GetErrorCode()); + } + goto WFMOExIntCleanup; + } + + if (fWAll) + { + // For a wait-all operation, check for duplicate wait objects in the array. This just uses a brute-force O(n^2) + // algorithm, but since MAXIMUM_WAIT_OBJECTS is small, the worst case is not so bad, and the average case would involve + // significantly fewer items. + for (DWORD i = 0; i < nCount - 1; ++i) + { + IPalObject *const objectToCheck = ppIPalObjs[i]; + for (DWORD j = i + 1; j < nCount; ++j) + { + if (ppIPalObjs[j] == objectToCheck) + { + ERROR("Duplicate handle provided for a wait-all operation [error=%u]\n", ERROR_INVALID_PARAMETER); + pThread->SetLastError(ERROR_INVALID_PARAMETER); + goto WFMOExIntCleanup; + } + } + } + } + + palErr = g_pSynchronizationManager->GetSynchWaitControllersForObjects( + pThread, ppIPalObjs, nCount, ppISyncWaitCtrlrs); + if (NO_ERROR != palErr) + { + ERROR("Unable to obtain ISynchWaitController interface for some or all " + "of the objects [error=%u]\n", palErr); + pThread->SetLastError(ERROR_INTERNAL_ERROR); + goto WFMOExIntCleanup; + } + + if (bAlertable) + { + // First check for pending APC. We need to do that while holding the global + // synch lock implicitely grabbed by GetSynchWaitControllersForObjects + if (g_pSynchronizationManager->AreAPCsPending(pThread)) + { + // If there is any pending APC we need to release the + // implicit global synch lock before calling into it + for (i = 0; (i < (int)nCount) && (NULL != ppISyncWaitCtrlrs[i]); i++) + { + ppISyncWaitCtrlrs[i]->ReleaseController(); + ppISyncWaitCtrlrs[i] = NULL; + } + palErr = g_pSynchronizationManager->DispatchPendingAPCs(pThread); + if (NO_ERROR == palErr) + { + dwRet = WAIT_IO_COMPLETION; + } + else + { + ASSERT("Awakened for APC, but no APC is pending\n"); + pThread->SetLastError(ERROR_INTERNAL_ERROR); + dwRet = WAIT_FAILED; + } + goto WFMOExIntCleanup; + } + } + + iSignaledObjCount = 0; + iSignaledObjIndex = -1; + for (i=0;i<(int)nCount;i++) + { + bool fValue; + palErr = ppISyncWaitCtrlrs[i]->CanThreadWaitWithoutBlocking(&fValue, &fAbandoned); + if (NO_ERROR != palErr) + { + ERROR("ISynchWaitController::CanThreadWaitWithoutBlocking() failed for " + "%d-th object [handle=%p error=%u]\n", i, lpHandles[i], palErr); + pThread->SetLastError(ERROR_INTERNAL_ERROR); + goto WFMOExIntReleaseControllers; + } + if (fValue) + { + iSignaledObjCount++; + iSignaledObjIndex = i; + if (!fWAll) + break; + } + } + + fNeedToBlock = (iSignaledObjCount == 0) || (fWAll && (iSignaledObjCount < (int)nCount)); + if (!fNeedToBlock) + { + // At least one object signaled, or bWaitAll==TRUE and all object signaled. + // No need to wait, let's unsignal the object(s) and return without blocking + int iStartIdx, iEndIdx; + + if (fWAll) + { + iStartIdx = 0; + iEndIdx = nCount; + } + else + { + iStartIdx = iSignaledObjIndex; + iEndIdx = iStartIdx + 1; + } + + // Unsignal objects + if( iStartIdx < 0 ) + { + ERROR("Buffer underflow due to iStartIdx < 0"); + pThread->SetLastError(ERROR_INTERNAL_ERROR); + dwRet = WAIT_FAILED; + goto WFMOExIntCleanup; + } + for (i = iStartIdx; i < iEndIdx; i++) + { + palErr = ppISyncWaitCtrlrs[i]->ReleaseWaitingThreadWithoutBlocking(); + if (NO_ERROR != palErr) + { + ERROR("ReleaseWaitingThreadWithoutBlocking() failed for %d-th " + "object [handle=%p error=%u]\n", + i, lpHandles[i], palErr); + pThread->SetLastError(palErr); + goto WFMOExIntReleaseControllers; + } + } + + dwRet = (fAbandoned ? WAIT_ABANDONED_0 : WAIT_OBJECT_0); + } + else if (0 == dwMilliseconds) + { + // Not enough objects signaled, but timeout is zero: no actual wait + dwRet = WAIT_TIMEOUT; + fNeedToBlock = false; + } + else + { + // Register the thread for waiting on all objects + for (i=0;i<(int)nCount;i++) + { + palErr = ppISyncWaitCtrlrs[i]->RegisterWaitingThread( + wtWaitType, + i, + (TRUE == bAlertable)); + if (NO_ERROR != palErr) + { + ERROR("RegisterWaitingThread() failed for %d-th object " + "[handle=%p error=%u]\n", i, lpHandles[i], palErr); + pThread->SetLastError(palErr); + goto WFMOExIntReleaseControllers; + } + } + } + +WFMOExIntReleaseControllers: + // Release all controllers before going to sleep + for (i = 0; i < (int)nCount; i++) + { + ppISyncWaitCtrlrs[i]->ReleaseController(); + ppISyncWaitCtrlrs[i] = NULL; + } + if (NO_ERROR != palErr) + goto WFMOExIntCleanup; + + if (fNeedToBlock) + { + ThreadWakeupReason twrWakeupReason; + + // + // Going to sleep + // + palErr = g_pSynchronizationManager->BlockThread(pThread, + dwMilliseconds, + (TRUE == bAlertable), + false, + &twrWakeupReason, + (DWORD *)&iSignaledObjIndex); + // + // Awakened + // + if (NO_ERROR != palErr) + { + ERROR("IPalSynchronizationManager::BlockThread failed for thread " + "pThread=%p [error=%u]\n", pThread, palErr); + pThread->SetLastError(palErr); + goto WFMOExIntCleanup; + } + switch (twrWakeupReason) + { + case WaitSucceeded: + dwRet = WAIT_OBJECT_0; // offset added later + break; + case MutexAbondoned: + dwRet = WAIT_ABANDONED_0; // offset added later + break; + case WaitTimeout: + dwRet = WAIT_TIMEOUT; + break; + case Alerted: + _ASSERT_MSG(bAlertable, + "Awakened for APC from a non-alertable wait\n"); + + dwRet = WAIT_IO_COMPLETION; + palErr = g_pSynchronizationManager->DispatchPendingAPCs(pThread); + + _ASSERT_MSG(NO_ERROR == palErr, + "Awakened for APC, but no APC is pending\n"); + break; + case WaitFailed: + default: + ERROR("Thread %p awakened with some failure\n", pThread); + dwRet = WAIT_FAILED; + break; + } + } + + if (!fWAll && ((WAIT_OBJECT_0 == dwRet) || (WAIT_ABANDONED_0 == dwRet))) + { + _ASSERT_MSG(0 <= iSignaledObjIndex, + "Failed to identify signaled/abandoned object\n"); + _ASSERT_MSG(iSignaledObjIndex >= 0 && nCount > static_cast<DWORD>(iSignaledObjIndex), + "SignaledObjIndex object out of range " + "[index=%d obj_count=%u\n", + iSignaledObjCount, nCount); + + if (iSignaledObjIndex < 0) + { + pThread->SetLastError(ERROR_INTERNAL_ERROR); + dwRet = WAIT_FAILED; + goto WFMOExIntCleanup; + } + dwRet += iSignaledObjIndex; + } + +WFMOExIntCleanup: + for (i = 0; i < (int)nCount; i++) + { + ppIPalObjs[i]->ReleaseReference(pThread); + ppIPalObjs[i] = NULL; + } + +WFMOExIntExit: + if (nCount > MAXIMUM_STACK_WAITOBJ_ARRAY_SIZE) + { + InternalDeleteArray(ppIPalObjs); + InternalDeleteArray(ppISyncWaitCtrlrs); + } + + return dwRet; +} + +DWORD CorUnix::InternalSleepEx ( + CPalThread * pThread, + DWORD dwMilliseconds, + BOOL bAlertable) +{ + PAL_ERROR palErr = NO_ERROR; + DWORD dwRet = WAIT_FAILED; + int iSignaledObjIndex; + + TRACE("Sleeping %u ms [bAlertable=%d]", dwMilliseconds, (int)bAlertable); + + if (bAlertable) + { + // In this case do not use AreAPCsPending. In fact, since we are + // not holding the synch lock(s) an APC posting may race with + // AreAPCsPending. + palErr = g_pSynchronizationManager->DispatchPendingAPCs(pThread); + if (NO_ERROR == palErr) + { + return WAIT_IO_COMPLETION; + } + } + + if (dwMilliseconds > 0) + { + ThreadWakeupReason twrWakeupReason; + palErr = g_pSynchronizationManager->BlockThread(pThread, + dwMilliseconds, + (TRUE == bAlertable), + true, + &twrWakeupReason, + (DWORD *)&iSignaledObjIndex); + if (NO_ERROR != palErr) + { + ERROR("IPalSynchronizationManager::BlockThread failed for thread " + "pThread=%p [error=%u]\n", pThread, palErr); + return dwRet; + } + + switch (twrWakeupReason) + { + case WaitSucceeded: + case WaitTimeout: + dwRet = 0; + break; + case Alerted: + _ASSERT_MSG(bAlertable, "Awakened for APC from a non-alertable wait\n"); + + dwRet = WAIT_IO_COMPLETION; + palErr = g_pSynchronizationManager->DispatchPendingAPCs(pThread); + _ASSERT_MSG(NO_ERROR == palErr, "Awakened for APC, but no APC is pending\n"); + + break; + case MutexAbondoned: + ASSERT("Thread %p awakened with reason=MutexAbondoned from a SleepEx\n", pThread); + break; + case WaitFailed: + default: + ERROR("Thread %p awakened with some failure\n", pThread); + break; + } + } + else + { + dwRet = 0; + } + + TRACE("Done sleeping %u ms [bAlertable=%d]", dwMilliseconds, (int)bAlertable); + return dwRet; +} + |