//
// 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;
}

