Commit 1d33fd21 authored by Adam Simpkins's avatar Adam Simpkins Committed by dcsommer

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
parent 6ba489cb
/*
* 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 <folly/io/Cursor.h>
#include <cstdio>
#include <folly/ScopeGuard.h>
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<char*>(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<char*>(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
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
#define FOLLY_CURSOR_H #define FOLLY_CURSOR_H
#include <assert.h> #include <assert.h>
#include <cstdarg>
#include <stdexcept> #include <stdexcept>
#include <string.h> #include <string.h>
#include <type_traits> #include <type_traits>
...@@ -28,6 +29,8 @@ ...@@ -28,6 +29,8 @@
#include <folly/io/IOBufQueue.h> #include <folly/io/IOBufQueue.h>
#include <folly/Likely.h> #include <folly/Likely.h>
#include <folly/Memory.h> #include <folly/Memory.h>
#include <folly/Portability.h>
#include <folly/Range.h>
/** /**
* Cursor class for fast iteration over IOBuf chains. * Cursor class for fast iteration over IOBuf chains.
...@@ -472,6 +475,17 @@ class Writable { ...@@ -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<Derived*>(this);
return d->pushAtMost(buf.data(), buf.size());
}
/** /**
* push len bytes of data from input cursor, data could be in an IOBuf chain. * 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 * If input cursor contains less than len bytes, or this cursor has less than
...@@ -507,7 +521,6 @@ class Writable { ...@@ -507,7 +521,6 @@ class Writable {
len -= available; len -= available;
} }
} }
}; };
} // namespace detail } // namespace detail
...@@ -554,6 +567,7 @@ class RWCursor ...@@ -554,6 +567,7 @@ class RWCursor
return this->crtBuf_->gather(this->offset_ + size); return this->crtBuf_->gather(this->offset_ + size);
} }
using detail::Writable<RWCursor<access>>::pushAtMost;
size_t pushAtMost(const uint8_t* buf, size_t len) { size_t pushAtMost(const uint8_t* buf, size_t len) {
size_t copied = 0; size_t copied = 0;
for (;;) { for (;;) {
...@@ -681,6 +695,7 @@ class Appender : public detail::Writable<Appender> { ...@@ -681,6 +695,7 @@ class Appender : public detail::Writable<Appender> {
crtBuf_ = buffer_->prev(); crtBuf_ = buffer_->prev();
} }
using detail::Writable<Appender>::pushAtMost;
size_t pushAtMost(const uint8_t* buf, size_t len) { size_t pushAtMost(const uint8_t* buf, size_t len) {
size_t copied = 0; size_t copied = 0;
for (;;) { for (;;) {
...@@ -703,6 +718,42 @@ class Appender : public detail::Writable<Appender> { ...@@ -703,6 +718,42 @@ class Appender : public detail::Writable<Appender> {
} }
} }
/*
* 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: private:
bool tryGrowChain() { bool tryGrowChain() {
assert(crtBuf_->next() == buffer_); assert(crtBuf_->next() == buffer_);
...@@ -757,7 +808,7 @@ class QueueAppender : public detail::Writable<QueueAppender> { ...@@ -757,7 +808,7 @@ class QueueAppender : public detail::Writable<QueueAppender> {
queue_->postallocate(sizeof(T)); queue_->postallocate(sizeof(T));
} }
using detail::Writable<QueueAppender>::pushAtMost;
size_t pushAtMost(const uint8_t* buf, size_t len) { size_t pushAtMost(const uint8_t* buf, size_t len) {
size_t remaining = len; size_t remaining = len;
while (remaining != 0) { while (remaining != 0) {
......
...@@ -20,12 +20,17 @@ ...@@ -20,12 +20,17 @@
#include <boost/random.hpp> #include <boost/random.hpp>
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <folly/Benchmark.h> #include <folly/Benchmark.h>
#include <folly/Format.h>
#include <folly/Range.h> #include <folly/Range.h>
#include <folly/io/Cursor.h> #include <folly/io/Cursor.h>
#include <folly/io/Cursor-defs.h>
DECLARE_bool(benchmark); DECLARE_bool(benchmark);
using folly::ByteRange;
using folly::format;
using folly::IOBuf; using folly::IOBuf;
using folly::StringPiece;
using std::unique_ptr; using std::unique_ptr;
using namespace folly::io; using namespace folly::io;
...@@ -174,8 +179,8 @@ void append(std::unique_ptr<IOBuf>& buf, folly::StringPiece data) { ...@@ -174,8 +179,8 @@ void append(std::unique_ptr<IOBuf>& buf, folly::StringPiece data) {
buf->append(data.size()); buf->append(data.size());
} }
void append(Appender& appender, folly::StringPiece data) { void append(Appender& appender, StringPiece data) {
appender.push(reinterpret_cast<const uint8_t*>(data.data()), data.size()); appender.push(ByteRange(data));
} }
std::string toString(const IOBuf& buf) { std::string toString(const IOBuf& buf) {
...@@ -424,6 +429,47 @@ TEST(IOBuf, Appender) { ...@@ -424,6 +429,47 @@ TEST(IOBuf, Appender) {
EXPECT_EQ("hello world", toString(*head)); 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) { TEST(IOBuf, QueueAppender) {
folly::IOBufQueue queue; folly::IOBufQueue queue;
......
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