improve io::Appender functionality
authorAdam Simpkins <simpkins@fb.com>
Sun, 28 Sep 2014 01:24:18 +0000 (18:24 -0700)
committerdcsommer <dcsommer@fb.com>
Wed, 29 Oct 2014 23:06:11 +0000 (16:06 -0700)
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 [new file with mode: 0644]
folly/io/Cursor.h
folly/io/test/IOBufCursorTest.cpp

diff --git a/folly/io/Cursor-defs.h b/folly/io/Cursor-defs.h
new file mode 100644 (file)
index 0000000..2ee9319
--- /dev/null
@@ -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 <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
index 7963f193fe825d00750bd9b33c10a910615a572f..6ae8aef39fadc4863aff7d20044763f4d9e5b131 100644 (file)
@@ -18,6 +18,7 @@
 #define FOLLY_CURSOR_H
 
 #include <assert.h>
+#include <cstdarg>
 #include <stdexcept>
 #include <string.h>
 #include <type_traits>
@@ -28,6 +29,8 @@
 #include <folly/io/IOBufQueue.h>
 #include <folly/Likely.h>
 #include <folly/Memory.h>
+#include <folly/Portability.h>
+#include <folly/Range.h>
 
 /**
  * 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<Derived*>(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<RWCursor<access>>::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<Appender> {
     crtBuf_ = buffer_->prev();
   }
 
+  using detail::Writable<Appender>::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<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:
   bool tryGrowChain() {
     assert(crtBuf_->next() == buffer_);
@@ -757,7 +808,7 @@ class QueueAppender : public detail::Writable<QueueAppender> {
     queue_->postallocate(sizeof(T));
   }
 
-
+  using detail::Writable<QueueAppender>::pushAtMost;
   size_t pushAtMost(const uint8_t* buf, size_t len) {
     size_t remaining = len;
     while (remaining != 0) {
index 58ad41d7e6eaec01b2c05ded1a9ff2587b9fd4f9..c135a372b8d9cf0abf749ee39faeecb59893bf86 100644 (file)
 #include <boost/random.hpp>
 #include <gtest/gtest.h>
 #include <folly/Benchmark.h>
+#include <folly/Format.h>
 #include <folly/Range.h>
 #include <folly/io/Cursor.h>
+#include <folly/io/Cursor-defs.h>
 
 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<IOBuf>& buf, folly::StringPiece data) {
   buf->append(data.size());
 }
 
-void append(Appender& appender, folly::StringPiece data) {
-  appender.push(reinterpret_cast<const uint8_t*>(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;