Skip to content

Commit 6225048

Browse files
committed
decommit unusable page(s) in medium heap blocks (bug 5822302)
in medium heap blocks, if the object size is bigger than 1 page, it can cause the whole last 1~3 page(s) never been allocated. when such case is hit, decommit those pages to save memory as well as capture corruption. when returning the pages back to page allocator, we should commit those pages again for reuse, in case of OOM here, just decommit all pages in the heap block and let the page allocator to manage the decommitted pages. in rescan code, asserting that we never scan those unallocatable pages
1 parent 66274cf commit 6225048

File tree

7 files changed

+114
-11
lines changed

7 files changed

+114
-11
lines changed

lib/Common/Core/FaultInjection.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,10 @@ namespace Js
156156
if(Js::FaultInjection::Global.ShouldInjectFault(Js::FaultInjection::Global.NoThrow, name, size)) \
157157
return NULL;
158158

159+
#define FAULTINJECT_MEMORY_NOTHROW_RET(name, size, ret) \
160+
if(Js::FaultInjection::Global.ShouldInjectFault(Js::FaultInjection::Global.NoThrow, name, size)) \
161+
return ret;
162+
159163
#define FAULTINJECT_MEMORY_THROW(name, size) \
160164
if(Js::FaultInjection::Global.ShouldInjectFault(Js::FaultInjection::Global.Throw, name, size)) \
161165
Js::Throw::OutOfMemory();
@@ -200,6 +204,7 @@ namespace Js
200204
#define IS_FAULTINJECT_NO_THROW_ON false
201205

202206
#define FAULTINJECT_MEMORY_NOTHROW(name, size)
207+
#define FAULTINJECT_MEMORY_NOTHROW_RET(name, size, ret)
203208
#define FAULTINJECT_MEMORY_THROW(name, size)
204209
#define FAULTINJECT_MEMORY_MARK_THROW(name, size)
205210
#define FAULTINJECT_MEMORY_MARK_NOTHROW(name, size)

lib/Common/Memory/HeapBlock.cpp

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,8 @@ SmallHeapBlockT<TBlockAttributes>::SetPage(__in_ecount_pagesize char * baseAddre
313313
MemoryBarrier();
314314
#endif
315315

316+
this->DecommitUnusablePages();
317+
316318
return TRUE;
317319
}
318320

@@ -334,14 +336,21 @@ SmallHeapBlockT<TBlockAttributes>::ReleasePages(Recycler * recycler)
334336
char* address = this->address;
335337

336338
#ifdef RECYCLER_FREE_MEM_FILL
337-
memset(address, DbgMemFill, AutoSystemInfo::PageSize * this->GetPageCount());
339+
memset(address, DbgMemFill, AutoSystemInfo::PageSize * (this->GetPageCount()-this->GetUnusablePageCount()));
338340
#endif
339341

340-
this->GetPageAllocator(recycler)->ReleasePages(address, this->GetPageCount(), this->GetPageSegment());
342+
if (!this->RecommitUnusablePages())
343+
{
344+
this->GetPageAllocator(recycler)->PartialDecommitPages(address, this->GetPageCount(), address,
345+
this->GetPageCount() - this->GetUnusablePageCount(), this->segment);
346+
}
347+
else
348+
{
349+
this->GetPageAllocator(recycler)->ReleasePages(address, this->GetPageCount(), this->GetPageSegment());
350+
}
341351

342352
this->segment = nullptr;
343353
this->address = nullptr;
344-
345354
}
346355

347356
template <class TBlockAttributes>
@@ -351,7 +360,15 @@ SmallHeapBlockT<TBlockAttributes>::BackgroundReleasePagesSweep(Recycler* recycle
351360
recycler->heapBlockMap.ClearHeapBlock(address, this->GetPageCount());
352361
char* address = this->address;
353362

354-
this->GetPageAllocator(recycler)->BackgroundReleasePages(address, this->GetPageCount(), this->GetPageSegment());
363+
if (!this->RecommitUnusablePages())
364+
{
365+
this->GetPageAllocator(recycler)->PartialDecommitPages(address, this->GetPageCount(), address,
366+
this->GetPageCount() - this->GetUnusablePageCount(), this->segment);
367+
}
368+
else
369+
{
370+
this->GetPageAllocator(recycler)->BackgroundReleasePages(address, this->GetPageCount(), this->GetPageSegment());
371+
}
355372

356373
this->address = nullptr;
357374
this->segment = nullptr;

lib/Common/Memory/HeapBlock.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ template <class TBlockAttributes> class SmallFinalizableWithBarrierHeapBlockT;
212212
class RecyclerHeapObjectInfo;
213213
class HeapBlock
214214
{
215+
friend MediumAllocationBlockAttributes;
215216
public:
216217
enum HeapBlockType : byte
217218
{
@@ -444,6 +445,20 @@ class SmallHeapBlockT : public HeapBlock
444445
public:
445446
~SmallHeapBlockT();
446447

448+
void DecommitUnusablePages()
449+
{
450+
TBlockAttributes::DecommitUnusablePages(this);
451+
}
452+
453+
BOOL RecommitUnusablePages()
454+
{
455+
return TBlockAttributes::RecommitUnusablePages(this);
456+
}
457+
458+
uint GetUnusablePageCount()
459+
{
460+
return TBlockAttributes::GetUnusablePageCount(this->objectSize);
461+
}
447462

448463
#ifdef RECYCLER_WRITE_BARRIER
449464
bool IsWithBarrier() const;

lib/Common/Memory/HeapBlockMap.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1067,7 +1067,8 @@ HeapBlockMap32::RescanHeapBlockOnOOM(TBlockType* heapBlock, char* pageAddress, H
10671067
// The following assert makes sure that this method is called only once per heap block
10681068
Assert(blockStartAddress == pageAddress);
10691069

1070-
for (int i = 0; i < TBlockType::HeapBlockAttributes::PageCount; i++)
1070+
int inUsePageCount = heapBlock->GetPageCount() - heapBlock->GetUnusablePageCount();
1071+
for (int i = 0; i < inUsePageCount; i++)
10711072
{
10721073
char* pageAddressToScan = blockStartAddress + (i * AutoSystemInfo::PageSize);
10731074

lib/Common/Memory/HeapConstants.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ class HeapConstants
2626
static const uint MediumBucketCount = (MaxMediumObjectSize - MaxSmallObjectSize) / MediumObjectGranularity;
2727
#endif
2828
};
29+
namespace Memory {
30+
class HeapBlock;
31+
}
2932

3033
///
3134
/// BlockAttributes are used to determine the allocation characteristics of a heap block
@@ -61,6 +64,9 @@ class SmallAllocationBlockAttributes
6164
static const bool IsLargeBlock = false;
6265

6366
static BOOL IsAlignedObjectSize(size_t sizeCat);
67+
static uint GetUnusablePageCount(size_t sizeCat);
68+
static void DecommitUnusablePages(Memory::HeapBlock* heapBlock);
69+
static BOOL RecommitUnusablePages(Memory::HeapBlock* heapBlock);
6470
};
6571

6672
class MediumAllocationBlockAttributes
@@ -83,6 +89,9 @@ class MediumAllocationBlockAttributes
8389
static const bool IsLargeBlock = false;
8490

8591
static BOOL IsAlignedObjectSize(size_t sizeCat);
92+
static uint GetUnusablePageCount(size_t sizeCat);
93+
static void DecommitUnusablePages(Memory::HeapBlock* heapBlock);
94+
static BOOL RecommitUnusablePages(Memory::HeapBlock* heapBlock);
8695
};
8796

8897
class LargeAllocationBlockAttributes

lib/Common/Memory/HeapInfo.cpp

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -505,10 +505,11 @@ HeapInfo::Initialize(Recycler * recycler
505505
if (pageheapmode == PageHeapMode::PageHeapModeOff)
506506
{
507507
#ifdef ENABLE_DEBUG_CONFIG_OPTIONS
508-
isPageHeapEnabled = recycler->GetRecyclerFlagsTable().PageHeap != PageHeapMode::PageHeapModeOff;
509-
pageheapmode = (PageHeapMode)recycler->GetRecyclerFlagsTable().PageHeap;
510-
blockTypeFilter = (PageHeapBlockTypeFilter)recycler->GetRecyclerFlagsTable().PageHeapBlockType;
511-
pBucketNumberRange = &recycler->GetRecyclerFlagsTable().PageHeapBucketNumber;
508+
auto& flags = recycler->GetRecyclerFlagsTable();
509+
isPageHeapEnabled = flags.PageHeap != PageHeapMode::PageHeapModeOff;
510+
pageheapmode = (PageHeapMode)flags.PageHeap;
511+
blockTypeFilter = (PageHeapBlockTypeFilter)flags.PageHeapBlockType;
512+
pBucketNumberRange = &flags.PageHeapBucketNumber;
512513

513514
#else
514515
// @TODO in free build, use environment var or other way to enable page heap
@@ -1767,12 +1768,60 @@ BOOL SmallAllocationBlockAttributes::IsAlignedObjectSize(size_t sizeCat)
17671768
{
17681769
return HeapInfo::IsAlignedSmallObjectSize(sizeCat);
17691770
}
1771+
/* static */
1772+
uint SmallAllocationBlockAttributes::GetUnusablePageCount(size_t sizeCat)
1773+
{
1774+
UNREFERENCED_PARAMETER(sizeCat);
1775+
return 0;
1776+
}
1777+
/* static */
1778+
void SmallAllocationBlockAttributes::DecommitUnusablePages(HeapBlock* heapBlock)
1779+
{
1780+
UNREFERENCED_PARAMETER(heapBlock);
1781+
}
1782+
/* static */
1783+
BOOL SmallAllocationBlockAttributes::RecommitUnusablePages(HeapBlock* heapBlock)
1784+
{
1785+
UNREFERENCED_PARAMETER(heapBlock);
1786+
return TRUE;
1787+
}
17701788

17711789
/* static */
17721790
BOOL MediumAllocationBlockAttributes::IsAlignedObjectSize(size_t sizeCat)
17731791
{
17741792
return HeapInfo::IsAlignedMediumObjectSize(sizeCat);
17751793
}
1794+
/* static */
1795+
uint MediumAllocationBlockAttributes::GetUnusablePageCount(size_t sizeCat)
1796+
{
1797+
return ((MediumAllocationBlockAttributes::PageCount*AutoSystemInfo::PageSize) % sizeCat) / AutoSystemInfo::PageSize;
1798+
}
1799+
/* static */
1800+
void MediumAllocationBlockAttributes::DecommitUnusablePages(HeapBlock* heapBlock)
1801+
{
1802+
size_t count = MediumAllocationBlockAttributes::GetUnusablePageCount(heapBlock->GetObjectSize(nullptr));
1803+
if (count > 0)
1804+
{
1805+
char* startPage = (char*)heapBlock->address + (MediumAllocationBlockAttributes::PageCount - count)*AutoSystemInfo::PageSize;
1806+
#pragma warning(suppress: 6250)
1807+
::VirtualFree(startPage, count*AutoSystemInfo::PageSize, MEM_DECOMMIT);
1808+
::ResetWriteWatch(startPage, count*AutoSystemInfo::PageSize);
1809+
}
1810+
}
1811+
/* static */
1812+
BOOL MediumAllocationBlockAttributes::RecommitUnusablePages(HeapBlock* heapBlock)
1813+
{
1814+
size_t count = MediumAllocationBlockAttributes::GetUnusablePageCount(heapBlock->GetObjectSize(nullptr));
1815+
if (count > 0)
1816+
{
1817+
FAULTINJECT_MEMORY_NOTHROW_RET(_u("recommit unusable pages"), count*AutoSystemInfo::PageSize, FALSE);
1818+
1819+
char* startPage = (char*)heapBlock->address + (MediumAllocationBlockAttributes::PageCount - count)*AutoSystemInfo::PageSize;
1820+
#pragma warning(suppress: 6250)
1821+
return startPage==::VirtualAlloc(startPage, count*AutoSystemInfo::PageSize, MEM_COMMIT, PAGE_READWRITE);
1822+
}
1823+
return TRUE;
1824+
}
17761825

17771826
template class HeapInfo::ValidPointersMap<SmallAllocationBlockAttributes>;
17781827
template class ValidPointers<SmallAllocationBlockAttributes>;

lib/Common/Memory/SmallNormalHeapBucket.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77

88
template <typename TBlockType>
9-
SmallNormalHeapBucketBase<TBlockType>::SmallNormalHeapBucketBase()
9+
SmallNormalHeapBucketBase<TBlockType>::SmallNormalHeapBucketBase()
1010
#if ENABLE_PARTIAL_GC
1111
: partialHeapBlockList(nullptr)
1212
#if ENABLE_CONCURRENT_GC
@@ -96,6 +96,7 @@ SmallNormalHeapBucketBase<TBlockType>::RescanObjectsOnPage(TBlockType * block, c
9696
const uint pageByteOffset = static_cast<uint>((char*)pageAddress - blockStartAddress);
9797
uint firstObjectOnPageIndex = pageByteOffset / localObjectSize;
9898

99+
99100
// This is not necessarily the address on the first object that starts on the page
100101
// If the last object on the previous page spans two pages, this is the address of that object
101102
// We do it this way so that we can figure out if we need to rescan the first few bytes of the page
@@ -114,6 +115,12 @@ SmallNormalHeapBucketBase<TBlockType>::RescanObjectsOnPage(TBlockType * block, c
114115
const uint pageObjectCount = blockInfoForPage.pageObjectCount;
115116
const uint localObjectCount = (TBlockAttributes::PageCount * AutoSystemInfo::PageSize) / localObjectSize;
116117

118+
// With decommitting unallocatable ending pages and reset writewatch, we should never be scanning on these pages.
119+
if (firstObjectOnPageIndex >= localObjectCount)
120+
{
121+
ReportFatalException(NULL, E_FAIL, Fatal_Recycler_MemoryCorruption, 3);
122+
}
123+
117124
// If all objects are marked, rescan whole block at once
118125
if (TBlockType::CanRescanFullBlock() && rescanMarkCount == pageObjectCount)
119126
{
@@ -588,7 +595,7 @@ template class SmallNormalHeapBucketBase<MediumFinalizableHeapBlock>;
588595
template class SmallNormalHeapBucketBase<SmallFinalizableWithBarrierHeapBlock>;
589596
template class SmallNormalHeapBucketBase<MediumFinalizableWithBarrierHeapBlock>;
590597
#endif
591-
598+
592599
template void SmallNormalHeapBucketBase<SmallNormalHeapBlock>::Sweep(RecyclerSweep& recyclerSweep);
593600
template void SmallNormalHeapBucketBase<MediumNormalHeapBlock>::Sweep(RecyclerSweep& recyclerSweep);
594601

0 commit comments

Comments
 (0)