mirror of
https://github.com/scylladb/scylladb.git
synced 2026-04-21 17:10:35 +00:00
The current code assumes that the sstring will have the same char_type as the output_stream. That was working well, until I was forced to change the type of my basic_sstring to another one that is backed by signed chars. Of course, the best solution for this would be to change the output_stream (as well as the input_stream), to take a signed char as well. And oh boy, have I tried. The data_sink assumes a char type, and when it tries to allocate a new buffer from it, the buffer will have no other choice than to be of a char type. Fix that one, and another one appears. I eventually gave up when the code wouldn't compile because struct fragment has a char type - and both using a template for such a simple struct, as well as sprinkling casts all over the place where it is used, sounded like horrible ideas to me. It's true that quitters never win, and winners never quit. But for now, my proposal would be to generalize the write code to accept basic_sstrings of general types. At least the cast lives in a single place. Signed-off-by: Glauber Costa <glommer@cloudius-systems.com>
182 lines
6.0 KiB
C++
182 lines
6.0 KiB
C++
/*
|
|
* This file is open source software, licensed to you under the terms
|
|
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
|
* distributed with this work for additional information regarding copyright
|
|
* ownership. You may not use this file except in compliance with the License.
|
|
*
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing,
|
|
* software distributed under the License is distributed on an
|
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
* KIND, either express or implied. See the License for the
|
|
* specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2015 Cloudius Systems, Ltd.
|
|
*/
|
|
|
|
//
|
|
// Buffered input and output streams
|
|
//
|
|
// Two abstract classes (data_source and data_sink) provide means
|
|
// to acquire bulk data from, or push bulk data to, some provider.
|
|
// These could be tied to a TCP connection, a disk file, or a memory
|
|
// buffer.
|
|
//
|
|
// Two concrete classes (input_stream and output_stream) buffer data
|
|
// from data_source and data_sink and provide easier means to process
|
|
// it.
|
|
//
|
|
|
|
#pragma once
|
|
|
|
#include "future.hh"
|
|
#include "temporary_buffer.hh"
|
|
#include "scattered_message.hh"
|
|
|
|
namespace net { class packet; }
|
|
|
|
class data_source_impl {
|
|
public:
|
|
virtual ~data_source_impl() {}
|
|
virtual future<temporary_buffer<char>> get() = 0;
|
|
};
|
|
|
|
class data_source {
|
|
std::unique_ptr<data_source_impl> _dsi;
|
|
protected:
|
|
data_source_impl* impl() const { return _dsi.get(); }
|
|
public:
|
|
data_source() = default;
|
|
explicit data_source(std::unique_ptr<data_source_impl> dsi) : _dsi(std::move(dsi)) {}
|
|
data_source(data_source&& x) = default;
|
|
data_source& operator=(data_source&& x) = default;
|
|
future<temporary_buffer<char>> get() { return _dsi->get(); }
|
|
};
|
|
|
|
class data_sink_impl {
|
|
public:
|
|
virtual ~data_sink_impl() {}
|
|
virtual temporary_buffer<char> allocate_buffer(size_t size) {
|
|
return temporary_buffer<char>(size);
|
|
}
|
|
virtual future<> put(net::packet data) = 0;
|
|
virtual future<> put(std::vector<temporary_buffer<char>> data) {
|
|
net::packet p;
|
|
p.reserve(data.size());
|
|
for (auto& buf : data) {
|
|
p = net::packet(std::move(p), net::fragment{buf.get_write(), buf.size()}, buf.release());
|
|
}
|
|
return put(std::move(p));
|
|
}
|
|
virtual future<> put(temporary_buffer<char> buf) {
|
|
return put(net::packet(net::fragment{buf.get_write(), buf.size()}, buf.release()));
|
|
}
|
|
virtual future<> close() = 0;
|
|
};
|
|
|
|
class data_sink {
|
|
std::unique_ptr<data_sink_impl> _dsi;
|
|
public:
|
|
data_sink() = default;
|
|
explicit data_sink(std::unique_ptr<data_sink_impl> dsi) : _dsi(std::move(dsi)) {}
|
|
data_sink(data_sink&& x) = default;
|
|
data_sink& operator=(data_sink&& x) = default;
|
|
temporary_buffer<char> allocate_buffer(size_t size) {
|
|
return _dsi->allocate_buffer(size);
|
|
}
|
|
future<> put(std::vector<temporary_buffer<char>> data) {
|
|
return _dsi->put(std::move(data));
|
|
}
|
|
future<> put(temporary_buffer<char> data) {
|
|
return _dsi->put(std::move(data));
|
|
}
|
|
future<> put(net::packet p) {
|
|
return _dsi->put(std::move(p));
|
|
}
|
|
future<> close() { return _dsi->close(); }
|
|
};
|
|
|
|
template <typename CharType>
|
|
class input_stream final {
|
|
static_assert(sizeof(CharType) == 1, "must buffer stream of bytes");
|
|
data_source _fd;
|
|
temporary_buffer<CharType> _buf;
|
|
bool _eof = false;
|
|
private:
|
|
using tmp_buf = temporary_buffer<CharType>;
|
|
size_t available() const { return _buf.size(); }
|
|
protected:
|
|
void reset() { _buf = {}; }
|
|
data_source* fd() { return &_fd; }
|
|
public:
|
|
// Consumer concept, for consume() method:
|
|
struct ConsumerConcept {
|
|
// call done(tmp_buf) to signal end of processing. tmp_buf parameter to
|
|
// done is unconsumed data
|
|
template <typename Done>
|
|
void operator()(tmp_buf data, Done done);
|
|
};
|
|
using char_type = CharType;
|
|
input_stream() = default;
|
|
explicit input_stream(data_source fd) : _fd(std::move(fd)), _buf(0) {}
|
|
input_stream(input_stream&&) = default;
|
|
input_stream& operator=(input_stream&&) = default;
|
|
future<temporary_buffer<CharType>> read_exactly(size_t n);
|
|
template <typename Consumer>
|
|
future<> consume(Consumer& c);
|
|
bool eof() { return _eof; }
|
|
private:
|
|
future<temporary_buffer<CharType>> read_exactly_part(size_t n, tmp_buf buf, size_t completed);
|
|
};
|
|
|
|
// Facilitates data buffering before it's handed over to data_sink.
|
|
//
|
|
// When trim_to_size is true it's guaranteed that data sink will not receive
|
|
// chunks larger than the configured size, which could be the case when a
|
|
// single write call is made with data larger than the configured size.
|
|
//
|
|
// The data sink will not receive empty chunks.
|
|
//
|
|
template <typename CharType>
|
|
class output_stream final {
|
|
static_assert(sizeof(CharType) == 1, "must buffer stream of bytes");
|
|
data_sink _fd;
|
|
temporary_buffer<CharType> _buf;
|
|
size_t _size = 0;
|
|
size_t _begin = 0;
|
|
size_t _end = 0;
|
|
bool _trim_to_size = false;
|
|
private:
|
|
size_t available() const { return _end - _begin; }
|
|
size_t possibly_available() const { return _size - _begin; }
|
|
future<> split_and_put(temporary_buffer<CharType> buf);
|
|
public:
|
|
using char_type = CharType;
|
|
output_stream() = default;
|
|
output_stream(data_sink fd, size_t size, bool trim_to_size = false)
|
|
: _fd(std::move(fd)), _size(size), _trim_to_size(trim_to_size) {}
|
|
output_stream(output_stream&&) = default;
|
|
output_stream& operator=(output_stream&&) = default;
|
|
future<> write(const char_type* buf, size_t n);
|
|
future<> write(const char_type* buf);
|
|
|
|
template <typename StringChar, typename SizeType, SizeType MaxSize>
|
|
future<> write(const basic_sstring<StringChar, SizeType, MaxSize>& s);
|
|
future<> write(const std::basic_string<char_type>& s);
|
|
|
|
future<> write(net::packet p);
|
|
future<> write(scattered_message<char_type> msg);
|
|
future<> flush();
|
|
future<> close() { return _fd.close(); }
|
|
private:
|
|
};
|
|
|
|
|
|
#include "iostream-impl.hh"
|