From 74b99014d28830db89d2f33febabc123b7f5cc2b Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Tue, 28 Oct 2014 12:30:30 -0700 Subject: [PATCH] Add beast::asio::basic_streambuf (RIPD-661): This is class whose interface is identical to the boost::asio::basic_streambuf, and uses an implementation that stores the data in multiple discontiguous linear buffers, expanding and shrinking as needed. --- Builds/VisualStudio2013/RippleD.vcxproj | 2 + .../VisualStudio2013/RippleD.vcxproj.filters | 3 + src/beast/beast/asio/streambuf.h | 653 ++++++++++++++++++ 3 files changed, 658 insertions(+) create mode 100644 src/beast/beast/asio/streambuf.h diff --git a/Builds/VisualStudio2013/RippleD.vcxproj b/Builds/VisualStudio2013/RippleD.vcxproj index 6a2e5c948..47ce73943 100644 --- a/Builds/VisualStudio2013/RippleD.vcxproj +++ b/Builds/VisualStudio2013/RippleD.vcxproj @@ -148,6 +148,8 @@ + + True diff --git a/Builds/VisualStudio2013/RippleD.vcxproj.filters b/Builds/VisualStudio2013/RippleD.vcxproj.filters index 3fe0bc9fb..d4c7d4505 100644 --- a/Builds/VisualStudio2013/RippleD.vcxproj.filters +++ b/Builds/VisualStudio2013/RippleD.vcxproj.filters @@ -618,6 +618,9 @@ beast\asio + + beast\asio + beast\asio\tests diff --git a/src/beast/beast/asio/streambuf.h b/src/beast/beast/asio/streambuf.h new file mode 100644 index 000000000..eb7e4401e --- /dev/null +++ b/src/beast/beast/asio/streambuf.h @@ -0,0 +1,653 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_ASIO_STREAMBUF_H_INCLUDED +#define BEAST_ASIO_STREAMBUF_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include // +#include + +namespace beast { +namespace asio { + +/** Implements asio::streambuf interface using multiple buffers. */ +template +class basic_streambuf +{ +public: + using size_type = typename std::allocator_traits::size_type; + using const_buffer = boost::asio::const_buffer; + using mutable_buffer = boost::asio::mutable_buffer; + +private: + class element; + + using alloc_traits = std::allocator_traits; + using list_type = typename boost::intrusive::make_list >::type; + using iterator = typename list_type::iterator; + using const_iterator = typename list_type::const_iterator; + + /* These diagrams illustrate the layout and state variables. + + Input and output sequences are contained entirely in one element: + + out_ + |<-----+----------+-------------+-------->| + 0 in_pos_ out_pos_ out_end_ + + + Output sequence is entirely contained in the second element: + + out_ + |<------------+------------>| |<----+----------+--------->| + 0 in_pos_ out_pos_ out_end_ + + + Output sequence occupies the second and third elements: + + out_ + |<-----+-------->| |<-------+------>| |<-----+--------->| + 0 in_pos_ out_pos_ out_end_ + + + Input sequence is empty: + + out_ + |<------+------------------>| |<-----------+------------->| + out_pos_ out_end_ + in_pos_ + + + Output sequence is empty: + + out_ + |<------+------------------>| |<------+------------------>| + in_pos_ out_pos_ + out_end_ + + + Normally if the output is empty but there is an element in out_, + out_pos_ and out_end_ will be set to zero. Except after comitting + everything, and causing the output sequence to start at the + last element. In this case out_pos_ becomes out_->size(), + and the result looks like this: + + out_ + |<------+------------------>| |<------------------------->| + in_pos_ out_pos_ + out_end_ + */ + + list_type list_; + Allocator alloc_; + size_type block_size_; + size_type block_size_next_; + size_type in_size_ = 0; // size of the input sequence + iterator out_; // element that contains out_pos_ + size_type in_pos_ = 0; // input offset in list_.front() + size_type out_pos_ = 0; // output offset in *out_ + size_type out_end_ = 0; // output end offset in list_.back() + +public: + class const_buffers_type; + class mutable_buffers_type; + + basic_streambuf (basic_streambuf const& other) = delete; + basic_streambuf& operator= (basic_streambuf const& other) = delete; + basic_streambuf& operator= (basic_streambuf&& other) = delete; + + ~basic_streambuf(); + + explicit + basic_streambuf(std::size_t block_size = 16*1024, + Allocator const& alloc = Allocator{}); + + basic_streambuf (basic_streambuf&& other); + + /** Get the maximum size of the basic_streambuf. */ + size_type + max_size() const + { + return std::numeric_limits::max(); + } + + /** Get the size of the input sequence. */ + size_type + size() const + { + return in_size_; + } + + /** Get a list of buffers that represents the output sequence, with the given size. */ + mutable_buffers_type + prepare (size_type n); + + /** Move bytes from the output sequence to the input sequence. */ + void + commit (size_type n); + + /** Get a list of buffers that represents the input sequence. */ + const_buffers_type + data() const; + + /** Remove bytes from the input sequence. */ + void + consume (size_type n); + +private: + void + debug_check() const; +}; + +//------------------------------------------------------------------------------ + +template +class basic_streambuf::element + : public boost::intrusive::list_base_hook < + boost::intrusive::link_mode > +{ +private: + size_type const size_; // size of the allocation minus sizeof(element) + +public: + element (element const&) = delete; + element& operator= (element const&) = delete; + + explicit + element (size_type block_size) + : size_(block_size) + { } + + size_type + size() const + { + return size_; + } + + size_type + alloc_size() const + { + return size_ + sizeof(*this); + } + + char* + data() + { + return reinterpret_cast(this+1); + } + + char const* + data() const + { + return reinterpret_cast(this+1); + } +}; + +//------------------------------------------------------------------------------ + +template +class basic_streambuf::const_buffers_type +{ +public: + using value_type = const_buffer; + +private: + struct transform + { + using argument_type = element; + using result_type = value_type; + + const_buffers_type const& buffers_; + + explicit + transform (const_buffers_type const& buffers) + : buffers_(buffers) + { } + + value_type + operator() (element const& e) const; + }; + + typename list_type::const_iterator begin_; + typename list_type::const_iterator end_; + size_type in_pos_ = 0; + size_type out_pos_ = 0; + +public: + using const_iterator = boost::transform_iterator< + transform, typename list_type::const_iterator, + value_type, value_type>; + + const_iterator + begin() const + { + return const_iterator(begin_,transform(*this)); + } + + const_iterator + end() const + { + return const_iterator(end_,transform(*this)); + } + +private: + friend class basic_streambuf; + const_buffers_type (typename list_type::const_iterator first, + typename list_type::const_iterator last, size_type in_pos, + size_type out_pos); +}; + +template +basic_streambuf::const_buffers_type::const_buffers_type ( + typename list_type::const_iterator first, + typename list_type::const_iterator last, size_type in_pos, + size_type out_pos) + : begin_(first) + , end_(last) + , in_pos_(in_pos) + , out_pos_(out_pos) + { } + +template +auto +basic_streambuf::const_buffers_type:: + transform::operator() (element const& e) const -> + value_type +{ + return value_type(e.data(), + (&e == &*std::prev(buffers_.end_)) ? buffers_.out_pos_ : e.size()) + + ((&e == &*buffers_.begin_) ? buffers_.in_pos_ : 0); +} + +//------------------------------------------------------------------------------ + +template +class basic_streambuf::mutable_buffers_type +{ +public: + using value_type = mutable_buffer; + +private: + struct transform + { + using argument_type = element; + using result_type = value_type; + + mutable_buffers_type const& buffers_; + + explicit + transform (mutable_buffers_type const& buffers) + : buffers_(buffers) + { + } + + value_type + operator() (element& e) const; + }; + + typename list_type::iterator begin_; + typename list_type::iterator end_; + size_type out_pos_ = 0; + size_type out_end_ = 0; + +public: + using const_iterator = boost::transform_iterator< + transform, typename list_type::iterator, + value_type, value_type>; + + const_iterator + begin() const + { + return const_iterator(begin_,transform(*this)); + } + + const_iterator + end() const + { + return const_iterator(end_,transform(*this)); + } + +private: + friend class basic_streambuf; + mutable_buffers_type (typename list_type::iterator first, + typename list_type::iterator last, size_type out_pos, + size_type out_end); +}; + +template +basic_streambuf::mutable_buffers_type::mutable_buffers_type ( + typename list_type::iterator first, + typename list_type::iterator last, size_type out_pos, + size_type out_end) + : begin_(first) + , end_(last) + , out_pos_(out_pos) + , out_end_(out_end) + { } + +template +auto +basic_streambuf::mutable_buffers_type:: + transform::operator() (element& e) const -> + value_type +{ + return value_type(e.data(), + (&e == &*std::prev(buffers_.end_)) ? buffers_.out_end_ : e.size()) + + ((&e == &*buffers_.begin_) ? buffers_.out_pos_ : 0); +} + +//------------------------------------------------------------------------------ + +template +basic_streambuf::~basic_streambuf() +{ + for(auto iter = list_.begin(); iter != list_.end();) + { + auto& e = *iter++; + size_type const n = e.alloc_size(); + alloc_traits::destroy(alloc_, &e); + alloc_traits::deallocate(alloc_, + reinterpret_cast(&e), n); + } +} + +template +basic_streambuf::basic_streambuf(std::size_t block_size, + Allocator const& alloc) + : alloc_(alloc) + , block_size_(block_size) + , block_size_next_(block_size) + , out_(list_.end()) +{ + if (! (block_size > 0)) + throw std::invalid_argument( + "basic_streambuf: invalid block_size"); +} + +template +basic_streambuf::basic_streambuf (basic_streambuf&& other) + : list_(std::move(other.list_)) + , alloc_(std::move(other.alloc_)) + , block_size_(other.block_size_) + , block_size_next_(other.block_size_next_) + , in_size_(other.in_size_) + , out_(other.out_) + , in_pos_(other.in_pos_) + , out_pos_(other.out_pos_) + , out_end_(other.out_end_) +{ + other.in_size_ = 0; + other.out_ = other.list_.end(); + other.in_pos_ = 0; + other.out_pos_ = 0; + other.out_end_ = 0; +} + +template +auto +basic_streambuf::prepare (size_type n) -> + mutable_buffers_type +{ + debug_check(); + + iterator last = out_; + + if (last != list_.end()) + { + size_type const avail = last->size() - out_pos_; + if (n > avail) + { + n -= avail; + while (++last != list_.end()) + { + if (n <= last->size()) + { + ++last; + out_end_ = n; + n = 0; + debug_check(); + break; + } + n -= last->size(); + } + } + else + { + ++last; + out_end_ = out_pos_ + n; + n = 0; + debug_check(); + } + } + + if (n > 0) + { + assert(last == list_.end()); + for(;;) + { + size_type const avail = block_size_next_; + element& e = *reinterpret_cast(alloc_traits::allocate( + alloc_, avail + sizeof(element))); + alloc_traits::construct(alloc_, &e, avail); + list_.push_back(e); + if (out_ == list_.end()) + { + out_ = list_.iterator_to(e); + debug_check(); + } + if (n <= avail) + { + out_end_ = n; + debug_check(); + n = 0; + break; + } + n -= avail; + } + last = list_.end(); + } + else + { + while (last != list_.end()) + { + element& e = *last++; + list_.erase(list_.iterator_to(e)); + size_type const len = e.alloc_size(); + alloc_traits::destroy(alloc_, &e); + alloc_traits::deallocate(alloc_, + reinterpret_cast(&e), len); + // do we set out_ to list_.end() if empty? + } + } + + return mutable_buffers_type(out_, last, out_pos_, out_end_); +} + +template +void +basic_streambuf::commit (size_type n) +{ + debug_check(); + + if (! list_.empty()) + { + auto const last = std::prev(list_.end()); + while(out_ != last) + { + size_type const avail = + out_->size() - out_pos_; + if (n < avail) + { + in_size_ += n; + out_pos_ += n; + debug_check(); + return; + } + n -= avail; + in_size_ += avail; + out_pos_ = 0; + ++out_; + debug_check(); + } + + assert(out_ != list_.end()); + size_type const avail = + out_end_ - out_pos_; + if (n > avail) + n = avail; + // out_pos_ can become out_->size() here (*) + in_size_ += n; + out_pos_ += n; + debug_check(); + } +} + +template +auto +basic_streambuf::data() const -> + const_buffers_type +{ + debug_check(); + if (out_ == list_.end()) + return const_buffers_type(list_.begin(), list_.end(), + in_pos_, out_pos_); + return const_buffers_type(list_.begin(), std::next(out_), + in_pos_, out_pos_); +} + +template +void +basic_streambuf::consume (size_type n) +{ + debug_check(); + if (out_ == list_.end()) + return; + + auto iter = list_.begin(); + for(;;) + { + if (iter != out_) + { + size_type const avail = iter->size() - in_pos_; + if (n < avail) + { + in_size_ -= n; + in_pos_ += n; + debug_check(); + break; + } + n -= avail; + in_size_ -= avail; + in_pos_ = 0; + debug_check(); + + element& e = *iter++; + list_.erase(list_.iterator_to(e)); + size_type const len = e.alloc_size(); + alloc_traits::destroy(alloc_, &e); + alloc_traits::deallocate(alloc_, + reinterpret_cast(&e), len); + } + else + { + size_type const avail = out_pos_ - in_pos_; + if (n < avail) + { + in_size_ -= n; + in_pos_ += n; + debug_check(); + } + else + { + auto const back = list_.iterator_to(list_.back()); + if (out_ != back && out_pos_ == out_->size()) + { + element& e = *out_++; + list_.erase(list_.iterator_to(e)); + size_type const len = e.alloc_size(); + alloc_traits::destroy(alloc_, &e); + alloc_traits::deallocate(alloc_, + reinterpret_cast(&e), len); + out_pos_ = 0; + } + else if (out_ == back && out_pos_ == out_end_) + { + element& e = *out_++; + list_.erase(list_.iterator_to(e)); + size_type const len = e.alloc_size(); + alloc_traits::destroy(alloc_, &e); + alloc_traits::deallocate(alloc_, + reinterpret_cast(&e), len); + out_pos_ = 0; + out_end_ = 0; + } + in_size_ -= avail; + in_pos_ = out_pos_; + debug_check(); + } + break; + } + } +} + +template +void +basic_streambuf::debug_check() const +{ +#ifndef NDEBUG + if (out_ == list_.end()) + { + assert(in_size_ == 0); + assert(in_pos_ == 0); + assert(out_pos_ == 0); + assert(out_end_ == 0); + return; + } + + assert(! list_.empty()); + + auto const& out = *out_; + auto const& back = list_.back(); + auto const& front = list_.front(); + + assert(in_pos_ < front.size()); + assert(out_end_ <= back.size()); + + assert(&out != &front || out_pos_ >= in_pos_); + assert(&out != &back || out_end_ <= back.size()); + assert(&out != &back || out_pos_ <= back.size()); + assert(&out == &back || out_pos_ < back.size()); +#endif +} + +//------------------------------------------------------------------------------ + +using streambuf = basic_streambuf>; + +} +} + +#endif