//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------

#include "Backend.h"

#if ENABLE_OOP_NATIVE_CODEGEN
#include "JITServer/JITServer.h"
#include "PageAllocatorPool.h"

CriticalSection PageAllocatorPool::cs;
PageAllocatorPool* PageAllocatorPool::Instance = nullptr;

PageAllocatorPool::PageAllocatorPool()
    :pageAllocators(&NoThrowHeapAllocator::Instance),
    idleCleanupTimer(NULL),
    activePageAllocatorCount(0)
{
    idleCleanupTimer = CreateThreadpoolTimer(IdleCleanupRoutine, NULL, NULL);
}

PageAllocatorPool::~PageAllocatorPool()
{
    AutoCriticalSection autoCS(&cs);
    RemoveAll();
}

void PageAllocatorPool::Initialize()
{
    Instance = HeapNewNoThrow(PageAllocatorPool);
    if (Instance == nullptr)
    {
        Js::Throw::FatalInternalError();
    }
}

void PageAllocatorPool::Shutdown()
{
    AutoCriticalSection autoCS(&cs);
    Assert(Instance);
    if (Instance)
    {
        PageAllocatorPool* localInstance = Instance;
        Instance = nullptr;
        
        if (localInstance->idleCleanupTimer)
        {
            CloseThreadpoolTimer(localInstance->idleCleanupTimer);
        }
        HeapDelete(localInstance);
    }
}

void PageAllocatorPool::RemoveAll()
{
    Assert(cs.IsLocked());
    while (!pageAllocators.Empty())
    {
        HeapDelete(pageAllocators.Pop());
    }
}

unsigned int PageAllocatorPool::GetInactivePageAllocatorCount()
{
    AutoCriticalSection autoCS(&cs);
    return pageAllocators.Count();
}

PageAllocator* PageAllocatorPool::GetPageAllocator()
{
    AutoCriticalSection autoCS(&cs);
    PageAllocator* pageAllocator = nullptr;
    if (pageAllocators.Count() > 0)
    {
        // TODO: OOP JIT, select the page allocator with right count of free pages
        // base on some heuristic
        pageAllocator = this->pageAllocators.Pop();
    }
    else
    {
        pageAllocator = HeapNew(PageAllocator, nullptr, Js::Configuration::Global.flags, PageAllocatorType_BGJIT,
            AutoSystemInfo::Data.IsLowMemoryProcess() ? PageAllocator::DefaultLowMaxFreePageCount : PageAllocator::DefaultMaxFreePageCount);
    }

    activePageAllocatorCount++;
    return pageAllocator;

}

void PageAllocatorPool::ReturnPageAllocator(PageAllocator* pageAllocator)
{
    AutoCriticalSection autoCS(&cs);
    if (!this->pageAllocators.PrependNoThrow(&NoThrowHeapAllocator::Instance, pageAllocator))
    {
        HeapDelete(pageAllocator);
    }

    activePageAllocatorCount--;
    if (activePageAllocatorCount == 0 || GetInactivePageAllocatorCount() > (uint)Js::Configuration::Global.flags.JITServerMaxInactivePageAllocatorCount)
    {
        PageAllocatorPool::IdleCleanup();
    }
}

void PageAllocatorPool::IdleCleanup()
{
    AutoCriticalSection autoCS(&cs);
    if (Instance
#ifdef PROFILE_EXEC
        // profiler keeps persistent reference to page allocators, so we can't cleanup on idle even if the allocators aren't currently being used for JIT
        && !Js::Configuration::Global.flags.IsEnabled(Js::ProfileFlag)
#endif
        )
    {
        LARGE_INTEGER liDueTime;
        liDueTime.QuadPart = Js::Configuration::Global.flags.JITServerIdleTimeout * -10000LL; // wait for JITServerIdleTimeout milliseconds to do the cleanup

        if (Instance->idleCleanupTimer)
        {
            // Setting the timer cancels the previous timer, if any.
            SetThreadpoolTimer(Instance->idleCleanupTimer, (PFILETIME)&liDueTime, 0, 0);
        }
        else
        {
            Instance->RemoveAll();
        }
    }
}

VOID CALLBACK PageAllocatorPool::IdleCleanupRoutine(
    _Inout_     PTP_CALLBACK_INSTANCE,
    _Inout_opt_ PVOID,
    _Inout_     PTP_TIMER)
{
    AutoCriticalSection autoCS(&cs);
    if (Instance)
    {
        // TODO: OOP JIT, use better stragtegy to do the cleanup, like do not remove all,
        // instead keep couple inactivate page allocator for next calls
        Instance->RemoveAll();
    }
}
#endif
