Skip to content

Commit d400a64

Browse files
committed
[MERGE #5326 @atulkatti] MSFT:17677205 IdleDecommit hang. Back-off from decommit if UI thread wants to enter IdleDecommit again.
Merge pull request #5326 from atulkatti:Bug17677205.IdleDecommitHang.2 We suspect that this may be a problem only under AppVerifier where decommit maybe running slower than usual. The fix should have no impact under normal conditions.
2 parents 2ed7144 + c38739f commit d400a64

File tree

5 files changed

+119
-14
lines changed

5 files changed

+119
-14
lines changed

lib/Common/Memory/AllocatorTelemetryStats.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,29 @@ struct AllocatorDecommitStats
1111
{
1212
Js::Tick lastLeaveDecommitRegion;
1313
Js::TickDelta maxDeltaBetweenDecommitRegionLeaveAndDecommit;
14+
15+
// The following values correspond to when we last entered the critical section for Enter/Leave IdleDecommit and
16+
// how long we waited to enter the critical section if we had to wait.
17+
Js::Tick lastEnterLeaveIdleDecommitTick;
18+
Js::TickDelta lastEnterLeaveIdleDecommitCSWaitTime;
19+
Js::TickDelta maxEnterLeaveIdleDecommitCSWaitTime;
20+
Js::TickDelta totalEnterLeaveIdleDecommitCSWaitTime;
21+
1422
int64 numDecommitCalls;
1523
int64 numPagesDecommitted;
1624
int64 numFreePageCount;
1725

1826
AllocatorDecommitStats() :
1927
lastLeaveDecommitRegion(),
2028
maxDeltaBetweenDecommitRegionLeaveAndDecommit(0),
29+
lastEnterLeaveIdleDecommitTick(),
30+
lastEnterLeaveIdleDecommitCSWaitTime(0),
31+
maxEnterLeaveIdleDecommitCSWaitTime(0),
32+
totalEnterLeaveIdleDecommitCSWaitTime(0),
2133
numDecommitCalls(0),
2234
numPagesDecommitted(0),
2335
numFreePageCount(0)
2436
{}
25-
2637
};
2738

2839
struct AllocatorSizes

lib/Common/Memory/IdleDecommitPageAllocator.cpp

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,26 @@ IdleDecommitPageAllocator::EnterIdleDecommit()
4444
return;
4545
}
4646
#ifdef IDLE_DECOMMIT_ENABLED
47-
cs.Enter();
47+
if (!cs.TryEnter())
48+
{
49+
AutoResetWaitingToEnterIdleDecommitFlag autoResetWaitingToEnterIdleDecommitFlag(this);
50+
51+
#ifdef ENABLE_BASIC_TELEMETRY
52+
AllocatorDecommitStats * decommitStats = this->GetDecommitStats();
53+
decommitStats->lastEnterLeaveIdleDecommitTick = Js::Tick::Now();
54+
#endif
55+
56+
cs.Enter();
57+
58+
#ifdef ENABLE_BASIC_TELEMETRY
59+
decommitStats->lastEnterLeaveIdleDecommitCSWaitTime = Js::Tick::Now() - decommitStats->lastEnterLeaveIdleDecommitTick;
60+
decommitStats->totalEnterLeaveIdleDecommitCSWaitTime += decommitStats->lastEnterLeaveIdleDecommitCSWaitTime;
61+
if (decommitStats->lastEnterLeaveIdleDecommitCSWaitTime > decommitStats->maxEnterLeaveIdleDecommitCSWaitTime)
62+
{
63+
decommitStats->maxEnterLeaveIdleDecommitCSWaitTime = decommitStats->lastEnterLeaveIdleDecommitCSWaitTime;
64+
}
65+
#endif
66+
}
4867

4968
this->isUsed = false;
5069
this->hadDecommitTimer = hasDecommitTimer;
@@ -80,7 +99,8 @@ IdleDecommitPageAllocator::LeaveIdleDecommit(bool allowTimer)
8099
Assert(this->maxFreePageCount == maxIdleDecommitFreePageCount);
81100

82101
#ifdef ENABLE_BASIC_TELEMETRY
83-
this->GetDecommitStats()->lastLeaveDecommitRegion = Js::Tick::Now();
102+
AllocatorDecommitStats * decommitStats = this->GetDecommitStats();
103+
decommitStats->lastLeaveDecommitRegion = Js::Tick::Now();
84104
#endif
85105

86106
#ifdef IDLE_DECOMMIT_ENABLED
@@ -96,7 +116,25 @@ IdleDecommitPageAllocator::LeaveIdleDecommit(bool allowTimer)
96116
#ifdef IDLE_DECOMMIT_ENABLED
97117
if (allowTimer)
98118
{
99-
cs.Enter();
119+
if (!cs.TryEnter())
120+
{
121+
AutoResetWaitingToEnterIdleDecommitFlag autoResetWaitingToEnterIdleDecommitFlag(this);
122+
123+
#ifdef ENABLE_BASIC_TELEMETRY
124+
decommitStats->lastEnterLeaveIdleDecommitTick = Js::Tick::Now();
125+
#endif
126+
127+
cs.Enter();
128+
129+
#ifdef ENABLE_BASIC_TELEMETRY
130+
decommitStats->lastEnterLeaveIdleDecommitCSWaitTime = Js::Tick::Now() - decommitStats->lastEnterLeaveIdleDecommitTick;
131+
decommitStats->totalEnterLeaveIdleDecommitCSWaitTime += decommitStats->lastEnterLeaveIdleDecommitCSWaitTime;
132+
if (decommitStats->lastEnterLeaveIdleDecommitCSWaitTime > decommitStats->maxEnterLeaveIdleDecommitCSWaitTime)
133+
{
134+
decommitStats->maxEnterLeaveIdleDecommitCSWaitTime = decommitStats->lastEnterLeaveIdleDecommitCSWaitTime;
135+
}
136+
#endif
137+
}
100138

101139
PAGE_ALLOC_VERBOSE_TRACE(_u("LeaveIdleDecommit"));
102140
Assert(maxIdleDecommitFreePageCount != maxNonIdleDecommitFreePageCount);

lib/Common/Memory/IdleDecommitPageAllocator.h

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,23 @@ class IdleDecommitPageAllocator : public PageAllocator
4141
#endif
4242

4343
private:
44+
class AutoResetWaitingToEnterIdleDecommitFlag
45+
{
46+
public:
47+
AutoResetWaitingToEnterIdleDecommitFlag(IdleDecommitPageAllocator * pageAllocator)
48+
{
49+
this->pageAllocator = pageAllocator;
50+
pageAllocator->waitingToEnterIdleDecommit = true;
51+
}
52+
53+
~AutoResetWaitingToEnterIdleDecommitFlag()
54+
{
55+
pageAllocator->waitingToEnterIdleDecommit = false;
56+
}
57+
58+
private:
59+
IdleDecommitPageAllocator * pageAllocator;
60+
};
4461

4562
#ifdef IDLE_DECOMMIT_ENABLED
4663
#if DBG_DUMP
@@ -83,5 +100,4 @@ class IdleDecommitPageAllocator : public PageAllocator
83100
}
84101
#endif
85102
};
86-
87103
}

lib/Common/Memory/PageAllocator.cpp

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,10 @@ PageAllocatorBase<TVirtualAlloc, TSegment, TPageSegment>::PageAllocatorBase(Allo
697697
#endif
698698
minFreePageCount(0),
699699
isUsed(false),
700+
waitingToEnterIdleDecommit(false),
701+
#if DBG
702+
idleDecommitBackOffCount(0),
703+
#endif
700704
idleDecommitEnterCount(1),
701705
isClosed(false),
702706
stopAllocationOnOutOfMemory(stopAllocationOnOutOfMemory),
@@ -1971,14 +1975,22 @@ PageAllocatorBase<TVirtualAlloc, TSegment, TPageSegment>::DecommitNow(bool all)
19711975
int numZeroPagesFreed = 0;
19721976

19731977
// There might be queued zero pages. Drain them first
1974-
1978+
bool zeroPageQueueEmpty = false;
19751979
while (true)
19761980
{
19771981
FreePageEntry * freePageEntry = PopPendingZeroPage();
19781982
if (freePageEntry == nullptr)
19791983
{
1984+
zeroPageQueueEmpty = true;
19801985
break;
19811986
}
1987+
1988+
// Back-off from decommit if we are trying to enter IdleDecommit again.
1989+
if (this->waitingToEnterIdleDecommit)
1990+
{
1991+
break;
1992+
}
1993+
19821994
PAGE_ALLOC_TRACE_AND_STATS_0(_u("Freeing page from zero queue"));
19831995
TPageSegment * segment = freePageEntry->segment;
19841996
uint pageCount = freePageEntry->pageCount;
@@ -2021,6 +2033,7 @@ PageAllocatorBase<TVirtualAlloc, TSegment, TPageSegment>::DecommitNow(bool all)
20212033

20222034
// Take the lock to make sure the recycler thread has finished zeroing out the pages after
20232035
// we drained the queue
2036+
if(zeroPageQueueEmpty)
20242037
{
20252038
AutoCriticalSection autoCS(&backgroundPageQueue->backgroundPageQueueCriticalSection);
20262039
this->hasZeroQueuedPages = false;
@@ -2099,12 +2112,18 @@ PageAllocatorBase<TVirtualAlloc, TSegment, TPageSegment>::DecommitNow(bool all)
20992112

21002113
}
21012114
pageToDecommit -= pageDecommitted;
2115+
2116+
// Back-off from decommit if we are trying to enter IdleDecommit again.
2117+
if (this->waitingToEnterIdleDecommit)
2118+
{
2119+
break;
2120+
}
21022121
}
21032122
}
21042123

2105-
// decommit pages that are empty
2106-
2107-
while (pageToDecommit > 0 && !emptySegments.Empty())
2124+
// decommit pages that are empty.
2125+
// back-off from decommit if we are trying to enter IdleDecommit again.
2126+
while (!this->waitingToEnterIdleDecommit && pageToDecommit > 0 && !emptySegments.Empty())
21082127
{
21092128
if (pageToDecommit >= maxAllocPageCount)
21102129
{
@@ -2131,6 +2150,7 @@ PageAllocatorBase<TVirtualAlloc, TSegment, TPageSegment>::DecommitNow(bool all)
21312150
}
21322151
}
21332152

2153+
if(!this->waitingToEnterIdleDecommit)
21342154
{
21352155
typename DListBase<TPageSegment>::EditingIterator i(&segments);
21362156

@@ -2146,23 +2166,34 @@ PageAllocatorBase<TVirtualAlloc, TSegment, TPageSegment>::DecommitNow(bool all)
21462166
i.MoveCurrentTo(&decommitSegments);
21472167
pageToDecommit -= pageDecommitted;
21482168

2169+
// Back-off from decommit if we are trying to enter IdleDecommit again.
2170+
if (this->waitingToEnterIdleDecommit)
2171+
{
2172+
break;
2173+
}
21492174
}
21502175
}
21512176

2152-
Assert(pageToDecommit == 0);
2153-
2177+
Assert(pageToDecommit == 0 || this->waitingToEnterIdleDecommit);
2178+
#if DBG
2179+
if (pageToDecommit != 0 && this->waitingToEnterIdleDecommit)
2180+
{
2181+
this->idleDecommitBackOffCount++;
2182+
}
2183+
#endif
21542184

21552185
#if DBG_DUMP
2156-
Assert(this->freePageCount == newFreePageCount + decommitCount);
2186+
Assert(this->freePageCount == newFreePageCount + decommitCount + pageToDecommit);
21572187
#endif
21582188

2159-
this->freePageCount = newFreePageCount;
2189+
// If we had to back-off from decommiting then we may still have some free pages left to decommit.
2190+
this->freePageCount = newFreePageCount + pageToDecommit;
21602191

21612192
#ifdef ENABLE_BASIC_TELEMETRY
21622193
if (this->decommitStats != nullptr)
21632194
{
21642195
this->decommitStats->numPagesDecommitted += decommitCount;
2165-
this->decommitStats->numFreePageCount += newFreePageCount;
2196+
this->decommitStats->numFreePageCount += newFreePageCount + pageToDecommit;
21662197
}
21672198
#endif
21682199

lib/Common/Memory/PageAllocator.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -858,6 +858,15 @@ class PageAllocatorBase: public PageAllocatorBaseCommon
858858

859859
// Idle Decommit
860860
bool isUsed;
861+
// A flag to indicate we are trying to enter IdleDecommit again and back-off from decommit in DecommitNow. This is to prevent
862+
// blocking UI thread for too long. We have seen hangs under AppVerifier and believe this may be due to the decommit being slower
863+
// under AppVerifier. This shouldn't be a problem otherwise.
864+
bool waitingToEnterIdleDecommit;
865+
866+
#if DBG
867+
uint idleDecommitBackOffCount;
868+
#endif
869+
861870
size_t minFreePageCount;
862871
uint idleDecommitEnterCount;
863872

0 commit comments

Comments
 (0)