Files
scylladb/utils/reusable_buffer.hh
Paweł Dziepak 2bcaf4309e utils/reusable_buffer: do not warn about large allocations
Reusable buffers are meant to be used when protocol or third-party
library limiations force us to allocate large contiguous buffers. There
isn't much that can be done about this so there is little point in
warning about that.

Fixes #3788.
Message-Id: <20180928085141.6469-1-pdziepak@scylladb.com>
2018-09-30 11:12:23 +03:00

181 lines
6.7 KiB
C++

/*
* Copyright (C) 2018 ScyllaDB
*/
/*
* This file is part of Scylla.
*
* Scylla is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Scylla is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <seastar/core/memory.hh>
#include <seastar/util/gcc6-concepts.hh>
#include "bytes.hh"
#include "bytes_ostream.hh"
#include "utils/fragmented_temporary_buffer.hh"
namespace utils {
/// A reusable buffer, for temporary linearisation of bytes_ostream
///
/// This class provides helpers for temporary linearisation of bytes_ostream.
/// Both cases when bytes_ostream holds input data as well as when it is
/// an output are supported. Copies are avoided if the buffers are not
/// fragmented.
///
/// Example of reading a possibly fragmented bytes_ostream:
///
/// ```c++
/// thread_local reusable_buffer rb;
/// bytes_ostream potentially_fragmented_buffer;
/// bytes_view view = rb.get_linearized_view(potentially_fragmented_buffer);
/// // view is a view of a buffer that holds the same data as potentially_fragmented buffer
/// ```
///
/// Example of writing to a bytes_ostream:
///
/// ```c++
/// thread_local reusable_buffer rb;
/// size_t maximum_size = compute_maximum_size();
/// bytes_ostream destination = rb.make_buffer([&] (bytes_mutable_view view) {
/// // view is a mutable view of some buffer which content will be in
/// // the bytes_ostream returned by make_buffer.
/// return actual_length_of_the_buffer;
/// });
/// ```
class reusable_buffer { // extract to utils
// FIXME: We should start using std::byte for these things.
std::unique_ptr<int8_t[]> _buffer;
size_t _size = 0;
private:
bytes_mutable_view reserve(size_t n) {
if (_size < n) {
// Reusable buffers are expected to be used when large contiguous
// allocations are unavoidable. There is not much point in warning
// about them since there isn't much that can be done.
seastar::memory::scoped_large_allocation_warning_disable g;
// std::make_unique would zero-initialise the buffer which is
// just a waste of cycles. We can, however, summon an ancient
// entity from the elder days of C++ to help us.
_buffer.reset(new int8_t[n]);
_size = n;
}
return bytes_mutable_view(_buffer.get(), n);
}
public:
/// Returns a linearised view of the provided bytes_ostream
///
/// This function returns a linearised view of the data stored in the
/// provided bytes_ostream. If it is fragmented the linearisation uses
/// a buffer owned by this.
/// The returned view remains valid as long as the original bytes_ostream
/// is not modifed and no other member functions of this are called.
bytes_view get_linearized_view(const bytes_ostream& data) {
if (data.is_linearized()) {
return data.view();
}
auto mutable_view = reserve(data.size());
auto dst = mutable_view.begin();
for (bytes_view fragment : data) {
dst = std::copy(fragment.begin(), fragment.end(), dst);
}
return bytes_view(mutable_view);
}
bytes_view get_linearized_view(const fragmented_temporary_buffer::view& data) {
if (data.empty()) {
return { };
} else if (std::next(data.begin()) == data.end()) {
return *data.begin();
}
auto mutable_view = reserve(data.size_bytes());
auto dst = mutable_view.begin();
using boost::range::for_each;
for_each(data, [&] (bytes_view fragment) {
dst = std::copy(fragment.begin(), fragment.end(), dst);
});
return bytes_view(mutable_view);
}
/// Creates a bytes_ostream
///
/// make_buffer calls provided function object and gives it a mutable
/// view of some buffer. Data written to that view will be in the returned
/// bytes_ostream. The function object is expected to return the actual
/// size of the buffer (less than or equal the previously specified maximum
/// length).
template<typename Function>
GCC6_CONCEPT(requires requires(Function fn, bytes_mutable_view view) {
{ fn(view) } -> size_t;
})
bytes_ostream make_buffer(size_t maximum_length, Function&& fn) {
bytes_ostream output;
bytes_mutable_view view = [&] {
if (maximum_length && maximum_length <= bytes_ostream::max_chunk_size()) {
auto ptr = output.write_place_holder(maximum_length);
return bytes_mutable_view(ptr, maximum_length);
}
return reserve(maximum_length);
}();
size_t actual_length = fn(view);
if (output.empty()) {
output.write(bytes_view(view.data(), actual_length));
} else {
output.remove_suffix(output.size() - actual_length);
}
return output;
}
template<typename Function>
GCC6_CONCEPT(requires requires(Function fn, bytes_mutable_view view) {
{ fn(view) } -> size_t;
})
fragmented_temporary_buffer make_fragmented_temporary_buffer(size_t maximum_length, size_t maximum_fragment_size, Function&& fn) {
std::vector<temporary_buffer<char>> fragments;
bytes_mutable_view view = [&] {
if (maximum_length <= maximum_fragment_size) {
fragments.emplace_back(maximum_length);
return bytes_mutable_view(reinterpret_cast<bytes::pointer>(fragments.back().get_write()), maximum_length);
}
return reserve(maximum_length);
}();
size_t actual_length = fn(view);
if (fragments.empty()) {
auto left = actual_length;
auto src = reinterpret_cast<const bytes::value_type*>(_buffer.get());
while (left) {
auto this_length = std::min(left, maximum_fragment_size);
fragments.emplace_back(reinterpret_cast<const char*>(src), this_length);
src += this_length;
left -= this_length;
}
} else {
fragments.back().trim(actual_length);
}
return fragmented_temporary_buffer(std::move(fragments), actual_length);
}
/// Releases all allocated memory.
void clear() noexcept {
_buffer.reset();
_size = 0;
}
};
}