Commit c6d5701f authored by jl@opera.com's avatar jl@opera.com

PartitionAlloc: Leave bucket in valid state when allocation fails

When an allocation with the PartitionAllocReturnNull flag fails, we need to
make sure to leave all structures in a valid state when bailing out, since
the process lives on and may call again later.

Specifically, make sure that the bucket's activePagesHead has a valid value,
instead of null, when returning null from partitionAllocSlowPath(). Since
there's no meaningful page to set as the active pages head, set it to its
initial value, &PartitionRootBase::gSeedPage.

This was already necessarily supported by the allocation code paths. The
deallocation code path needs some minor adjustments to handle this state
correctly.

Review URL: https://codereview.chromium.org/645223007

git-svn-id: svn://svn.chromium.org/blink/trunk@184983 bbb929c8-8fbe-4397-9dbb-9b2b20218538
parent ae934c75
...@@ -699,8 +699,13 @@ void* partitionAllocSlowPath(PartitionRootBase* root, int flags, size_t size, Pa ...@@ -699,8 +699,13 @@ void* partitionAllocSlowPath(PartitionRootBase* root, int flags, size_t size, Pa
return partitionPageAllocAndFillFreelist(newPage); return partitionPageAllocAndFillFreelist(newPage);
partitionAllocSlowPathFailed: partitionAllocSlowPathFailed:
if (returnNull) if (returnNull) {
// If we get here, we will set the active page to null, which is an
// invalid state. To support continued use of this bucket, we need to
// restore a valid state, by setting the active page to the seed page.
bucket->activePagesHead = &PartitionRootGeneric::gSeedPage;
return nullptr; return nullptr;
}
partitionOutOfMemory(); partitionOutOfMemory();
return nullptr; return nullptr;
} }
...@@ -763,7 +768,6 @@ void partitionFreeSlowPath(PartitionPage* page) ...@@ -763,7 +768,6 @@ void partitionFreeSlowPath(PartitionPage* page)
{ {
PartitionBucket* bucket = page->bucket; PartitionBucket* bucket = page->bucket;
ASSERT(page != &PartitionRootGeneric::gSeedPage); ASSERT(page != &PartitionRootGeneric::gSeedPage);
ASSERT(bucket->activePagesHead != &PartitionRootGeneric::gSeedPage);
if (LIKELY(page->numAllocatedSlots == 0)) { if (LIKELY(page->numAllocatedSlots == 0)) {
// Page became fully unused. // Page became fully unused.
if (UNLIKELY(partitionBucketIsDirectMapped(bucket))) { if (UNLIKELY(partitionBucketIsDirectMapped(bucket))) {
...@@ -801,7 +805,10 @@ void partitionFreeSlowPath(PartitionPage* page) ...@@ -801,7 +805,10 @@ void partitionFreeSlowPath(PartitionPage* page)
// non-full page list. Also make it the current page to increase the // non-full page list. Also make it the current page to increase the
// chances of it being filled up again. The old current page will be // chances of it being filled up again. The old current page will be
// the next page. // the next page.
page->nextPage = bucket->activePagesHead; if (UNLIKELY(bucket->activePagesHead == &PartitionRootGeneric::gSeedPage))
page->nextPage = 0;
else
page->nextPage = bucket->activePagesHead;
bucket->activePagesHead = page; bucket->activePagesHead = page;
--bucket->numFullPages; --bucket->numFullPages;
// Special case: for a partition page with just a single slot, it may // Special case: for a partition page with just a single slot, it may
......
...@@ -32,6 +32,7 @@ ...@@ -32,6 +32,7 @@
#include "wtf/PartitionAlloc.h" #include "wtf/PartitionAlloc.h"
#include "wtf/BitwiseOperations.h" #include "wtf/BitwiseOperations.h"
#include "wtf/CPU.h"
#include "wtf/OwnPtr.h" #include "wtf/OwnPtr.h"
#include "wtf/PassOwnPtr.h" #include "wtf/PassOwnPtr.h"
#include <gtest/gtest.h> #include <gtest/gtest.h>
...@@ -40,6 +41,8 @@ ...@@ -40,6 +41,8 @@
#if OS(POSIX) #if OS(POSIX)
#include <sys/mman.h> #include <sys/mman.h>
#include <sys/resource.h>
#include <sys/time.h>
#ifndef MAP_ANONYMOUS #ifndef MAP_ANONYMOUS
#define MAP_ANONYMOUS MAP_ANON #define MAP_ANONYMOUS MAP_ANON
...@@ -85,6 +88,44 @@ static void TestShutdown() ...@@ -85,6 +88,44 @@ static void TestShutdown()
EXPECT_TRUE(genericAllocator.shutdown()); EXPECT_TRUE(genericAllocator.shutdown());
} }
static bool SetAddressSpaceLimit()
{
#if !CPU(64BIT)
// 32 bits => address space is limited already.
return true;
#elif OS(POSIX)
const size_t kAddressSpaceLimit = static_cast<size_t>(4096) * 1024 * 1024;
struct rlimit limit;
if (getrlimit(RLIMIT_AS, &limit) != 0)
return false;
if (limit.rlim_cur == RLIM_INFINITY || limit.rlim_cur > kAddressSpaceLimit) {
limit.rlim_cur = kAddressSpaceLimit;
if (setrlimit(RLIMIT_AS, &limit) != 0)
return false;
}
return true;
#else
return false;
#endif
}
static bool ClearAddressSpaceLimit()
{
#if !CPU(64BIT)
return true;
#elif OS(POSIX)
struct rlimit limit;
if (getrlimit(RLIMIT_AS, &limit) != 0)
return false;
limit.rlim_cur = limit.rlim_max;
if (setrlimit(RLIMIT_AS, &limit) != 0)
return false;
return true;
#else
return false;
#endif
}
static WTF::PartitionPage* GetFullPage(size_t size) static WTF::PartitionPage* GetFullPage(size_t size)
{ {
size_t realSize = size + kExtraAllocSize; size_t realSize = size + kExtraAllocSize;
...@@ -1097,6 +1138,54 @@ TEST(PartitionAllocTest, LostFreePagesBug) ...@@ -1097,6 +1138,54 @@ TEST(PartitionAllocTest, LostFreePagesBug)
TestShutdown(); TestShutdown();
} }
#if !CPU(64BIT) || OS(POSIX)
// Tests that if an allocation fails in "return null" mode, repeating it doesn't
// crash, and still returns null. The test tries to allocate 6 GB of memory in
// 512 kB blocks. On 64-bit POSIX systems, the address space is limited to 4 GB
// using setrlimit() first.
TEST(PartitionAllocTest, RepeatedReturnNull)
{
TestSetup();
EXPECT_TRUE(SetAddressSpaceLimit());
// 512 kB x 12288 == 6 GB
const size_t blockSize = 512 * 1024;
const int numAllocations = 12288;
void* ptrs[numAllocations];
int i;
for (i = 0; i < numAllocations; ++i) {
ptrs[i] = partitionAllocGenericFlags(genericAllocator.root(), WTF::PartitionAllocReturnNull, blockSize);
if (!ptrs[i]) {
ptrs[i] = partitionAllocGenericFlags(genericAllocator.root(), WTF::PartitionAllocReturnNull, blockSize);
EXPECT_FALSE(ptrs[i]);
break;
}
}
// We shouldn't succeed in allocating all 6 GB of memory. If we do, then
// we're not actually testing anything here.
EXPECT_LT(i, numAllocations);
// Free, reallocate and free again each block we allocated. We do this to
// check that freeing memory also works correctly after a failed allocation.
for (--i; i >= 0; --i) {
partitionFreeGeneric(genericAllocator.root(), ptrs[i]);
ptrs[i] = partitionAllocGenericFlags(genericAllocator.root(), WTF::PartitionAllocReturnNull, blockSize);
EXPECT_TRUE(ptrs[i]);
partitionFreeGeneric(genericAllocator.root(), ptrs[i]);
}
EXPECT_TRUE(ClearAddressSpaceLimit());
TestShutdown();
}
#endif // !CPU(64BIT) || OS(POSIX)
#if !OS(ANDROID) #if !OS(ANDROID)
// Make sure that malloc(-1) dies. // Make sure that malloc(-1) dies.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment