blob: aa27812ed27cde9161bdfd1d7338d19fa553edbf [file]
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license 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,
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();
PAL_ERROR palErr = InternalSleepEx(pThread, dwMilliseconds, FALSE);
if (NO_ERROR != palErr)
{
ERROR("Sleep(dwMilliseconds=%u) failed [error=%u]\n",
dwMilliseconds, palErr);
pThread->SetLastError(palErr);
}
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 (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;
}
PAL_ERROR 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)
{
dwRet = WAIT_IO_COMPLETION;
goto InternalSleepExExit;
}
else if (ERROR_NOT_FOUND == palErr)
{
// No APC was pending, just continue
palErr = NO_ERROR;
}
}
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);
goto InternalSleepExExit;
}
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);
palErr = ERROR_INTERNAL_ERROR;
break;
case WaitFailed:
default:
ERROR("Thread %p awakened with some failure\n", pThread);
palErr = ERROR_INTERNAL_ERROR;
break;
}
}
else
{
dwRet = 0;
}
TRACE("Done sleeping %u ms [bAlertable=%d]", dwMilliseconds, (int)bAlertable);
InternalSleepExExit:
return dwRet;
}