2 // Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
8 #ifndef BEAST_IMPL_STREAMBUF_IPP
9 #define BEAST_IMPL_STREAMBUF_IPP
11 #include <beast/core/detail/type_traits.hpp>
12 #include <beast/core/detail/write_dynabuf.hpp>
13 #include <boost/assert.hpp>
22 /* These diagrams illustrate the layout and state variables.
24 1 Input and output contained entirely in one element:
27 |<-------------+------------------------------------------->|
28 in_pos_ out_pos_ out_end_
31 2 Output contained in first and second elements:
34 |<------+----------+------->| |<----------+-------------->|
35 in_pos_ out_pos_ out_end_
38 3 Output contained in the second element:
41 |<------------+------------>| |<----+-------------------->|
42 in_pos_ out_pos_ out_end_
45 4 Output contained in second and third elements:
48 |<-----+-------->| |<-------+------>| |<--------------->|
49 in_pos_ out_pos_ out_end_
52 5 Input sequence is empty:
55 |<------+------------------>| |<-----------+------------->|
60 6 Output sequence is empty:
63 |<------+------------------>| |<------+------------------>|
68 7 The end of output can point to the end of an element.
69 But out_pos_ should never point to the end:
72 |<------+------------------>| |<------+------------------>|
73 in_pos_ out_pos_ out_end_
76 8 When the input sequence entirely fills the last element and
77 the output sequence is empty, out_ will point to the end of
78 the list of buffers, and out_pos_ and out_end_ will be 0:
81 |<------+------------------>| out_ == list_.end()
86 template<class Allocator>
87 class basic_streambuf<Allocator>::element
88 : public boost::intrusive::list_base_hook<
89 boost::intrusive::link_mode<
90 boost::intrusive::normal_link>>
92 using size_type = typename std::allocator_traits<Allocator>::size_type;
94 size_type const size_;
97 element(element const&) = delete;
98 element& operator=(element const&) = delete;
115 return const_cast<char*>(
116 reinterpret_cast<char const*>(this+1));
120 template<class Allocator>
121 class basic_streambuf<Allocator>::const_buffers_type
123 basic_streambuf const* sb_;
125 friend class basic_streambuf;
128 const_buffers_type(basic_streambuf const& sb);
132 using value_type = boost::asio::const_buffer;
134 class const_iterator;
136 const_buffers_type() = delete;
137 const_buffers_type(const_buffers_type const&) = default;
138 const_buffers_type& operator=(const_buffers_type const&) = default;
147 template<class Allocator>
148 class basic_streambuf<Allocator>::mutable_buffers_type
150 basic_streambuf const* sb_;
152 friend class basic_streambuf;
155 mutable_buffers_type(basic_streambuf const& sb);
158 using value_type = mutable_buffer;
160 class const_iterator;
162 mutable_buffers_type() = delete;
163 mutable_buffers_type(mutable_buffers_type const&) = default;
164 mutable_buffers_type& operator=(mutable_buffers_type const&) = default;
173 //------------------------------------------------------------------------------
175 template<class Allocator>
176 class basic_streambuf<Allocator>::const_buffers_type::const_iterator
178 basic_streambuf const* sb_ = nullptr;
179 typename list_type::const_iterator it_;
183 typename const_buffers_type::value_type;
184 using pointer = value_type const*;
185 using reference = value_type;
186 using difference_type = std::ptrdiff_t;
187 using iterator_category =
188 std::bidirectional_iterator_tag;
190 const_iterator() = default;
191 const_iterator(const_iterator&& other) = default;
192 const_iterator(const_iterator const& other) = default;
193 const_iterator& operator=(const_iterator&& other) = default;
194 const_iterator& operator=(const_iterator const& other) = default;
196 const_iterator(basic_streambuf const& sb,
197 typename list_type::const_iterator const& it)
204 operator==(const_iterator const& other) const
206 return sb_ == other.sb_ && it_ == other.it_;
210 operator!=(const_iterator const& other) const
212 return !(*this == other);
218 auto const& e = *it_;
219 return value_type{e.data(),
220 (sb_->out_ == sb_->list_.end() ||
221 &e != &*sb_->out_) ? e.size() : sb_->out_pos_} +
222 (&e == &*sb_->list_.begin() ? sb_->in_pos_ : 0);
226 operator->() const = delete;
259 template<class Allocator>
260 basic_streambuf<Allocator>::const_buffers_type::const_buffers_type(
261 basic_streambuf const& sb)
266 template<class Allocator>
268 basic_streambuf<Allocator>::const_buffers_type::begin() const ->
271 return const_iterator{*sb_, sb_->list_.begin()};
274 template<class Allocator>
276 basic_streambuf<Allocator>::const_buffers_type::end() const ->
279 return const_iterator{*sb_, sb_->out_ ==
280 sb_->list_.end() ? sb_->list_.end() :
281 std::next(sb_->out_)};
284 //------------------------------------------------------------------------------
286 template<class Allocator>
287 class basic_streambuf<Allocator>::mutable_buffers_type::const_iterator
289 basic_streambuf const* sb_ = nullptr;
290 typename list_type::const_iterator it_;
294 typename mutable_buffers_type::value_type;
295 using pointer = value_type const*;
296 using reference = value_type;
297 using difference_type = std::ptrdiff_t;
298 using iterator_category =
299 std::bidirectional_iterator_tag;
301 const_iterator() = default;
302 const_iterator(const_iterator&& other) = default;
303 const_iterator(const_iterator const& other) = default;
304 const_iterator& operator=(const_iterator&& other) = default;
305 const_iterator& operator=(const_iterator const& other) = default;
307 const_iterator(basic_streambuf const& sb,
308 typename list_type::const_iterator const& it)
315 operator==(const_iterator const& other) const
317 return sb_ == other.sb_ && it_ == other.it_;
321 operator!=(const_iterator const& other) const
323 return !(*this == other);
329 auto const& e = *it_;
330 return value_type{e.data(),
331 &e == &*std::prev(sb_->list_.end()) ?
332 sb_->out_end_ : e.size()} +
333 (&e == &*sb_->out_ ? sb_->out_pos_ : 0);
337 operator->() const = delete;
370 template<class Allocator>
371 basic_streambuf<Allocator>::mutable_buffers_type::mutable_buffers_type(
372 basic_streambuf const& sb)
377 template<class Allocator>
379 basic_streambuf<Allocator>::mutable_buffers_type::begin() const ->
382 return const_iterator{*sb_, sb_->out_};
385 template<class Allocator>
387 basic_streambuf<Allocator>::mutable_buffers_type::end() const ->
390 return const_iterator{*sb_, sb_->list_.end()};
393 //------------------------------------------------------------------------------
395 template<class Allocator>
396 basic_streambuf<Allocator>::~basic_streambuf()
401 template<class Allocator>
402 basic_streambuf<Allocator>::
403 basic_streambuf(basic_streambuf&& other)
404 : detail::empty_base_optimization<allocator_type>(
405 std::move(other.member()))
406 , alloc_size_(other.alloc_size_)
407 , in_size_(other.in_size_)
408 , in_pos_(other.in_pos_)
409 , out_pos_(other.out_pos_)
410 , out_end_(other.out_end_)
413 other.out_ == other.list_.end();
414 list_ = std::move(other.list_);
415 out_ = at_end ? list_.end() : other.out_;
417 other.out_ = other.list_.end();
423 template<class Allocator>
424 basic_streambuf<Allocator>::
425 basic_streambuf(basic_streambuf&& other,
426 allocator_type const& alloc)
427 : basic_streambuf(other.alloc_size_, alloc)
429 using boost::asio::buffer_copy;
430 if(this->member() != other.member())
431 commit(buffer_copy(prepare(other.size()), other.data()));
433 move_assign(other, std::true_type{});
436 template<class Allocator>
438 basic_streambuf<Allocator>::operator=(
439 basic_streambuf&& other) -> basic_streambuf&
443 // VFALCO If any memory allocated we could use it first?
445 alloc_size_ = other.alloc_size_;
446 move_assign(other, std::integral_constant<bool,
447 alloc_traits::propagate_on_container_move_assignment::value>{});
451 template<class Allocator>
452 basic_streambuf<Allocator>::
453 basic_streambuf(basic_streambuf const& other)
454 : basic_streambuf(other.alloc_size_,
455 alloc_traits::select_on_container_copy_construction(other.member()))
457 commit(boost::asio::buffer_copy(prepare(other.size()), other.data()));
460 template<class Allocator>
461 basic_streambuf<Allocator>::
462 basic_streambuf(basic_streambuf const& other,
463 allocator_type const& alloc)
464 : basic_streambuf(other.alloc_size_, alloc)
466 commit(boost::asio::buffer_copy(prepare(other.size()), other.data()));
469 template<class Allocator>
471 basic_streambuf<Allocator>::operator=(
472 basic_streambuf const& other) ->
477 using boost::asio::buffer_copy;
479 copy_assign(other, std::integral_constant<bool,
480 alloc_traits::propagate_on_container_copy_assignment::value>{});
481 commit(buffer_copy(prepare(other.size()), other.data()));
485 template<class Allocator>
486 template<class OtherAlloc>
487 basic_streambuf<Allocator>::basic_streambuf(
488 basic_streambuf<OtherAlloc> const& other)
489 : basic_streambuf(other.alloc_size_)
491 using boost::asio::buffer_copy;
492 commit(buffer_copy(prepare(other.size()), other.data()));
495 template<class Allocator>
496 template<class OtherAlloc>
497 basic_streambuf<Allocator>::basic_streambuf(
498 basic_streambuf<OtherAlloc> const& other,
499 allocator_type const& alloc)
500 : basic_streambuf(other.alloc_size_, alloc)
502 using boost::asio::buffer_copy;
503 commit(buffer_copy(prepare(other.size()), other.data()));
506 template<class Allocator>
507 template<class OtherAlloc>
509 basic_streambuf<Allocator>::operator=(
510 basic_streambuf<OtherAlloc> const& other) ->
513 using boost::asio::buffer_copy;
515 commit(buffer_copy(prepare(other.size()), other.data()));
519 template<class Allocator>
520 basic_streambuf<Allocator>::basic_streambuf(
521 std::size_t alloc_size, Allocator const& alloc)
522 : detail::empty_base_optimization<allocator_type>(alloc)
524 , alloc_size_(alloc_size)
527 throw detail::make_exception<std::invalid_argument>(
528 "invalid alloc_size", __FILE__, __LINE__);
531 template<class Allocator>
533 basic_streambuf<Allocator>::capacity() const
536 if(pos == list_.end())
538 auto n = pos->size() - out_pos_;
539 while(++pos != list_.end())
544 template<class Allocator>
546 basic_streambuf<Allocator>::
550 return const_buffers_type(*this);
553 template<class Allocator>
555 basic_streambuf<Allocator>::prepare(size_type n) ->
559 if(out_ != list_.end())
561 if(out_ != list_.iterator_to(list_.back()))
563 out_end_ = out_->size();
564 reuse.splice(reuse.end(), list_,
565 std::next(out_), list_.end());
568 auto const avail = out_->size() - out_pos_;
571 out_end_ = out_->size();
576 out_end_ = out_pos_ + n;
581 while(n > 0 && ! reuse.empty())
583 auto& e = reuse.front();
584 reuse.erase(reuse.iterator_to(e));
600 auto const size = std::max(alloc_size_, n);
601 auto& e = *reinterpret_cast<element*>(static_cast<
602 void*>(alloc_traits::allocate(this->member(),
603 sizeof(element) + size)));
604 alloc_traits::construct(this->member(), &e, size);
606 if(out_ == list_.end())
607 out_ = list_.iterator_to(e);
620 for(auto it = reuse.begin(); it != reuse.end();)
623 reuse.erase(list_.iterator_to(e));
624 auto const len = e.size() + sizeof(e);
625 alloc_traits::destroy(this->member(), &e);
626 alloc_traits::deallocate(this->member(),
627 reinterpret_cast<char*>(&e), len);
629 return mutable_buffers_type(*this);
632 template<class Allocator>
634 basic_streambuf<Allocator>::commit(size_type n)
638 if(out_ == list_.end())
641 list_.iterator_to(list_.back());
645 out_->size() - out_pos_;
660 n = (std::min)(n, out_end_ - out_pos_);
663 if(out_pos_ == out_->size())
672 template<class Allocator>
674 basic_streambuf<Allocator>::consume(size_type n)
681 if(list_.begin() != out_)
683 auto const avail = list_.front().size() - in_pos_;
694 auto& e = list_.front();
695 list_.erase(list_.iterator_to(e));
696 auto const len = e.size() + sizeof(e);
697 alloc_traits::destroy(this->member(), &e);
698 alloc_traits::deallocate(this->member(),
699 reinterpret_cast<char*>(&e), len);
704 auto const avail = out_pos_ - in_pos_;
713 if(out_ != list_.iterator_to(list_.back()) ||
714 out_pos_ != out_end_)
720 // Input and output sequences are empty, reuse buffer.
721 // Alternatively we could deallocate it.
733 template<class Allocator>
735 basic_streambuf<Allocator>::
740 out_ = list_.begin();
747 template<class Allocator>
749 basic_streambuf<Allocator>::
750 move_assign(basic_streambuf& other, std::false_type)
752 using boost::asio::buffer_copy;
753 if(this->member() != other.member())
755 commit(buffer_copy(prepare(other.size()), other.data()));
759 move_assign(other, std::true_type{});
762 template<class Allocator>
764 basic_streambuf<Allocator>::
765 move_assign(basic_streambuf& other, std::true_type)
767 this->member() = std::move(other.member());
769 other.out_ == other.list_.end();
770 list_ = std::move(other.list_);
771 out_ = at_end ? list_.end() : other.out_;
773 in_size_ = other.in_size_;
774 in_pos_ = other.in_pos_;
775 out_pos_ = other.out_pos_;
776 out_end_ = other.out_end_;
779 other.out_ = other.list_.end();
785 template<class Allocator>
787 basic_streambuf<Allocator>::
788 copy_assign(basic_streambuf const& other, std::false_type)
790 beast::detail::ignore_unused(other);
793 template<class Allocator>
795 basic_streambuf<Allocator>::
796 copy_assign(basic_streambuf const& other, std::true_type)
798 this->member() = other.member();
801 template<class Allocator>
803 basic_streambuf<Allocator>::delete_list()
805 for(auto iter = list_.begin(); iter != list_.end();)
808 auto const n = e.size() + sizeof(e);
809 alloc_traits::destroy(this->member(), &e);
810 alloc_traits::deallocate(this->member(),
811 reinterpret_cast<char*>(&e), n);
815 template<class Allocator>
817 basic_streambuf<Allocator>::debug_check() const
820 using boost::asio::buffer_size;
821 BOOST_ASSERT(buffer_size(data()) == in_size_);
824 BOOST_ASSERT(in_pos_ == 0);
825 BOOST_ASSERT(in_size_ == 0);
826 BOOST_ASSERT(out_pos_ == 0);
827 BOOST_ASSERT(out_end_ == 0);
828 BOOST_ASSERT(out_ == list_.end());
832 auto const& front = list_.front();
834 BOOST_ASSERT(in_pos_ < front.size());
836 if(out_ == list_.end())
838 BOOST_ASSERT(out_pos_ == 0);
839 BOOST_ASSERT(out_end_ == 0);
843 auto const& out = *out_;
844 auto const& back = list_.back();
846 BOOST_ASSERT(out_end_ <= back.size());
847 BOOST_ASSERT(out_pos_ < out.size());
848 BOOST_ASSERT(&out != &front || out_pos_ >= in_pos_);
849 BOOST_ASSERT(&out != &front || out_pos_ - in_pos_ == in_size_);
850 BOOST_ASSERT(&out != &back || out_pos_ <= out_end_);
855 template<class Allocator>
857 read_size_helper(basic_streambuf<
858 Allocator> const& streambuf, std::size_t max_size)
860 BOOST_ASSERT(max_size >= 1);
861 // If we already have an allocated
862 // buffer, try to fill that up first
863 auto const avail = streambuf.capacity() - streambuf.size();
865 return (std::min)(avail, max_size);
866 // Try to have just one new block allocated
867 constexpr std::size_t low = 512;
868 if (streambuf.alloc_size_ > low)
869 return (std::min)(max_size, streambuf.alloc_size_);
870 // ...but enforce a 512 byte minimum.
871 return (std::min)(max_size, low);
874 template<class Alloc, class T>
875 basic_streambuf<Alloc>&
876 operator<<(basic_streambuf<Alloc>& streambuf, T const& t)
878 detail::write_dynabuf(streambuf, t);