// ==++==
//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
// ==--==
// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
//
// ThreadProxy.cpp
//
// Proxy for an OS context.
//
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

#include "concrtinternal.h"

namespace Concurrency
{
namespace details
{
    /// <summary>
    ///     Constructs a thread proxy.
    /// </summary>
    ThreadProxy::ThreadProxy(IThreadProxyFactory * pFactory, unsigned int stackSize) :
        m_pFactory(pFactory),
        m_stackSize(stackSize),
        m_threadPriority(THREAD_PRIORITY_NORMAL),
        m_fBlocked(TRUE),
        m_fCanceled(FALSE),
        m_fSuspended(false)
    {
        // Thread proxy factories for Win32 threads need to be reference counted by the individual thread proxies, unlike
        // UMS based thread proxy factories. This is because thread proxies that were loaned out to threads based schedulers
        // could still be executing their dispatch loop and about to put themselves onto the idle pool on the factory at 
        // the time that the schedulers and corresponding scheduler proxies are actually destroyed (and have removed their
        // references on the RM). If no references exist on the RM, the RM goes aheads and destroys the factories. However,
        // it is dangerous to do this while thread proxies are possibly in the process of returning to the factory. Therefore,
        // the outstanding thread proxies (alive but not in the idle pool), need to keep the factory alive until they have all
        // returned.
        //
        // UMS thread proxies on the other hand, need the existence of a UMS virtual processor root in order to execute, and the
        // UMS virtual processor roots are responsible for adding them to the idle pool. It is safe to say that all UMS thread 
        // proxies loaned out to a UMS scheduler are back in the idle pool of the factory at the time the UMS scheduler/scheduler
        // proxy (virtual processors roots and all) are destroyed, and the factory can safely be shutdown without worrying about
        // stragglers.
        m_pFactory->Reference();

        m_id = ResourceManager::GetThreadProxyId();

        m_hBlock = CreateEventW(NULL, FALSE, FALSE, NULL);
        if (m_hBlock == NULL)
            throw scheduler_resource_allocation_error(HRESULT_FROM_WIN32(GetLastError()));

        m_hPhysicalContext = LoadLibraryAndCreateThread(NULL,
                                          m_stackSize*KB,
                                          ThreadProxyMain,
                                          this,
                                          STACK_SIZE_PARAM_IS_A_RESERVATION,
                                          &m_threadId);
    }

    /// <summary>
    ///     Destroys a thread proxy.
    /// </summary>
    ThreadProxy::~ThreadProxy()
    {
        CloseHandle(m_hBlock);
        CloseHandle(m_hPhysicalContext);
        m_pFactory->Release();
    }

    /// <summary>
    ///     Returns a process unique identifier for the thread proxy.
    /// </summary>
    unsigned int ThreadProxy::GetId() const
    {
        return m_id;
    }

    /// <summary>
    ///     Sets the priority of the underlying thread.
    /// </summary>
    /// <param name="priority">
    ///     The new priority value for the thread.
    /// </param>
    void ThreadProxy::SetPriority(int priority)
    {
        m_threadPriority = priority;

        if (SetThreadPriority(m_hPhysicalContext, m_threadPriority) == 0)
            throw scheduler_resource_allocation_error(HRESULT_FROM_WIN32(GetLastError()));
    }

    /// <summary>
    ///     Blocks the thread proxy until is is resumed via ResumeExecution or a different thread proxy switching to it.
    /// </summary>
    void ThreadProxy::SuspendExecution()
    {
        ASSERT(m_fBlocked == FALSE);
        InterlockedExchange(&m_fBlocked, TRUE);

        WaitForSingleObject(m_hBlock, INFINITE);

        ASSERT(m_fBlocked == TRUE);
        InterlockedExchange(&m_fBlocked, FALSE);
    }

    /// <summary>
    ///     Resumes execution of a thread proxy.
    /// </summary>
    void ThreadProxy::ResumeExecution()
    {
        SetEvent(m_hBlock);
    }

    /// <summary>
    ///     Cancels the thread proxy causing the underlying thread to exit.
    /// </summary>
    void ThreadProxy::Cancel()
    {
        ASSERT(m_fCanceled == false);
        m_fCanceled = true;
        ResumeExecution();
    }

    /// <summary>
    ///     Spins until the 'this' thread proxy is in a firmly blocked state. 
    /// </summary>
    /// <remarks>
    ///     This implements a sort of barrier. At certain points during execution, it is essential to wait until a thread proxy
    ///     has set the flag inidicating it is blocked, in order to preserve correct behavior. One example is if there is a race
    ///     between block and unblock for the same proxy, i.e. if a thread proxy is trying to block at the same time a different
    ///     context is trying to unblock it.
    /// </remarks>
    void ThreadProxy::SpinUntilBlocked()
    {
        if (m_fBlocked == FALSE)
        {
            _SpinWaitBackoffNone spinWait(_Sleep0);

            do
            {
                spinWait._SpinOnce();

            } while (m_fBlocked == FALSE);
        }
        ASSERT(m_fBlocked == TRUE);
    }

    /// <summary>
    ///     Thread start routine for proxies.
    /// </summary>
    DWORD CALLBACK ThreadProxy::ThreadProxyMain(LPVOID lpParameter)
    {
        ThreadProxy* pThreadProxy = reinterpret_cast<ThreadProxy*> (lpParameter);

        // To start the dispatch loop cleanly, the context must block until it is switched to, or resumed..
        WaitForSingleObject(pThreadProxy->m_hBlock, INFINITE);
        InterlockedExchange(&pThreadProxy->m_fBlocked, FALSE);

        pThreadProxy->Dispatch();

        ASSERT(pThreadProxy->m_fCanceled);
        // Thread proxy needs to be deleted after it is canceled and it returns from the dispatch loop.
        delete pThreadProxy;
        FreeLibraryAndDestroyThread(0);
        return 0;
    }

} // namespace details
} // namespace Concurrency
