}
// We were the last user. Free the buffer
- if (ext_.sharedInfo->freeFn != NULL) {
- 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);
- }
+ freeExtBuffer();
// Free the SharedInfo if it was allocated separately.
//
size_t newCapacity = (size_t)length_ + minHeadroom + minTailroom;
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.
// There are a few options.
// - If we have enough total room, move the data around in the buffer
uint32_t newHeadroom = 0;
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()) {
size_t headSlack = oldHeadroom - minHeadroom;
// 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
// than we need, we consider that "wasted". We arbitrarily define "too
// much" headroom to be 25% of the capacity.
}
newBuffer = static_cast<uint8_t*>(p);
memcpy(newBuffer + minHeadroom, data_, length_);
- if (flags_ & kFlagExt) {
- free(ext_.buf);
+ if ((flags_ & (kFlagExt | kFlagUserOwned)) == kFlagExt) {
+ freeExtBuffer();
}
newHeadroom = minHeadroom;
}
uint32_t cap;
initExtBuffer(newBuffer, newAllocatedCapacity, &info, &cap);
- flags_ = kFlagExt;
+ if (flags_ & kFlagFreeSharedInfo) {
+ delete ext_.sharedInfo;
+ }
+ flags_ = kFlagExt;
ext_.capacity = cap;
ext_.type = kExtAllocated;
ext_.buf = newBuffer;
// 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,
uint8_t** bufReturn,
SharedInfo** infoReturn,
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) {
uint32_t fillSeed = 0x23456789;
boost::mt19937 gen(fillSeed);
EXPECT_EQ(0, iob->headroom());
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) {