Skip to content

Commit 51034ab

Browse files
Bikramjeet Vigfacebook-github-bot
Bikramjeet Vig
authored andcommitted
Fix allocation calls where requested size is covered by the collateral (facebookincubator#9145)
Summary: Currently calls to allocation APIs in memory allocator, namely allocateNonContiguous and allocateContiguous where the requested size is covered by the provided collateral allocation is incorrectly handeled. The issue arises when the variable tracking the number of additional pages to be allocated becomes negative. This negative value is then passed to the MemoryPool's reserve() call, where it is implicitly converted into an unsigned integer. Instead of failing the reservation due to an unusually large reservation request (as a result of the negative int64 to uint64 conversion), the reservation remains unchanged. This happens because when MemoryPoolImpl::reserve() uses reservationSizeLocked(int64_t size) to check if more reservation is needed, it returns 0. This is due to another implicit conversion from uint64 back to int64, which is then used to calculate that no increment to the reservation is necessary. Finally, the used and cumulative metrics also get updated correctly because they are signed integers and the compiler implicitly converts unit64 back to int64. Therefore, it never manifests into an error state but the code is fragile and can cause issues with future changes if the implementation changes. This change fixes this silent bug by ensuring this case is properly handled and extra memory is released with the proper APIs. Differential Revision: D55047654
1 parent 596476a commit 51034ab

File tree

4 files changed

+63
-26
lines changed

4 files changed

+63
-26
lines changed

velox/common/memory/MemoryAllocator.cpp

+31-22
Original file line numberDiff line numberDiff line change
@@ -177,18 +177,22 @@ bool MemoryAllocator::allocateNonContiguous(
177177
}
178178

179179
const SizeMix mix = allocationSize(numPages, minSizeClass);
180-
const int64_t numNeededPages = mix.totalPages - numPagesToFree;
181-
// TODO: handle negative 'numNeededPages' in a follow-up.
182-
183180
if (reservationCB != nullptr) {
184-
try {
185-
reservationCB(AllocationTraits::pageBytes(numNeededPages), true);
186-
} catch (const std::exception&) {
187-
VELOX_MEM_LOG_EVERY_MS(WARNING, 1000)
188-
<< "Exceeded memory reservation limit when reserve " << numNeededPages
189-
<< " new pages when allocate " << mix.totalPages << " pages";
190-
cleanupAllocAndReleaseReservation(bytesToFree);
191-
std::rethrow_exception(std::current_exception());
181+
if (mix.totalPages >= numPagesToFree) {
182+
const uint64_t numNeededPages = mix.totalPages - numPagesToFree;
183+
try {
184+
reservationCB(AllocationTraits::pageBytes(numNeededPages), true);
185+
} catch (const std::exception&) {
186+
VELOX_MEM_LOG_EVERY_MS(WARNING, 1000)
187+
<< "Exceeded memory reservation limit when reserve "
188+
<< numNeededPages << " new pages when allocate " << mix.totalPages
189+
<< " pages";
190+
cleanupAllocAndReleaseReservation(bytesToFree);
191+
std::rethrow_exception(std::current_exception());
192+
}
193+
} else {
194+
const uint64_t numExtraPages = numPagesToFree - mix.totalPages;
195+
reservationCB(AllocationTraits::pageBytes(numExtraPages), false);
192196
}
193197
}
194198

@@ -222,7 +226,6 @@ bool MemoryAllocator::allocateContiguous(
222226
allocation.numPages() + (collateral ? collateral->numPages() : 0);
223227
const uint64_t totalCollateralBytes =
224228
AllocationTraits::pageBytes(numCollateralPages);
225-
const int64_t newPages = numPages - numCollateralPages;
226229
auto cleanupCollateralAndReleaseReservation = [&](uint64_t reservationBytes) {
227230
if ((collateral != nullptr) && !collateral->empty()) {
228231
freeNonContiguous(*collateral);
@@ -239,17 +242,23 @@ bool MemoryAllocator::allocateContiguous(
239242
cleanupCollateralAndReleaseReservation(totalCollateralBytes);
240243
return true;
241244
}
242-
// TODO: handle negative 'newPages'.
245+
243246
if (reservationCB != nullptr) {
244-
try {
245-
reservationCB(AllocationTraits::pageBytes(newPages), true);
246-
} catch (const std::exception& e) {
247-
VELOX_MEM_LOG_EVERY_MS(WARNING, 1000)
248-
<< "Exceeded memory reservation limit when reserve " << newPages
249-
<< " new pages when allocate " << numPages
250-
<< " pages, error: " << e.what();
251-
cleanupCollateralAndReleaseReservation(totalCollateralBytes);
252-
std::rethrow_exception(std::current_exception());
247+
if (numPages >= numCollateralPages) {
248+
const int64_t numNeededPages = numPages - numCollateralPages;
249+
try {
250+
reservationCB(AllocationTraits::pageBytes(numNeededPages), true);
251+
} catch (const std::exception& e) {
252+
VELOX_MEM_LOG_EVERY_MS(WARNING, 1000)
253+
<< "Exceeded memory reservation limit when reserve "
254+
<< numNeededPages << " new pages when allocate " << numPages
255+
<< " pages, error: " << e.what();
256+
cleanupCollateralAndReleaseReservation(totalCollateralBytes);
257+
std::rethrow_exception(std::current_exception());
258+
}
259+
} else {
260+
const uint64_t numExtraPages = numCollateralPages - numPages;
261+
reservationCB(AllocationTraits::pageBytes(numExtraPages), false);
253262
}
254263
}
255264

velox/common/memory/MemoryAllocator.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ class MemoryAllocator : public std::enable_shared_from_this<MemoryAllocator> {
221221
/// the same as 'this'.
222222
virtual void registerCache(const std::shared_ptr<Cache>& cache) = 0;
223223

224-
using ReservationCallback = std::function<void(int64_t, bool)>;
224+
using ReservationCallback = std::function<void(uint64_t, bool)>;
225225

226226
/// Returns the capacity of the allocator in bytes.
227227
virtual size_t capacity() const = 0;

velox/common/memory/MemoryPool.cpp

+3-3
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,7 @@ void MemoryPoolImpl::allocateNonContiguous(
545545
if (!allocator_->allocateNonContiguous(
546546
numPages,
547547
out,
548-
[this](int64_t allocBytes, bool preAllocate) {
548+
[this](uint64_t allocBytes, bool preAllocate) {
549549
if (preAllocate) {
550550
reserve(allocBytes);
551551
} else {
@@ -597,7 +597,7 @@ void MemoryPoolImpl::allocateContiguous(
597597
numPages,
598598
nullptr,
599599
out,
600-
[this](int64_t allocBytes, bool preAlloc) {
600+
[this](uint64_t allocBytes, bool preAlloc) {
601601
if (preAlloc) {
602602
reserve(allocBytes);
603603
} else {
@@ -632,7 +632,7 @@ void MemoryPoolImpl::growContiguous(
632632
MachinePageCount increment,
633633
ContiguousAllocation& allocation) {
634634
if (!allocator_->growContiguous(
635-
increment, allocation, [this](int64_t allocBytes, bool preAlloc) {
635+
increment, allocation, [this](uint64_t allocBytes, bool preAlloc) {
636636
if (preAlloc) {
637637
reserve(allocBytes);
638638
} else {

velox/common/memory/tests/MemoryPoolTest.cpp

+28
Original file line numberDiff line numberDiff line change
@@ -3671,6 +3671,34 @@ TEST_P(MemoryPoolTest, overuseUnderArbitration) {
36713671
ASSERT_EQ(child->reservedBytes(), 0);
36723672
}
36733673

3674+
TEST_P(MemoryPoolTest, allocationWithCoveredCollateral) {
3675+
// Verify that the memory pool's reservation is correctly updated when an
3676+
// allocation call is attempted with collateral that covers the allocation
3677+
// (that is, the collateral is larger than the requested allocation).
3678+
auto manager = getMemoryManager();
3679+
auto root = manager->addRootPool("root", kMaxMemory, nullptr);
3680+
ASSERT_TRUE(root->trackUsage());
3681+
auto pool =
3682+
root->addLeafChild("allocationWithCoveredCollateral", isLeafThreadSafe_);
3683+
ASSERT_TRUE(pool->trackUsage());
3684+
// Check non-contiguous allocation.
3685+
ASSERT_EQ(pool->reservedBytes(), 0);
3686+
Allocation allocation;
3687+
pool->allocateNonContiguous(100, allocation);
3688+
auto prevReservedBytes = pool->currentBytes();
3689+
pool->allocateNonContiguous(50, allocation);
3690+
ASSERT_LT(pool->currentBytes(), prevReservedBytes);
3691+
pool->freeNonContiguous(allocation);
3692+
3693+
// Check contiguous allocation.
3694+
ContiguousAllocation contiguousAllocation;
3695+
pool->allocateContiguous(100, contiguousAllocation);
3696+
prevReservedBytes = pool->currentBytes();
3697+
pool->allocateContiguous(50, contiguousAllocation);
3698+
ASSERT_LT(pool->currentBytes(), prevReservedBytes);
3699+
pool->freeContiguous(contiguousAllocation);
3700+
}
3701+
36743702
VELOX_INSTANTIATE_TEST_SUITE_P(
36753703
MemoryPoolTestSuite,
36763704
MemoryPoolTest,

0 commit comments

Comments
 (0)