From e7319aade1ae528f39a72d64ebdeaff3ed9f727a Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sun, 5 Feb 2017 18:02:14 -0500 Subject: [PATCH] Add flat_streambuf: Objects of this type meet the requirements of DynamicBuffer and offer an additional invariant: buffer sequences returned by data() and prepare() are always of length one. --- CHANGELOG.md | 1 + doc/quickref.xml | 2 + doc/types/DynamicBuffer.qbk | 4 +- include/beast/core.hpp | 1 + include/beast/core/flat_streambuf.hpp | 310 ++++++++++++++++++++ include/beast/core/impl/flat_streambuf.ipp | 316 +++++++++++++++++++++ test/Jamfile | 1 + test/core/CMakeLists.txt | 1 + test/core/flat_streambuf.cpp | 171 +++++++++++ test/core/streambuf.cpp | 5 - 10 files changed, 806 insertions(+), 6 deletions(-) create mode 100644 include/beast/core/flat_streambuf.hpp create mode 100644 include/beast/core/impl/flat_streambuf.ipp create mode 100644 test/core/flat_streambuf.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e88f105..cf49fc24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ * Add Appveyor build scripts and badge * Tidy up MSVC CMake configuration * Make close_code a proper enum +* Add flat_streambuf -------------------------------------------------------------------------------- diff --git a/doc/quickref.xml b/doc/quickref.xml index 874c5a69..cabad789 100644 --- a/doc/quickref.xml +++ b/doc/quickref.xml @@ -165,6 +165,7 @@ Classes async_completion + basic_flat_streambuf basic_streambuf buffers_adapter consuming_buffers @@ -173,6 +174,7 @@ error_category error_code error_condition + flat_streambuf handler_alloc handler_ptr static_streambuf diff --git a/doc/types/DynamicBuffer.qbk b/doc/types/DynamicBuffer.qbk index 792c2591..009abbdd 100644 --- a/doc/types/DynamicBuffer.qbk +++ b/doc/types/DynamicBuffer.qbk @@ -19,7 +19,9 @@ The interface to this concept is intended to permit the following implementation strategies: * A single contiguous octet array, which is reallocated as necessary to - accommodate changes in the size of the octet sequence. + accommodate changes in the size of the octet sequence. This is the + implementation approach currently offered by + [link beast.ref.basic_flat_streambuf `basic_flat_streambuf`]. * A sequence of one or more octet arrays, where each array is of the same size. Additional octet array objects are appended to the sequence to diff --git a/include/beast/core.hpp b/include/beast/core.hpp index 9377cd99..b0dd3fc3 100644 --- a/include/beast/core.hpp +++ b/include/beast/core.hpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include diff --git a/include/beast/core/flat_streambuf.hpp b/include/beast/core/flat_streambuf.hpp new file mode 100644 index 00000000..496adc00 --- /dev/null +++ b/include/beast/core/flat_streambuf.hpp @@ -0,0 +1,310 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_FLAT_STREAMBUF_HPP +#define BEAST_FLAT_STREAMBUF_HPP + +#include +#include +#include +#include +#include + +namespace beast { + +/** A linear dynamic buffer. + + Objects of this type meet the requirements of @b DynamicBuffer + and offer additional invariants: + + @li Buffer sequences returned by @ref data and @ref prepare + will always be of length one. + + @li A configurable maximum buffer size may be set upon + construction. Attempts to exceed the buffer size will throw + `std::length_error`. + + @note This class is designed for use with algorithms that + take dynamic buffers as parameters, and are optimized + for the case where the input sequence or output sequence + is stored in a single contiguous buffer. +*/ +template +class basic_flat_streambuf +#if ! GENERATING_DOCS + : private detail::empty_base_optimization< + typename std::allocator_traits:: + template rebind_alloc> +#endif +{ +public: +#if GENERATING_DOCS + /// The type of allocator used. + using allocator_type = Allocator; +#else + using allocator_type = typename + std::allocator_traits:: + template rebind_alloc; +#endif + +private: + enum + { + min_size = 512 + }; + + template + friend class basic_flat_streambuf; + + using alloc_traits = + std::allocator_traits; + + static + inline + std::size_t + dist(char const* first, char const* last) + { + return static_cast(last - first); + } + + char* p_; + char* in_; + char* out_; + char* last_; + char* end_; + std::size_t max_; + +public: + /// The type used to represent the input sequence as a list of buffers. + using const_buffers_type = boost::asio::const_buffers_1; + + /// The type used to represent the output sequence as a list of buffers. + using mutable_buffers_type = boost::asio::mutable_buffers_1; + + /// Copy assignment (disallowed). + basic_flat_streambuf& + operator=(basic_flat_streambuf const&) = delete; + + /// Destructor. + ~basic_flat_streambuf(); + + /** Move constructor. + + The new object will have the same input sequence + and an empty output sequence. + + @note After the move, the moved-from object will + have a capacity of zero, an empty input sequence, + and an empty output sequence. + */ + basic_flat_streambuf(basic_flat_streambuf&&); + + /** Move constructor. + + The new object will have the same input sequence + and an empty output sequence. + + @note After the move, the moved-from object will + have a capacity of zero, an empty input sequence, + and an empty output sequence. + + @param alloc The allocator to associate with the + stream buffer. + */ + basic_flat_streambuf(basic_flat_streambuf&&, + Allocator const& alloc); + + /** Copy constructor. + + The new object will have a copy of the input sequence + and an empty output sequence. + */ + basic_flat_streambuf(basic_flat_streambuf const&); + + /** Copy constructor. + + The new object will have a copy of the input sequence + and an empty output sequence. + + @param alloc The allocator to associate with the + stream buffer. + */ + basic_flat_streambuf(basic_flat_streambuf const&, + Allocator const& alloc); + + /** Copy constructor. + + The new object will have a copy of the input sequence + and an empty output sequence. + */ + template + basic_flat_streambuf( + basic_flat_streambuf const&); + + /** Copy constructor. + + The new object will have a copy of the input sequence + and an empty output sequence. + + @param alloc The allocator to associate with the + stream buffer. + */ + template + basic_flat_streambuf( + basic_flat_streambuf const&, + Allocator const& alloc); + + /** Construct a flat stream buffer. + + No allocation is performed; the buffer will have + empty input and output sequences. + + @param limit An optional non-zero value specifying the + maximum of the sum of the input and output sequence sizes + that can be allocated. If unspecified, the largest + possible value of `std::size_t` is used. + */ + explicit + basic_flat_streambuf(std::size_t limit = ( + std::numeric_limits::max)()); + + /** Construct a flat stream buffer. + + No allocation is performed; the buffer will have + empty input and output sequences. + + @param alloc The allocator to associate with the + stream buffer. + + @param limit An optional non-zero value specifying the + maximum of the sum of the input and output sequence sizes + that can be allocated. If unspecified, the largest + possible value of `std::size_t` is used. + */ + basic_flat_streambuf(Allocator const& alloc, + std::size_t limit = ( + std::numeric_limits::max)()); + + /// Returns a copy of the associated allocator. + allocator_type + get_allocator() const + { + return this->member(); + } + + /// Returns the size of the input sequence. + std::size_t + size() const + { + return dist(in_, out_); + } + + /// Return the maximum sum of the input and output sequence sizes. + std::size_t + max_size() const + { + return max_; + } + + /// Return the maximum sum of input and output sizes that can be held without an allocation. + std::size_t + capacity() const + { + return dist(p_, end_); + } + + /// Get a list of buffers that represent the input sequence. + const_buffers_type + data() const + { + return {in_, dist(in_, out_)}; + } + + /** Get a list of buffers that represent the output sequence, with the given size. + + @throws std::length_error if `size() + n` exceeds `max_size()`. + + @note All previous buffers sequences obtained from + calls to @ref data or @ref prepare are invalidated. + */ + mutable_buffers_type + prepare(std::size_t n); + + /** Move bytes from the output sequence to the input sequence. + + @param n The number of bytes to move. If this is larger than + the number of bytes in the output sequences, then the entire + output sequences is moved. + + @note All previous buffers sequences obtained from + calls to @ref data or @ref prepare are invalidated. + */ + void + commit(std::size_t n) + { + out_ += (std::min)(n, dist(out_, last_)); + } + + /** Remove bytes from the input sequence. + + If `n` is greater than the number of bytes in the input + sequence, all bytes in the input sequence are removed. + + @note All previous buffers sequences obtained from + calls to @ref data or @ref prepare are invalidated. + */ + void + consume(std::size_t n); + + /** Reserve space in the stream. + + This reallocates the buffer if necessary. + + @note All previous buffers sequences obtained from + calls to @ref data or @ref prepare are invalidated. + + @param n The number of bytes to reserve. Upon success, + the capacity will be at least `n`. + + @throws std::length_error if `n` exceeds `max_size()`. + */ + void + reserve(std::size_t n); + + /** Reallocate the buffer to fit the input sequence. + + @note All previous buffers sequences obtained from + calls to @ref data or @ref prepare are invalidated. + */ + void + shrink_to_fit(); + + // Helper for boost::asio::read_until + template + friend + std::size_t + read_size_helper(basic_flat_streambuf< + OtherAlloc> const&, std::size_t); + +private: + void + move_from(basic_flat_streambuf& other); + + template + void + copy_from(basic_flat_streambuf< + OtherAlloc> const& other); +}; + +using flat_streambuf = + basic_flat_streambuf>; + +} // beast + +#include + +#endif diff --git a/include/beast/core/impl/flat_streambuf.ipp b/include/beast/core/impl/flat_streambuf.ipp new file mode 100644 index 00000000..8a9cf79d --- /dev/null +++ b/include/beast/core/impl/flat_streambuf.ipp @@ -0,0 +1,316 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BEAST_IMPL_FLAT_STREAMBUF_HPP +#define BEAST_IMPL_FLAT_STREAMBUF_HPP + +#include +#include + +namespace beast { + +/* Memory is laid out thusly: + + p_ ..|.. in_ ..|.. out_ ..|.. last_ ..|.. end_ +*/ + +namespace detail { + +inline +std::size_t +next_pow2(std::size_t x) +{ + std::size_t n = 0; + while(x > 0) + { + ++n; + x >>= 1; + } + return std::size_t{1} << n; +} + +} // detail + +template +void +basic_flat_streambuf:: +move_from(basic_flat_streambuf& other) +{ + p_ = other.p_; + in_ = other.in_; + out_ = other.out_; + last_ = out_; + end_ = other.end_; + max_ = other.max_; + other.p_ = nullptr; + other.in_ = nullptr; + other.out_ = nullptr; + other.last_ = nullptr; + other.end_ = nullptr; +} + +template +template +void +basic_flat_streambuf:: +copy_from(basic_flat_streambuf< + OtherAlloc> const& other) +{ + max_ = other.max_; + auto const n = other.size(); + if(n > 0) + { + p_ = alloc_traits::allocate( + this->member(), n); + in_ = p_; + out_ = p_ + n; + last_ = out_; + end_ = out_; + std::memcpy(in_, other.in_, n); + return; + } + p_ = nullptr; + in_ = nullptr; + out_ = nullptr; + last_ = nullptr; + end_ = nullptr; +} + +template +basic_flat_streambuf:: +~basic_flat_streambuf() +{ + if(p_) + alloc_traits::deallocate( + this->member(), p_, dist(p_, end_)); +} + +template +basic_flat_streambuf:: +basic_flat_streambuf(basic_flat_streambuf&& other) + : detail::empty_base_optimization< + allocator_type>(std::move(other.member())) +{ + move_from(other); +} + +template +basic_flat_streambuf:: +basic_flat_streambuf(basic_flat_streambuf&& other, + Allocator const& alloc) + : detail::empty_base_optimization< + allocator_type>(alloc) +{ + if(this->member() != other.member()) + { + copy_from(other); + return; + } + move_from(other); +} + +template +basic_flat_streambuf:: +basic_flat_streambuf( + basic_flat_streambuf const& other) + : detail::empty_base_optimization( + alloc_traits::select_on_container_copy_construction( + other.member())) +{ + copy_from(other); +} + +template +basic_flat_streambuf:: +basic_flat_streambuf( + basic_flat_streambuf const& other, + Allocator const& alloc) + : detail::empty_base_optimization< + allocator_type>(alloc) +{ + copy_from(other); +} + +template +template +basic_flat_streambuf:: +basic_flat_streambuf( + basic_flat_streambuf const& other) +{ + copy_from(other); +} + +template +template +basic_flat_streambuf:: +basic_flat_streambuf( + basic_flat_streambuf const& other, + Allocator const& alloc) + : detail::empty_base_optimization< + allocator_type>(alloc) +{ + copy_from(other); +} + +template +basic_flat_streambuf:: +basic_flat_streambuf(std::size_t limit) + : p_(nullptr) + , in_(nullptr) + , out_(nullptr) + , last_(nullptr) + , end_(nullptr) + , max_(limit) +{ + BOOST_ASSERT(limit >= 1); +} + +template +basic_flat_streambuf:: +basic_flat_streambuf(Allocator const& alloc, + std::size_t limit) + : detail::empty_base_optimization< + allocator_type>(alloc) + , p_(nullptr) + , in_(nullptr) + , out_(nullptr) + , last_(nullptr) + , end_(nullptr) + , max_(limit) +{ + BOOST_ASSERT(limit >= 1); +} + +template +auto +basic_flat_streambuf:: +prepare(std::size_t n) -> + mutable_buffers_type +{ + if(n <= dist(out_, end_)) + { + last_ = out_ + n; + return{out_, n}; + } + auto const len = size(); + if(n <= dist(p_, end_) - len) + { + if(len > 0) + std::memmove(p_, in_, len); + in_ = p_; + out_ = in_ + len; + last_ = out_ + n; + return {out_, n}; + } + if(n > max_ - len) + throw std::length_error{ + "flat_streambuf overflow"}; + auto const new_size = (std::min)(max_, + std::max( + detail::next_pow2(len + n), min_size)); + auto const p = alloc_traits::allocate( + this->member(), new_size); + std::memcpy(p, in_, len); + alloc_traits::deallocate( + this->member(), p_, dist(p_, end_)); + p_ = p; + in_ = p_; + out_ = in_ + len; + last_ = out_ + n; + end_ = p_ + new_size; + return {out_, n}; +} + +template +void +basic_flat_streambuf:: +consume(std::size_t n) +{ + if(n >= dist(in_, out_)) + { + in_ = p_; + out_ = p_; + return; + } + in_ += n; +} + +template +void +basic_flat_streambuf:: +reserve(std::size_t n) +{ + if(n <= dist(p_, end_)) + return; + if(n > max_) + throw std::length_error{ + "flat_streambuf overflow"}; + auto const new_size = (std::min)(max_, + std::max( + detail::next_pow2(n), min_size)); + auto const p = alloc_traits::allocate( + this->member(), new_size); + auto const len = size(); + if(len > 0) + std::memcpy(p, in_, len); + alloc_traits::deallocate( + this->member(), p_, dist(p_, end_)); + p_ = p; + in_ = p_; + out_ = p_ + len; + last_ = out_; + end_ = p_ + new_size; +} + +template +void +basic_flat_streambuf:: +shrink_to_fit() +{ + auto const len = size(); + if(len == dist(p_, end_)) + return; + char* p; + if(len > 0) + { + p = alloc_traits::allocate( + this->member(), len); + std::memcpy(p, in_, len); + } + else + { + p = nullptr; + } + alloc_traits::deallocate( + this->member(), p_, dist(p_, end_)); + p_ = p; + in_ = p_; + out_ = p_ + len; + last_ = out_; + end_ = out_; +} + +template +std::size_t +read_size_helper(basic_flat_streambuf< + Allocator> const& fb, std::size_t max_size) +{ + BOOST_ASSERT(max_size >= 1); + auto const len = fb.size(); + auto const avail = fb.capacity() - len; + if (avail > 0) + return (std::min)(avail, max_size); + auto size = (std::min)( + fb.capacity() * 2, fb.max_size()) - len; + if(size == 0) + size = 1; + return (std::min)(size, max_size); +} + +} // beast + +#endif diff --git a/test/Jamfile b/test/Jamfile index 018ec76f..cae47088 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -25,6 +25,7 @@ unit-test core-tests : core/consuming_buffers.cpp core/dynabuf_readstream.cpp core/error.cpp + core/flat_streambuf.cpp core/handler_alloc.cpp core/handler_concepts.cpp core/handler_ptr.cpp diff --git a/test/core/CMakeLists.txt b/test/core/CMakeLists.txt index 766627e3..cf1e24cf 100644 --- a/test/core/CMakeLists.txt +++ b/test/core/CMakeLists.txt @@ -18,6 +18,7 @@ add_executable (core-tests consuming_buffers.cpp dynabuf_readstream.cpp error.cpp + flat_streambuf.cpp handler_alloc.cpp handler_concepts.cpp handler_ptr.cpp diff --git a/test/core/flat_streambuf.cpp b/test/core/flat_streambuf.cpp new file mode 100644 index 00000000..c6c034aa --- /dev/null +++ b/test/core/flat_streambuf.cpp @@ -0,0 +1,171 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +// Test that header file is self-contained. +#include + +#include "buffer_test.hpp" +#include +#include + +namespace beast { + +static_assert(is_DynamicBuffer::value, + "DynamicBuffer requirements not met"); + +class flat_streambuf_test : public beast::unit_test::suite +{ +public: + template + static + bool + eq(basic_flat_streambuf const& sb1, + basic_flat_streambuf const& sb2) + { + return to_string(sb1.data()) == to_string(sb2.data()); + } + + void + testSpecialMembers() + { + using boost::asio::buffer; + using boost::asio::buffer_copy; + { + flat_streambuf fb{10}; + BEAST_EXPECT(fb.max_size() == 10); + } + { + flat_streambuf fb{1024}; + BEAST_EXPECT(fb.max_size() == 1024); + } + std::string const s = "Hello, world!"; + for(std::size_t i = 1; i < s.size() - 1; ++i) + { + flat_streambuf fb{1024}; + fb.commit(buffer_copy( + fb.prepare(i), buffer(s))); + fb.commit(buffer_copy( + fb.prepare(s.size() - i), + buffer(s.data() + i, s.size() - i))); + BEAST_EXPECT(to_string(fb.data()) == s); + { + flat_streambuf fb2{fb}; + BEAST_EXPECT(eq(fb2, fb)); + flat_streambuf fb3{std::move(fb2)}; + BEAST_EXPECT(eq(fb3, fb)); + BEAST_EXPECT(! eq(fb2, fb3)); + BEAST_EXPECT(fb2.size() == 0); + } + + using alloc_type = std::allocator; + using type = + basic_flat_streambuf; + alloc_type alloc; + { + type fba{alloc, 1}; + BEAST_EXPECT(fba.max_size() == 1); + } + { + type fba{alloc, 1024}; + BEAST_EXPECT(fba.max_size() == 1024); + } + { + type fb2{fb}; + BEAST_EXPECT(eq(fb2, fb)); + type fb3{std::move(fb2)}; + BEAST_EXPECT(eq(fb3, fb)); + BEAST_EXPECT(! eq(fb2, fb3)); + BEAST_EXPECT(fb2.size() == 0); + } + { + type fb2{fb, alloc}; + BEAST_EXPECT(eq(fb2, fb)); + type fb3{std::move(fb2), alloc}; + BEAST_EXPECT(eq(fb3, fb)); + BEAST_EXPECT(! eq(fb2, fb3)); + BEAST_EXPECT(fb2.size() == 0); + } + } + } + + void + testStream() + { + using boost::asio::buffer_size; + + flat_streambuf fb{100}; + BEAST_EXPECT(fb.size() == 0); + BEAST_EXPECT(fb.capacity() == 0); + + BEAST_EXPECT(buffer_size(fb.prepare(100)) == 100); + BEAST_EXPECT(fb.size() == 0); + BEAST_EXPECT(fb.capacity() > 0); + + fb.commit(20); + BEAST_EXPECT(fb.size() == 20); + BEAST_EXPECT(fb.capacity() == 100); + + fb.consume(5); + BEAST_EXPECT(fb.size() == 15); + BEAST_EXPECT(fb.capacity() == 100); + + fb.prepare(80); + fb.commit(80); + BEAST_EXPECT(fb.size() == 95); + BEAST_EXPECT(fb.capacity() == 100); + + fb.shrink_to_fit(); + BEAST_EXPECT(fb.size() == 95); + BEAST_EXPECT(fb.capacity() == 95); + } + + void + testPrepare() + { + flat_streambuf fb{100}; + fb.prepare(20); + BEAST_EXPECT(fb.capacity() == 100); + fb.commit(10); + BEAST_EXPECT(fb.capacity() == 100); + fb.consume(4); + BEAST_EXPECT(fb.capacity() == 100); + fb.prepare(14); + BEAST_EXPECT(fb.size() == 6); + BEAST_EXPECT(fb.capacity() == 100); + fb.consume(10); + BEAST_EXPECT(fb.size() == 0); + BEAST_EXPECT(fb.capacity() == 100); + } + + void + testMax() + { + flat_streambuf fb{1}; + try + { + fb.prepare(2); + fail("", __FILE__, __LINE__); + } + catch(std::length_error const&) + { + pass(); + } + } + + void + run() override + { + testSpecialMembers(); + testStream(); + testPrepare(); + testMax(); + } +}; + +BEAST_DEFINE_TESTSUITE(flat_streambuf,core,beast); + +} // beast diff --git a/test/core/streambuf.cpp b/test/core/streambuf.cpp index eb295161..ee162672 100644 --- a/test/core/streambuf.cpp +++ b/test/core/streambuf.cpp @@ -172,8 +172,6 @@ public: void testSpecialMembers() { using boost::asio::buffer; - using boost::asio::buffer_cast; - using boost::asio::buffer_size; std::string const s = "Hello, world"; BEAST_EXPECT(s.size() == 12); for(std::size_t i = 1; i < 12; ++i) { @@ -263,7 +261,6 @@ public: void testCommit() { - using boost::asio::buffer_size; streambuf sb(2); sb.prepare(2); sb.prepare(5); @@ -273,7 +270,6 @@ public: void testConsume() { - using boost::asio::buffer_size; streambuf sb(1); expect_size(5, sb.prepare(5)); sb.commit(3); @@ -285,7 +281,6 @@ public: void testMatrix() { using boost::asio::buffer; - using boost::asio::buffer_cast; using boost::asio::buffer_size; std::string const s = "Hello, world"; BEAST_EXPECT(s.size() == 12);