From 1d33fd218811e52e59ccb5824422e5cc96feb581 Mon Sep 17 00:00:00 2001 From: Adam Simpkins Date: Sat, 27 Sep 2014 18:24:18 -0700 Subject: [PATCH] improve io::Appender functionality Summary: Add an operator()(StringPiece) method to Appender, so it can be used directly with folly::Formatter objects. Also add printf() and vprintf() methods to Appender, for places that need to use existing printf-style formatting. This also includes versions of push() and pushAtMost() that accept ByteRange objects. Previously we only had push() implementations that took separate buffer and size arguments. Test Plan: Added new unit tests to IOBufCursorTest.cpp Reviewed By: davejwatson@fb.com Subscribers: trunkagent, doug, net-systems@, exa, njormrod FB internal diff: D1583684 --- folly/io/Cursor-defs.h | 72 +++++++++++++++++++++++++++++++ folly/io/Cursor.h | 55 ++++++++++++++++++++++- folly/io/test/IOBufCursorTest.cpp | 50 ++++++++++++++++++++- 3 files changed, 173 insertions(+), 4 deletions(-) create mode 100644 folly/io/Cursor-defs.h diff --git a/folly/io/Cursor-defs.h b/folly/io/Cursor-defs.h new file mode 100644 index 00000000..2ee9319d --- /dev/null +++ b/folly/io/Cursor-defs.h @@ -0,0 +1,72 @@ +/* + * Copyright 2014 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include + +#include +#include + +namespace folly { namespace io { + +void Appender::printf(const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); +} + +void Appender::vprintf(const char* fmt, va_list ap) { + // Make a copy of ap in case we need to retry. + // We use ap on the first attempt, so it always gets advanced + // passed the used arguments. We'll only use apCopy if we need to retry. + va_list apCopy; + va_copy(apCopy, ap); + SCOPE_EXIT { + va_end(apCopy); + }; + + // First try writing into our available data space. + int ret = vsnprintf(reinterpret_cast(writableData()), length(), + fmt, ap); + if (ret < 0) { + throw std::runtime_error("error formatting printf() data"); + } + // vsnprintf() returns the number of characters that would be printed, + // not including the terminating nul. + if (ret < length()) { + // All of the data was successfully written. + append(ret); + return; + } + + // There wasn't enough room for the data. + // Allocate more room, and then retry. + ensure(ret + 1); + ret = vsnprintf(reinterpret_cast(writableData()), length(), + fmt, apCopy); + if (ret < 0) { + throw std::runtime_error("error formatting printf() data"); + } + if (ret >= length()) { + // This shouldn't ever happen. + throw std::runtime_error("unexpectedly out of buffer space on second " + "vsnprintf() attmept"); + } + append(ret); +} + +}} // folly::io diff --git a/folly/io/Cursor.h b/folly/io/Cursor.h index 7963f193..6ae8aef3 100644 --- a/folly/io/Cursor.h +++ b/folly/io/Cursor.h @@ -18,6 +18,7 @@ #define FOLLY_CURSOR_H #include +#include #include #include #include @@ -28,6 +29,8 @@ #include #include #include +#include +#include /** * Cursor class for fast iteration over IOBuf chains. @@ -472,6 +475,17 @@ class Writable { } } + void push(ByteRange buf) { + if (this->pushAtMost(buf) != buf.size()) { + throw std::out_of_range("overflow"); + } + } + + size_t pushAtMost(ByteRange buf) { + Derived* d = static_cast(this); + return d->pushAtMost(buf.data(), buf.size()); + } + /** * push len bytes of data from input cursor, data could be in an IOBuf chain. * If input cursor contains less than len bytes, or this cursor has less than @@ -507,7 +521,6 @@ class Writable { len -= available; } } - }; } // namespace detail @@ -554,6 +567,7 @@ class RWCursor return this->crtBuf_->gather(this->offset_ + size); } + using detail::Writable>::pushAtMost; size_t pushAtMost(const uint8_t* buf, size_t len) { size_t copied = 0; for (;;) { @@ -681,6 +695,7 @@ class Appender : public detail::Writable { crtBuf_ = buffer_->prev(); } + using detail::Writable::pushAtMost; size_t pushAtMost(const uint8_t* buf, size_t len) { size_t copied = 0; for (;;) { @@ -703,6 +718,42 @@ class Appender : public detail::Writable { } } + /* + * Append to the end of this buffer, using a printf() style + * format specifier. + * + * Note that folly/Format.h provides nicer and more type-safe mechanisms + * for formatting strings, which should generally be preferred over + * printf-style formatting. Appender objects can be used directly as an + * output argument for Formatter objects. For example: + * + * Appender app(&iobuf); + * format("{} {}", "hello", "world")(app); + * + * However, printf-style strings are still needed when dealing with existing + * third-party code in some cases. + * + * This will always add a nul-terminating character after the end + * of the output. However, the buffer data length will only be updated to + * include the data itself. The nul terminator will be the first byte in the + * buffer tailroom. + * + * This method may throw exceptions on error. + */ + void printf(FOLLY_PRINTF_FORMAT const char* fmt, ...) + FOLLY_PRINTF_FORMAT_ATTR(2, 3); + + void vprintf(const char* fmt, va_list ap); + + /* + * Calling an Appender object with a StringPiece will append the string + * piece. This allows Appender objects to be used directly with + * Formatter. + */ + void operator()(StringPiece sp) { + push(ByteRange(sp)); + } + private: bool tryGrowChain() { assert(crtBuf_->next() == buffer_); @@ -757,7 +808,7 @@ class QueueAppender : public detail::Writable { queue_->postallocate(sizeof(T)); } - + using detail::Writable::pushAtMost; size_t pushAtMost(const uint8_t* buf, size_t len) { size_t remaining = len; while (remaining != 0) { diff --git a/folly/io/test/IOBufCursorTest.cpp b/folly/io/test/IOBufCursorTest.cpp index 58ad41d7..c135a372 100644 --- a/folly/io/test/IOBufCursorTest.cpp +++ b/folly/io/test/IOBufCursorTest.cpp @@ -20,12 +20,17 @@ #include #include #include +#include #include #include +#include DECLARE_bool(benchmark); +using folly::ByteRange; +using folly::format; using folly::IOBuf; +using folly::StringPiece; using std::unique_ptr; using namespace folly::io; @@ -174,8 +179,8 @@ void append(std::unique_ptr& buf, folly::StringPiece data) { buf->append(data.size()); } -void append(Appender& appender, folly::StringPiece data) { - appender.push(reinterpret_cast(data.data()), data.size()); +void append(Appender& appender, StringPiece data) { + appender.push(ByteRange(data)); } std::string toString(const IOBuf& buf) { @@ -424,6 +429,47 @@ TEST(IOBuf, Appender) { EXPECT_EQ("hello world", toString(*head)); } +TEST(IOBuf, Printf) { + IOBuf head(IOBuf::CREATE, 24); + Appender app(&head, 32); + + app.printf("%s", "test"); + EXPECT_EQ(head.length(), 4); + EXPECT_EQ(0, memcmp(head.data(), "test\0", 5)); + + app.printf("%d%s %s%s %#x", 32, "this string is", + "longer than our original allocation size,", + "and will therefore require a new allocation", 0x12345678); + // The tailroom should start with a nul byte now. + EXPECT_GE(head.prev()->tailroom(), 1); + EXPECT_EQ(0, *head.prev()->tail()); + + EXPECT_EQ("test32this string is longer than our original " + "allocation size,and will therefore require a " + "new allocation 0x12345678", + head.moveToFbString().toStdString()); +} + +TEST(IOBuf, Format) { + IOBuf head(IOBuf::CREATE, 24); + Appender app(&head, 32); + + format("{}", "test")(app); + EXPECT_EQ(head.length(), 4); + EXPECT_EQ(0, memcmp(head.data(), "test", 4)); + + auto fmt = format("{}{} {}{} {:#x}", + 32, "this string is", + "longer than our original allocation size,", + "and will therefore require a new allocation", + 0x12345678); + fmt(app); + EXPECT_EQ("test32this string is longer than our original " + "allocation size,and will therefore require a " + "new allocation 0x12345678", + head.moveToFbString().toStdString()); +} + TEST(IOBuf, QueueAppender) { folly::IOBufQueue queue; -- 2.34.1