Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/butil/iobuf.h
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,20 @@ class IOBufCutter {
// Returns pointer to the character, nullptr on empty
const void* fetch1();

// Peek the unconsumed part of the current reference without copying.
// Load the next reference if needed; return empty on exhausted input.
StringPiece front_block();

// Peek n bytes without consuming them. Return a pointer into the backing
// block when contiguous, otherwise copy into aux_buffer (at least n bytes).
// Return nullptr on insufficient data, leaving aux_buffer unchanged.
// For n == 0, return aux_buffer without accessing the buffer.
const void* fetch(void* aux_buffer, size_t n) const;

// Access the underlying IOBuf without copying or synchronizing the cursor.
// Its front block may still include bytes already consumed by this cutter.
const IOBuf& buf() const { return *_buf; }

// Pop n bytes from front side
// Returns bytes popped.
size_t pop_front(size_t n);
Expand Down
36 changes: 36 additions & 0 deletions src/butil/iobuf_inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,42 @@ inline const void* IOBufCutter::fetch1() {
return _data;
}

inline StringPiece IOBufCutter::front_block() {
const char* data = static_cast<const char*>(fetch1());
if (data == nullptr) {
return StringPiece();
}
const char* end = static_cast<const char*>(_data_end);
return StringPiece(data, static_cast<size_t>(end - data));
}

inline const void* IOBufCutter::fetch(void* aux_buffer, size_t n) const {
if (n == 0) {
return aux_buffer;
}
if (_block == nullptr) {
return _buf->fetch(aux_buffer, n);
}
const size_t size = (const char*)_data_end - (const char*)_data;
if (n <= size) {
return _data;
}
if (n > remaining_bytes()) {
return nullptr;
}
// The exhausted front block has not been removed yet. Peeking into the
// next block must also remain zero-copy when all n bytes fit there.
if (size == 0) {
const StringPiece next = _buf->backing_block(1);
if (n <= next.size()) {
return next.data();
}
}
const size_t offset = _buf->_front_ref().length - size;
_buf->copy_to(aux_buffer, n, offset);
return aux_buffer;
}

inline size_t IOBufCutter::copy_to(void* out, size_t n) {
size_t size = (char*)_data_end - (char*)_data;
if (n <= size) {
Expand Down
127 changes: 127 additions & 0 deletions test/iobuf_unittest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2030,6 +2030,133 @@ static void append_as_separate_blocks(butil::IOBuf* buf,
}
}

TEST_F(IOBufTest, cutter_front_block_empty_and_append) {
butil::IOBuf buf;
butil::IOBufCutter cutter(&buf);
ASSERT_TRUE(cutter.front_block().empty());
ASSERT_TRUE(cutter.front_block().empty());
ASSERT_EQ(0u, cutter.remaining_bytes());

append_as_separate_blocks(&buf, "abc", 3);
ASSERT_EQ(butil::StringPiece("abc"), cutter.front_block());
ASSERT_EQ(buf.backing_block(0).data(), cutter.front_block().data());
ASSERT_EQ(3u, cutter.remaining_bytes());
ASSERT_EQ(3u, cutter.pop_front(3));

// Append before the exhausted reference has been removed.
append_as_separate_blocks(&buf, "def", 3);
const char* next = buf.backing_block(1).data();
ASSERT_EQ(butil::StringPiece("def"), cutter.front_block());
ASSERT_EQ(next, cutter.front_block().data());
ASSERT_EQ(3u, cutter.remaining_bytes());
ASSERT_EQ(3u, cutter.pop_front(3));
ASSERT_TRUE(cutter.front_block().empty());
ASSERT_TRUE(cutter.front_block().empty());
ASSERT_EQ(0u, cutter.remaining_bytes());

// Appending also works after front_block() has observed empty input.
append_as_separate_blocks(&buf, "ghi", 3);
ASSERT_EQ(butil::StringPiece("ghi"), cutter.front_block());
ASSERT_EQ(buf.backing_block(0).data(), cutter.front_block().data());
ASSERT_EQ(3u, cutter.remaining_bytes());
}

TEST_F(IOBufTest, cutter_front_block_partial_and_next) {
butil::IOBuf buf;
append_as_separate_blocks(&buf, "abcdefghi", 3);
const butil::StringPiece blocks[] = {
buf.backing_block(0), buf.backing_block(1), buf.backing_block(2)
};
butil::IOBufCutter cutter(&buf);
for (size_t i = 0; i < arraysize(blocks); ++i) {
ASSERT_EQ(blocks[i], cutter.front_block());
ASSERT_EQ(blocks[i].data(), cutter.front_block().data());
ASSERT_EQ(9u - i * 3, cutter.remaining_bytes());

ASSERT_EQ(1u, cutter.pop_front(1));
const butil::StringPiece rest(blocks[i].data() + 1, 2);
ASSERT_EQ(rest, cutter.front_block());
ASSERT_EQ(rest.data(), cutter.front_block().data());
ASSERT_EQ(8u - i * 3, cutter.remaining_bytes());
std::string consumed;
ASSERT_EQ(2u, cutter.cutn(&consumed, 2));
ASSERT_EQ(rest, butil::StringPiece(consumed));
}
ASSERT_TRUE(cutter.front_block().empty());
ASSERT_EQ(0u, cutter.remaining_bytes());
ASSERT_TRUE(buf.empty());
}

TEST_F(IOBufTest, cutter_fetch_empty_and_append) {
butil::IOBuf buf;
{
const butil::IOBufCutter cutter(&buf);
char aux[4] = "xyz";
ASSERT_EQ(&buf, &cutter.buf());
ASSERT_EQ(aux, cutter.fetch(aux, 0));
ASSERT_EQ(nullptr, cutter.fetch(nullptr, 0));
ASSERT_EQ(nullptr, cutter.fetch(aux, 1));
ASSERT_STREQ("xyz", aux);

ASSERT_EQ(0, buf.append("abc"));
ASSERT_EQ(buf.backing_block(0).data(), cutter.fetch(aux, 3));
ASSERT_EQ(nullptr, cutter.fetch(aux, 4));
ASSERT_STREQ("xyz", aux);
ASSERT_EQ(3u, cutter.remaining_bytes());
}
ASSERT_EQ("abc", buf.to_string());
}

TEST_F(IOBufTest, cutter_fetch_at_every_offset) {
const std::string payload = "abcdefghijkl";
const size_t chunk = 3;
for (size_t offset = 0; offset <= payload.size(); ++offset) {
butil::IOBuf buf;
append_as_separate_blocks(&buf, payload, chunk);
{
butil::IOBufCutter cutter(&buf);
ASSERT_EQ(offset, cutter.pop_front(offset));
const butil::IOBufCutter& reader = cutter;
const std::string underlying = buf.to_string();
const size_t block_count = buf.backing_block_num();
const size_t remaining = payload.size() - offset;
for (size_t n = 0; n <= remaining + 1; ++n) {
char aux[16];
memset(aux, '#', sizeof(aux));
const void* data = reader.fetch(aux, n);
if (n == 0) {
ASSERT_EQ(aux, data);
} else if (n > remaining) {
ASSERT_EQ(nullptr, data);
} else {
ASSERT_NE(nullptr, data);
ASSERT_EQ(payload.substr(offset, n),
std::string(static_cast<const char*>(data), n));
if (n <= chunk - offset % chunk) {
const size_t index = offset - (payload.size() - buf.size());
const butil::StringPiece block = buf.backing_block(index / chunk);
ASSERT_EQ(block.data() + index % chunk, data);
} else {
ASSERT_EQ(aux, data);
}
}
if (data != aux || n == 0) {
ASSERT_EQ(std::string(sizeof(aux), '#'),
std::string(aux, sizeof(aux)));
}
ASSERT_EQ(remaining, reader.remaining_bytes());
ASSERT_EQ(&buf, &reader.buf());
ASSERT_EQ(underlying, reader.buf().to_string());
ASSERT_EQ(block_count, reader.buf().backing_block_num());
}
std::string rest;
ASSERT_EQ(remaining, cutter.cutn(&rest, remaining));
ASSERT_EQ(payload.substr(offset), rest);
}
ASSERT_TRUE(buf.empty());
}
}

TEST_F(IOBufTest, as_input_stream_multi_block_read) {
butil::IOBuf buf;
const std::string payload = "the quick brown fox jumps over the lazy dog";
Expand Down
Loading