boost/beast/core/impl/multi_buffer.ipp
// // Copyright (c) 2016-2017 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) // // Official repository: https://github.com/boostorg/beast // #ifndef BOOST_BEAST_IMPL_MULTI_BUFFER_IPP #define BOOST_BEAST_IMPL_MULTI_BUFFER_IPP #include <boost/beast/core/detail/type_traits.hpp> #include <boost/assert.hpp> #include <boost/throw_exception.hpp> #include <algorithm> #include <exception> #include <sstream> #include <string> #include <utility> namespace boost { namespace beast { /* These diagrams illustrate the layout and state variables. 1 Input and output contained entirely in one element: 0 out_ |<-------------+------------------------------------------->| in_pos_ out_pos_ out_end_ 2 Output contained in first and second elements: out_ |<------+----------+------->| |<----------+-------------->| in_pos_ out_pos_ out_end_ 3 Output contained in the second element: out_ |<------------+------------>| |<----+-------------------->| in_pos_ out_pos_ out_end_ 4 Output contained in second and third elements: out_ |<-----+-------->| |<-------+------>| |<--------------->| in_pos_ out_pos_ out_end_ 5 Input sequence is empty: out_ |<------+------------------>| |<-----------+------------->| out_pos_ out_end_ in_pos_ 6 Output sequence is empty: out_ |<------+------------------>| |<------+------------------>| in_pos_ out_pos_ out_end_ 7 The end of output can point to the end of an element. But out_pos_ should never point to the end: out_ |<------+------------------>| |<------+------------------>| in_pos_ out_pos_ out_end_ 8 When the input sequence entirely fills the last element and the output sequence is empty, out_ will point to the end of the list of buffers, and out_pos_ and out_end_ will be 0: |<------+------------------>| out_ == list_.end() in_pos_ out_pos_ == 0 out_end_ == 0 */ template<class Allocator> class basic_multi_buffer<Allocator>::element : public boost::intrusive::list_base_hook< boost::intrusive::link_mode< boost::intrusive::normal_link>> { using size_type = typename detail::allocator_traits<Allocator>::size_type; size_type const size_; public: element(element const&) = delete; element& operator=(element const&) = delete; explicit element(size_type n) : size_(n) { } size_type size() const { return size_; } char* data() const { return const_cast<char*>( reinterpret_cast<char const*>(this+1)); } }; template<class Allocator> class basic_multi_buffer<Allocator>::const_buffers_type { basic_multi_buffer const* b_; friend class basic_multi_buffer; explicit const_buffers_type(basic_multi_buffer const& b); public: using value_type = boost::asio::mutable_buffer; class const_iterator; const_buffers_type() = delete; const_buffers_type(const_buffers_type const&) = default; const_buffers_type& operator=(const_buffers_type const&) = default; const_iterator begin() const; const_iterator end() const; friend std::size_t buffer_size(const_buffers_type const& buffers) { return buffers.b_->size(); } }; template<class Allocator> class basic_multi_buffer<Allocator>::mutable_buffers_type { basic_multi_buffer const* b_; friend class basic_multi_buffer; explicit mutable_buffers_type(basic_multi_buffer const& b); public: using value_type = mutable_buffer; class const_iterator; mutable_buffers_type() = delete; mutable_buffers_type(mutable_buffers_type const&) = default; mutable_buffers_type& operator=(mutable_buffers_type const&) = default; const_iterator begin() const; const_iterator end() const; }; //------------------------------------------------------------------------------ template<class Allocator> class basic_multi_buffer<Allocator>::const_buffers_type::const_iterator { basic_multi_buffer const* b_ = nullptr; typename list_type::const_iterator it_; public: using value_type = typename const_buffers_type::value_type; using pointer = value_type const*; using reference = value_type; using difference_type = std::ptrdiff_t; using iterator_category = std::bidirectional_iterator_tag; const_iterator() = default; const_iterator(const_iterator&& other) = default; const_iterator(const_iterator const& other) = default; const_iterator& operator=(const_iterator&& other) = default; const_iterator& operator=(const_iterator const& other) = default; const_iterator(basic_multi_buffer const& b, typename list_type::const_iterator const& it) : b_(&b) , it_(it) { } bool operator==(const_iterator const& other) const { return b_ == other.b_ && it_ == other.it_; } bool operator!=(const_iterator const& other) const { return !(*this == other); } reference operator*() const { auto const& e = *it_; return value_type{e.data(), (b_->out_ == b_->list_.end() || &e != &*b_->out_) ? e.size() : b_->out_pos_} + (&e == &*b_->list_.begin() ? b_->in_pos_ : 0); } pointer operator->() const = delete; const_iterator& operator++() { ++it_; return *this; } const_iterator operator++(int) { auto temp = *this; ++(*this); return temp; } const_iterator& operator--() { --it_; return *this; } const_iterator operator--(int) { auto temp = *this; --(*this); return temp; } }; template<class Allocator> basic_multi_buffer<Allocator>:: const_buffers_type:: const_buffers_type( basic_multi_buffer const& b) : b_(&b) { } template<class Allocator> auto basic_multi_buffer<Allocator>:: const_buffers_type:: begin() const -> const_iterator { return const_iterator{*b_, b_->list_.begin()}; } template<class Allocator> auto basic_multi_buffer<Allocator>:: const_buffers_type:: end() const -> const_iterator { return const_iterator{*b_, b_->out_ == b_->list_.end() ? b_->list_.end() : std::next(b_->out_)}; } //------------------------------------------------------------------------------ template<class Allocator> class basic_multi_buffer<Allocator>::mutable_buffers_type::const_iterator { basic_multi_buffer const* b_ = nullptr; typename list_type::const_iterator it_; public: using value_type = typename mutable_buffers_type::value_type; using pointer = value_type const*; using reference = value_type; using difference_type = std::ptrdiff_t; using iterator_category = std::bidirectional_iterator_tag; const_iterator() = default; const_iterator(const_iterator&& other) = default; const_iterator(const_iterator const& other) = default; const_iterator& operator=(const_iterator&& other) = default; const_iterator& operator=(const_iterator const& other) = default; const_iterator(basic_multi_buffer const& b, typename list_type::const_iterator const& it) : b_(&b) , it_(it) { } bool operator==(const_iterator const& other) const { return b_ == other.b_ && it_ == other.it_; } bool operator!=(const_iterator const& other) const { return !(*this == other); } reference operator*() const { auto const& e = *it_; return value_type{e.data(), &e == &*std::prev(b_->list_.end()) ? b_->out_end_ : e.size()} + (&e == &*b_->out_ ? b_->out_pos_ : 0); } pointer operator->() const = delete; const_iterator& operator++() { ++it_; return *this; } const_iterator operator++(int) { auto temp = *this; ++(*this); return temp; } const_iterator& operator--() { --it_; return *this; } const_iterator operator--(int) { auto temp = *this; --(*this); return temp; } }; template<class Allocator> basic_multi_buffer<Allocator>:: mutable_buffers_type:: mutable_buffers_type( basic_multi_buffer const& b) : b_(&b) { } template<class Allocator> auto basic_multi_buffer<Allocator>:: mutable_buffers_type:: begin() const -> const_iterator { return const_iterator{*b_, b_->out_}; } template<class Allocator> auto basic_multi_buffer<Allocator>:: mutable_buffers_type:: end() const -> const_iterator { return const_iterator{*b_, b_->list_.end()}; } //------------------------------------------------------------------------------ template<class Allocator> basic_multi_buffer<Allocator>:: ~basic_multi_buffer() { delete_list(); } template<class Allocator> basic_multi_buffer<Allocator>:: basic_multi_buffer() : out_(list_.end()) { } template<class Allocator> basic_multi_buffer<Allocator>:: basic_multi_buffer(std::size_t limit) : max_(limit) , out_(list_.end()) { } template<class Allocator> basic_multi_buffer<Allocator>:: basic_multi_buffer(Allocator const& alloc) : detail::empty_base_optimization< base_alloc_type>(alloc) , out_(list_.end()) { } template<class Allocator> basic_multi_buffer<Allocator>:: basic_multi_buffer(std::size_t limit, Allocator const& alloc) : detail::empty_base_optimization< base_alloc_type>(alloc) , max_(limit) , out_(list_.end()) { } template<class Allocator> basic_multi_buffer<Allocator>:: basic_multi_buffer(basic_multi_buffer&& other) : detail::empty_base_optimization< base_alloc_type>(std::move(other.member())) , max_(other.max_) , in_size_(other.in_size_) , in_pos_(other.in_pos_) , out_pos_(other.out_pos_) , out_end_(other.out_end_) { auto const at_end = other.out_ == other.list_.end(); list_ = std::move(other.list_); out_ = at_end ? list_.end() : other.out_; other.in_size_ = 0; other.out_ = other.list_.end(); other.in_pos_ = 0; other.out_pos_ = 0; other.out_end_ = 0; } template<class Allocator> basic_multi_buffer<Allocator>:: basic_multi_buffer(basic_multi_buffer&& other, Allocator const& alloc) : detail::empty_base_optimization< base_alloc_type>(alloc) , max_(other.max_) { if(this->member() != other.member()) { out_ = list_.end(); copy_from(other); other.reset(); } else { auto const at_end = other.out_ == other.list_.end(); list_ = std::move(other.list_); out_ = at_end ? list_.end() : other.out_; in_size_ = other.in_size_; 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<class Allocator> basic_multi_buffer<Allocator>:: basic_multi_buffer(basic_multi_buffer const& other) : detail::empty_base_optimization< base_alloc_type>(alloc_traits:: select_on_container_copy_construction( other.member())) , max_(other.max_) , out_(list_.end()) { copy_from(other); } template<class Allocator> basic_multi_buffer<Allocator>:: basic_multi_buffer(basic_multi_buffer const& other, Allocator const& alloc) : detail::empty_base_optimization< base_alloc_type>(alloc) , max_(other.max_) , out_(list_.end()) { copy_from(other); } template<class Allocator> template<class OtherAlloc> basic_multi_buffer<Allocator>:: basic_multi_buffer( basic_multi_buffer<OtherAlloc> const& other) : out_(list_.end()) { copy_from(other); } template<class Allocator> template<class OtherAlloc> basic_multi_buffer<Allocator>:: basic_multi_buffer( basic_multi_buffer<OtherAlloc> const& other, allocator_type const& alloc) : detail::empty_base_optimization< base_alloc_type>(alloc) , max_(other.max_) , out_(list_.end()) { copy_from(other); } template<class Allocator> auto basic_multi_buffer<Allocator>:: operator=(basic_multi_buffer&& other) -> basic_multi_buffer& { if(this == &other) return *this; reset(); max_ = other.max_; move_assign(other, std::integral_constant<bool, alloc_traits::propagate_on_container_move_assignment::value>{}); return *this; } template<class Allocator> auto basic_multi_buffer<Allocator>:: operator=(basic_multi_buffer const& other) -> basic_multi_buffer& { if(this == &other) return *this; copy_assign(other, std::integral_constant<bool, alloc_traits::propagate_on_container_copy_assignment::value>{}); return *this; } template<class Allocator> template<class OtherAlloc> auto basic_multi_buffer<Allocator>:: operator=( basic_multi_buffer<OtherAlloc> const& other) -> basic_multi_buffer& { reset(); max_ = other.max_; copy_from(other); return *this; } template<class Allocator> std::size_t basic_multi_buffer<Allocator>:: capacity() const { auto pos = out_; if(pos == list_.end()) return in_size_; auto n = pos->size() - out_pos_; while(++pos != list_.end()) n += pos->size(); return in_size_ + n; } template<class Allocator> auto basic_multi_buffer<Allocator>:: data() const -> const_buffers_type { return const_buffers_type(*this); } template<class Allocator> auto basic_multi_buffer<Allocator>:: prepare(size_type n) -> mutable_buffers_type { if(in_size_ + n > max_) BOOST_THROW_EXCEPTION(std::length_error{ "dynamic buffer overflow"}); list_type reuse; std::size_t total = in_size_; // put all empty buffers on reuse list if(out_ != list_.end()) { total += out_->size() - out_pos_; if(out_ != list_.iterator_to(list_.back())) { out_end_ = out_->size(); reuse.splice(reuse.end(), list_, std::next(out_), list_.end()); #if BOOST_BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); #endif } auto const avail = out_->size() - out_pos_; if(n > avail) { out_end_ = out_->size(); n -= avail; } else { out_end_ = out_pos_ + n; n = 0; } #if BOOST_BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); #endif } // get space from reuse buffers while(n > 0 && ! reuse.empty()) { auto& e = reuse.front(); reuse.erase(reuse.iterator_to(e)); list_.push_back(e); total += e.size(); if(n > e.size()) { out_end_ = e.size(); n -= e.size(); } else { out_end_ = n; n = 0; } #if BOOST_BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); #endif } BOOST_ASSERT(total <= max_); if(! reuse.empty() || n > 0) { for(auto it = reuse.begin(); it != reuse.end();) { auto& e = *it++; reuse.erase(list_.iterator_to(e)); auto const len = sizeof(e) + e.size(); alloc_traits::destroy(this->member(), &e); alloc_traits::deallocate(this->member(), reinterpret_cast<char*>(&e), len); } if(n > 0) { static auto const growth_factor = 2.0f; auto const size = (std::min<std::size_t>)( max_ - total, (std::max<std::size_t>)({ static_cast<std::size_t>( in_size_ * growth_factor - in_size_), 512, n})); auto& e = *reinterpret_cast<element*>(static_cast< void*>(alloc_traits::allocate(this->member(), sizeof(element) + size))); alloc_traits::construct(this->member(), &e, size); list_.push_back(e); if(out_ == list_.end()) out_ = list_.iterator_to(e); out_end_ = n; #if BOOST_BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); #endif } } return mutable_buffers_type(*this); } template<class Allocator> void basic_multi_buffer<Allocator>:: commit(size_type n) { if(list_.empty()) return; if(out_ == list_.end()) return; auto const back = list_.iterator_to(list_.back()); while(out_ != back) { auto const avail = out_->size() - out_pos_; if(n < avail) { out_pos_ += n; in_size_ += n; #if BOOST_BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); #endif return; } ++out_; n -= avail; out_pos_ = 0; in_size_ += avail; #if BOOST_BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); #endif } n = (std::min)(n, out_end_ - out_pos_); out_pos_ += n; in_size_ += n; if(out_pos_ == out_->size()) { ++out_; out_pos_ = 0; out_end_ = 0; } #if BOOST_BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); #endif } template<class Allocator> void basic_multi_buffer<Allocator>:: consume(size_type n) { if(list_.empty()) return; for(;;) { if(list_.begin() != out_) { auto const avail = list_.front().size() - in_pos_; if(n < avail) { in_size_ -= n; in_pos_ += n; #if BOOST_BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); #endif break; } n -= avail; in_size_ -= avail; in_pos_ = 0; auto& e = list_.front(); list_.erase(list_.iterator_to(e)); auto const len = sizeof(e) + e.size(); alloc_traits::destroy(this->member(), &e); alloc_traits::deallocate(this->member(), reinterpret_cast<char*>(&e), len); #if BOOST_BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); #endif } else { auto const avail = out_pos_ - in_pos_; if(n < avail) { in_size_ -= n; in_pos_ += n; } else { in_size_ = 0; if(out_ != list_.iterator_to(list_.back()) || out_pos_ != out_end_) { in_pos_ = out_pos_; } else { // Input and output sequences are empty, reuse buffer. // Alternatively we could deallocate it. in_pos_ = 0; out_pos_ = 0; out_end_ = 0; } } #if BOOST_BEAST_MULTI_BUFFER_DEBUG_CHECK debug_check(); #endif break; } } } template<class Allocator> inline void basic_multi_buffer<Allocator>:: delete_list() { for(auto iter = list_.begin(); iter != list_.end();) { auto& e = *iter++; auto const len = sizeof(e) + e.size(); alloc_traits::destroy(this->member(), &e); alloc_traits::deallocate(this->member(), reinterpret_cast<char*>(&e), len); } } template<class Allocator> inline void basic_multi_buffer<Allocator>:: reset() { delete_list(); list_.clear(); out_ = list_.end(); in_size_ = 0; in_pos_ = 0; out_pos_ = 0; out_end_ = 0; } template<class Allocator> template<class DynamicBuffer> inline void basic_multi_buffer<Allocator>:: copy_from(DynamicBuffer const& buffer) { if(buffer.size() == 0) return; using boost::asio::buffer_copy; commit(buffer_copy( prepare(buffer.size()), buffer.data())); } template<class Allocator> inline void basic_multi_buffer<Allocator>:: move_assign(basic_multi_buffer& other, std::false_type) { if(this->member() != other.member()) { copy_from(other); other.reset(); } else { move_assign(other, std::true_type{}); } } template<class Allocator> inline void basic_multi_buffer<Allocator>:: move_assign(basic_multi_buffer& other, std::true_type) { this->member() = std::move(other.member()); auto const at_end = other.out_ == other.list_.end(); list_ = std::move(other.list_); out_ = at_end ? list_.end() : other.out_; in_size_ = other.in_size_; 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<class Allocator> inline void basic_multi_buffer<Allocator>:: copy_assign( basic_multi_buffer const& other, std::false_type) { reset(); max_ = other.max_; copy_from(other); } template<class Allocator> inline void basic_multi_buffer<Allocator>:: copy_assign( basic_multi_buffer const& other, std::true_type) { reset(); max_ = other.max_; this->member() = other.member(); copy_from(other); } template<class Allocator> inline void basic_multi_buffer<Allocator>:: swap(basic_multi_buffer& other) { swap(other, typename alloc_traits::propagate_on_container_swap{}); } template<class Allocator> inline void basic_multi_buffer<Allocator>:: swap(basic_multi_buffer& other, std::true_type) { using std::swap; auto const at_end0 = out_ == list_.end(); auto const at_end1 = other.out_ == other.list_.end(); swap(this->member(), other.member()); swap(list_, other.list_); swap(out_, other.out_); if(at_end1) out_ = list_.end(); if(at_end0) other.out_ = other.list_.end(); swap(in_size_, other.in_size_); swap(in_pos_, other.in_pos_); swap(out_pos_, other.out_pos_); swap(out_end_, other.out_end_); } template<class Allocator> inline void basic_multi_buffer<Allocator>:: swap(basic_multi_buffer& other, std::false_type) { BOOST_ASSERT(this->member() == other.member()); using std::swap; auto const at_end0 = out_ == list_.end(); auto const at_end1 = other.out_ == other.list_.end(); swap(list_, other.list_); swap(out_, other.out_); if(at_end1) out_ = list_.end(); if(at_end0) other.out_ = other.list_.end(); swap(in_size_, other.in_size_); swap(in_pos_, other.in_pos_); swap(out_pos_, other.out_pos_); swap(out_end_, other.out_end_); } template<class Allocator> void swap( basic_multi_buffer<Allocator>& lhs, basic_multi_buffer<Allocator>& rhs) { lhs.swap(rhs); } template<class Allocator> void basic_multi_buffer<Allocator>:: debug_check() const { #ifndef NDEBUG using boost::asio::buffer_size; BOOST_ASSERT(buffer_size(data()) == in_size_); if(list_.empty()) { BOOST_ASSERT(in_pos_ == 0); BOOST_ASSERT(in_size_ == 0); BOOST_ASSERT(out_pos_ == 0); BOOST_ASSERT(out_end_ == 0); BOOST_ASSERT(out_ == list_.end()); return; } auto const& front = list_.front(); BOOST_ASSERT(in_pos_ < front.size()); if(out_ == list_.end()) { BOOST_ASSERT(out_pos_ == 0); BOOST_ASSERT(out_end_ == 0); } else { auto const& out = *out_; auto const& back = list_.back(); BOOST_ASSERT(out_end_ <= back.size()); BOOST_ASSERT(out_pos_ < out.size()); BOOST_ASSERT(&out != &front || out_pos_ >= in_pos_); BOOST_ASSERT(&out != &front || out_pos_ - in_pos_ == in_size_); BOOST_ASSERT(&out != &back || out_pos_ <= out_end_); } #endif } } // beast } // boost #endif