Commit 3d5106cd authored by Adam Simpkins's avatar Adam Simpkins Committed by Jordan DeLong

fix IOBuf::reserve() when operating on a user-supplied buffer

Summary:
The IOBuf::reserveSlow() code assumed that external buffers were always
allocated with malloc.  This would cause problems when if reserve() was
ever used on a buffer created with IOBuf::takeOwnership().

This changes the code to now check if a user-specified free function has
been supplied.  If so, then it does not try to use realloc()/rallocm(),
and it now frees the old buffer using the specified free function rather
than free().

User-supplied buffers also have a separately allocated SharedInfo
object, which must be freed when we no longer need it.

Test Plan:
Added additional unit tests to check calling reserve() on IOBufs created
with IOBuf::takeOwnership().

Reviewed By: davejwatson@fb.com

FB internal diff: D1072292
parent bfa6ffb5
...@@ -453,18 +453,7 @@ void IOBuf::decrementRefcount() { ...@@ -453,18 +453,7 @@ void IOBuf::decrementRefcount() {
} }
// We were the last user. Free the buffer // We were the last user. Free the buffer
if (ext_.sharedInfo->freeFn != NULL) { freeExtBuffer();
try {
ext_.sharedInfo->freeFn(ext_.buf, ext_.sharedInfo->userData);
} catch (...) {
// The user's free function should never throw. Otherwise we might
// throw from the IOBuf destructor. Other code paths like coalesce()
// also assume that decrementRefcount() cannot throw.
abort();
}
} else {
free(ext_.buf);
}
// Free the SharedInfo if it was allocated separately. // Free the SharedInfo if it was allocated separately.
// //
...@@ -485,6 +474,11 @@ void IOBuf::reserveSlow(uint32_t minHeadroom, uint32_t minTailroom) { ...@@ -485,6 +474,11 @@ void IOBuf::reserveSlow(uint32_t minHeadroom, uint32_t minTailroom) {
size_t newCapacity = (size_t)length_ + minHeadroom + minTailroom; size_t newCapacity = (size_t)length_ + minHeadroom + minTailroom;
DCHECK_LT(newCapacity, UINT32_MAX); DCHECK_LT(newCapacity, UINT32_MAX);
// reserveSlow() is dangerous if anyone else is sharing the buffer, as we may
// reallocate and free the original buffer. It should only ever be called if
// we are the only user of the buffer.
DCHECK(!isSharedOne());
// We'll need to reallocate the buffer. // We'll need to reallocate the buffer.
// There are a few options. // There are a few options.
// - If we have enough total room, move the data around in the buffer // - If we have enough total room, move the data around in the buffer
...@@ -511,11 +505,15 @@ void IOBuf::reserveSlow(uint32_t minHeadroom, uint32_t minTailroom) { ...@@ -511,11 +505,15 @@ void IOBuf::reserveSlow(uint32_t minHeadroom, uint32_t minTailroom) {
uint32_t newHeadroom = 0; uint32_t newHeadroom = 0;
uint32_t oldHeadroom = headroom(); uint32_t oldHeadroom = headroom();
if ((flags_ & kFlagExt) && length_ != 0 && oldHeadroom >= minHeadroom) { // If we have a buffer allocated with malloc and we just need more tailroom,
// try to use realloc()/rallocm() to grow the buffer in place.
if ((flags_ & (kFlagExt | kFlagUserOwned)) == kFlagExt &&
(ext_.sharedInfo->freeFn == nullptr) &&
length_ != 0 && oldHeadroom >= minHeadroom) {
if (usingJEMalloc()) { if (usingJEMalloc()) {
size_t headSlack = oldHeadroom - minHeadroom; size_t headSlack = oldHeadroom - minHeadroom;
// We assume that tailroom is more useful and more important than // We assume that tailroom is more useful and more important than
// tailroom (not least because realloc / rallocm allow us to grow the // headroom (not least because realloc / rallocm allow us to grow the
// buffer at the tail, but not at the head) So, if we have more headroom // buffer at the tail, but not at the head) So, if we have more headroom
// than we need, we consider that "wasted". We arbitrarily define "too // than we need, we consider that "wasted". We arbitrarily define "too
// much" headroom to be 25% of the capacity. // much" headroom to be 25% of the capacity.
...@@ -559,8 +557,8 @@ void IOBuf::reserveSlow(uint32_t minHeadroom, uint32_t minTailroom) { ...@@ -559,8 +557,8 @@ void IOBuf::reserveSlow(uint32_t minHeadroom, uint32_t minTailroom) {
} }
newBuffer = static_cast<uint8_t*>(p); newBuffer = static_cast<uint8_t*>(p);
memcpy(newBuffer + minHeadroom, data_, length_); memcpy(newBuffer + minHeadroom, data_, length_);
if (flags_ & kFlagExt) { if ((flags_ & (kFlagExt | kFlagUserOwned)) == kFlagExt) {
free(ext_.buf); freeExtBuffer();
} }
newHeadroom = minHeadroom; newHeadroom = minHeadroom;
} }
...@@ -569,8 +567,11 @@ void IOBuf::reserveSlow(uint32_t minHeadroom, uint32_t minTailroom) { ...@@ -569,8 +567,11 @@ void IOBuf::reserveSlow(uint32_t minHeadroom, uint32_t minTailroom) {
uint32_t cap; uint32_t cap;
initExtBuffer(newBuffer, newAllocatedCapacity, &info, &cap); initExtBuffer(newBuffer, newAllocatedCapacity, &info, &cap);
flags_ = kFlagExt; if (flags_ & kFlagFreeSharedInfo) {
delete ext_.sharedInfo;
}
flags_ = kFlagExt;
ext_.capacity = cap; ext_.capacity = cap;
ext_.type = kExtAllocated; ext_.type = kExtAllocated;
ext_.buf = newBuffer; ext_.buf = newBuffer;
...@@ -579,6 +580,23 @@ void IOBuf::reserveSlow(uint32_t minHeadroom, uint32_t minTailroom) { ...@@ -579,6 +580,23 @@ void IOBuf::reserveSlow(uint32_t minHeadroom, uint32_t minTailroom) {
// length_ is unchanged // length_ is unchanged
} }
void IOBuf::freeExtBuffer() {
DCHECK((flags_ & (kFlagExt | kFlagUserOwned)) == kFlagExt);
if (ext_.sharedInfo->freeFn) {
try {
ext_.sharedInfo->freeFn(ext_.buf, ext_.sharedInfo->userData);
} catch (...) {
// The user's free function should never throw. Otherwise we might
// throw from the IOBuf destructor. Other code paths like coalesce()
// also assume that decrementRefcount() cannot throw.
abort();
}
} else {
free(ext_.buf);
}
}
void IOBuf::allocExtBuffer(uint32_t minCapacity, void IOBuf::allocExtBuffer(uint32_t minCapacity,
uint8_t** bufReturn, uint8_t** bufReturn,
SharedInfo** infoReturn, SharedInfo** infoReturn,
......
...@@ -1078,6 +1078,7 @@ class IOBuf { ...@@ -1078,6 +1078,7 @@ class IOBuf {
size_t newTailroom); size_t newTailroom);
void decrementRefcount(); void decrementRefcount();
void reserveSlow(uint32_t minHeadroom, uint32_t minTailroom); void reserveSlow(uint32_t minHeadroom, uint32_t minTailroom);
void freeExtBuffer();
static size_t goodExtBufferSize(uint32_t minCapacity); static size_t goodExtBufferSize(uint32_t minCapacity);
static void initExtBuffer(uint8_t* buf, size_t mallocSize, static void initExtBuffer(uint8_t* buf, size_t mallocSize,
......
...@@ -494,6 +494,12 @@ TEST(IOBuf, Chaining) { ...@@ -494,6 +494,12 @@ TEST(IOBuf, Chaining) {
EXPECT_EQ(3, iob2->computeChainDataLength()); EXPECT_EQ(3, iob2->computeChainDataLength());
} }
void reserveTestFreeFn(void* buffer, void* ptr) {
uint32_t* freeCount = static_cast<uint32_t*>(ptr);;
delete[] static_cast<uint8_t*>(buffer);
++(*freeCount);
};
TEST(IOBuf, Reserve) { TEST(IOBuf, Reserve) {
uint32_t fillSeed = 0x23456789; uint32_t fillSeed = 0x23456789;
boost::mt19937 gen(fillSeed); boost::mt19937 gen(fillSeed);
...@@ -555,6 +561,26 @@ TEST(IOBuf, Reserve) { ...@@ -555,6 +561,26 @@ TEST(IOBuf, Reserve) {
EXPECT_EQ(0, iob->headroom()); EXPECT_EQ(0, iob->headroom());
EXPECT_LE(2000, iob->tailroom()); EXPECT_LE(2000, iob->tailroom());
} }
// Test reserving from a user-allocated buffer.
{
uint8_t* buf = static_cast<uint8_t*>(malloc(100));
auto iob = IOBuf::takeOwnership(buf, 100);
iob->reserve(0, 2000);
EXPECT_EQ(0, iob->headroom());
EXPECT_LE(2000, iob->tailroom());
}
// Test reserving from a user-allocated with a custom free function.
{
uint32_t freeCount{0};
uint8_t* buf = new uint8_t[100];
auto iob = IOBuf::takeOwnership(buf, 100, reserveTestFreeFn, &freeCount);
iob->reserve(0, 2000);
EXPECT_EQ(0, iob->headroom());
EXPECT_LE(2000, iob->tailroom());
EXPECT_EQ(1, freeCount);
}
} }
TEST(IOBuf, copyBuffer) { TEST(IOBuf, copyBuffer) {
......
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