blob: fd16df95194b980a7f1b9193686aabe47082f1a3 [file]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#include "RuntimeBasePch.h"
#include "Base/ThreadServiceWrapperBase.h"
ThreadServiceWrapperBase::ThreadServiceWrapperBase() :
threadContext(nullptr),
needIdleCollect(false),
inIdleCollect(false),
hasScheduledIdleCollect(false),
shouldScheduleIdleCollectOnExitIdle(false),
forceIdleCollectOnce(false)
{
}
bool ThreadServiceWrapperBase::Initialize(ThreadContext *newThreadContext)
{
if (newThreadContext == nullptr)
{
return false;
}
threadContext = newThreadContext;
threadContext->SetThreadServiceWrapper(this);
return true;
}
void ThreadServiceWrapperBase::Shutdown()
{
if (hasScheduledIdleCollect)
{
#if DBG
// Fake the inIdleCollect to get pass asserts in FinishIdleCollect
inIdleCollect = true;
#endif
FinishIdleCollect(FinishReason::FinishReasonNormal);
}
}
bool ThreadServiceWrapperBase::ScheduleIdleCollect(uint ticks, bool scheduleAsTask)
{
Assert(!threadContext->IsInScript());
// We should schedule have called this in one of two cases:
// 1) Either needIdleCollect is true- in which case, we should schedule one
// 2) Or ScheduleNextCollectionOnExit was called when needIdleCollect was true, but we didn't schedule
// one because we were at the time in one. Later, as we unwound, we might have set needIdleCollect to false
// but because we had noted that we needed to schedule a collect, we would end up coming into this function
// so allow for that
Assert(needIdleCollect || shouldScheduleIdleCollectOnExitIdle || threadContext->GetRecycler()->CollectionInProgress());
if (!CanScheduleIdleCollect())
{
return false;
}
if (hasScheduledIdleCollect)
{
return true;
}
if (OnScheduleIdleCollect(ticks, scheduleAsTask))
{
JS_ETW(EventWriteJSCRIPT_GC_IDLE_START(this));
IDLE_COLLECT_VERBOSE_TRACE(_u("ScheduledIdleCollect- Set hasScheduledIdleCollect\n"));
hasScheduledIdleCollect = true;
return true;
}
else
{
IDLE_COLLECT_TRACE(_u("Idle timer setup failed\n"));
FinishIdleCollect(FinishReason::FinishReasonIdleTimerSetupFailed);
return false;
}
}
bool ThreadServiceWrapperBase::IdleCollect()
{
Assert(hasScheduledIdleCollect);
IDLE_COLLECT_VERBOSE_TRACE(_u("IdleCollect- reset hasScheduledIdleCollect\n"));
hasScheduledIdleCollect = false;
// Don't do anything and kill the timer if we are called recursively or if we are in script
if (inIdleCollect || threadContext->IsInScript())
{
FinishIdleCollect(FinishReason::FinishReasonNormal);
return hasScheduledIdleCollect;
}
// If during idle collect we determine that we need to schedule another
// idle collect, this gets flipped to true
shouldScheduleIdleCollectOnExitIdle = false;
AutoBooleanToggle autoInIdleCollect(&inIdleCollect);
Recycler* recycler = threadContext->GetRecycler();
#if ENABLE_CONCURRENT_GC
// Finish concurrent on timer heart beat if needed
// We wouldn't try to finish if we need to schedule
// an idle task to finish the collection
if (this->ShouldFinishConcurrentCollectOnIdleCallback() && recycler->FinishConcurrent<FinishConcurrentOnIdle>())
{
IDLE_COLLECT_TRACE(_u("Idle callback: finish concurrent\n"));
JS_ETW(EventWriteJSCRIPT_GC_IDLE_CALLBACK_FINISH(this));
}
#endif
while (true)
{
// If a GC is still happening, just wait for the next heart beat
if (recycler->CollectionInProgress())
{
ScheduleIdleCollect(IdleTicks, true /* schedule as task */);
break;
}
// If there no more need of idle collect, then cancel the timer
if (!needIdleCollect)
{
FinishIdleCollect(FinishReason::FinishReasonNormal);
break;
}
int timeDiff = tickCountNextIdleCollection - GetTickCount();
// See if we pass the time for the next scheduled Idle GC
if (timeDiff > 0)
{
// Not time yet, wait for the next heart beat
ScheduleIdleCollect(IdleTicks, false /* not schedule as task */);
IDLE_COLLECT_TRACE(_u("Idle callback: nop until next collection: %d\n"), timeDiff);
break;
}
// activate an idle collection
IDLE_COLLECT_TRACE(_u("Idle callback: collection: %d\n"), timeDiff);
JS_ETW(EventWriteJSCRIPT_GC_IDLE_CALLBACK_NEWCOLLECT(this));
needIdleCollect = false;
recycler->CollectNow<CollectOnScriptIdle>();
}
if (shouldScheduleIdleCollectOnExitIdle)
{
ScheduleIdleCollect(IdleTicks, false /* not schedule as task */);
}
return hasScheduledIdleCollect;
}
void ThreadServiceWrapperBase::FinishIdleCollect(ThreadServiceWrapperBase::FinishReason reason)
{
Assert(reason == FinishReason::FinishReasonIdleTimerSetupFailed ||
reason == FinishReason::FinishReasonTaskComplete ||
inIdleCollect || threadContext->IsInScript() || !threadContext->GetRecycler()->CollectionInProgress());
IDLE_COLLECT_VERBOSE_TRACE(_u("FinishIdleCollect- Reset hasScheduledIdleCollect\n"));
hasScheduledIdleCollect = false;
needIdleCollect = false;
OnFinishIdleCollect();
IDLE_COLLECT_TRACE(_u("Idle timer finished\n"));
JS_ETW(EventWriteJSCRIPT_GC_IDLE_FINISHED(this));
}
bool ThreadServiceWrapperBase::ScheduleNextCollectOnExit()
{
Assert(!threadContext->IsInScript());
Assert(!needIdleCollect || hasScheduledIdleCollect);
Recycler* recycler = threadContext->GetRecycler();
#if ENABLE_CONCURRENT_GC
recycler->FinishConcurrent<FinishConcurrentOnExitScript>();
#endif
#ifdef RECYCLER_TRACE
bool oldNeedIdleCollect = needIdleCollect;
if (forceIdleCollectOnce)
{
IDLE_COLLECT_VERBOSE_TRACE(_u("Need to force one idle collection\n"));
}
#endif
needIdleCollect = forceIdleCollectOnce || recycler->ShouldIdleCollectOnExit();
if (needIdleCollect)
{
// Set up when we will do the idle decommit
tickCountNextIdleCollection = GetTickCount() + IdleTicks;
IDLE_COLLECT_VERBOSE_TRACE(_u("Idle on exit collect %s: %d\n"), (oldNeedIdleCollect ? _u("rescheduled") : _u("scheduled")),
tickCountNextIdleCollection - GetTickCount());
JS_ETW(EventWriteJSCRIPT_GC_IDLE_SCHEDULED(this));
}
else
{
IDLE_COLLECT_VERBOSE_TRACE(_u("Idle on exit collect %s\n"), oldNeedIdleCollect ? _u("cancelled") : _u("not scheduled"));
if (!recycler->CollectionInProgress())
{
// We collected and finished, no need to ensure the idle collect call back.
return true;
}
IDLE_COLLECT_VERBOSE_TRACE(_u("Idle on exit collect %s\n"), hasScheduledIdleCollect || oldNeedIdleCollect ? _u("reschedule finish") : _u("schedule finish"));
}
// Don't schedule the call back if we are already in idle call back, as we don't do anything on recursive call anyways
// IdleCollect will schedule one if necessary
if (inIdleCollect)
{
shouldScheduleIdleCollectOnExitIdle = true;
return true;
}
else
{
return ScheduleIdleCollect(IdleTicks, false /* not schedule as task */);
}
}
void ThreadServiceWrapperBase::ClearForceOneIdleCollection()
{
IDLE_COLLECT_VERBOSE_TRACE(_u("Clearing force idle collect flag\n"));
this->forceIdleCollectOnce = false;
}
void ThreadServiceWrapperBase::SetForceOneIdleCollection()
{
IDLE_COLLECT_VERBOSE_TRACE(_u("Setting force idle collect flag\n"));
this->forceIdleCollectOnce = true;
}
void ThreadServiceWrapperBase::ScheduleFinishConcurrent()
{
Assert(!threadContext->IsInScript());
Assert(threadContext->GetRecycler()->CollectionInProgress());
if (!this->inIdleCollect)
{
IDLE_COLLECT_VERBOSE_TRACE(_u("Idle collect %s\n"), needIdleCollect ? _u("reschedule finish") : _u("scheduled finish"));
this->needIdleCollect = false;
ScheduleIdleCollect(IdleFinishTicks, true /* schedule as task */);
}
}