Skip to content

Commit 0dfee8b

Browse files
committed
Merge 'Support chunked_vector and other json/httpd changes' from Travis Downs
This series came out of an effort to squash large allocations in a json httpd endpoint in redpanda: the basic idea is to optionally use chunked_fifo instead of std::vector as the underlying type in the json2code generation. Some other bugs I ran into along the the way are also fixed, and even code that doesn't use chunked_fifo should get some benefits from the addition of move support on the json response hot path. Test cases added where I thought it made sense. Closes scylladb#2647 * github.com:scylladb/seastar: json2code: support chunked_fifo json: remove unused headers httpd: test cases for streaming httpd: handle streaming results in more handlers json: stream_object now moves value json: support for rvalue ranges chunked_fifo: make copyable json2code_test: factor out query method seastar-json2code: fix error handling
2 parents f1821a0 + c9f848b commit 0dfee8b

File tree

9 files changed

+338
-54
lines changed

9 files changed

+338
-54
lines changed

include/seastar/core/chunked_fifo.hh

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#ifndef SEASTAR_MODULE
2525
#include <algorithm>
2626
#include <cassert>
27+
#include <iterator>
2728
#include <type_traits>
2829
#include <seastar/util/assert.hh>
2930
#include <seastar/util/modules.hh>
@@ -177,10 +178,11 @@ public:
177178
public:
178179
chunked_fifo() noexcept = default;
179180
chunked_fifo(chunked_fifo&& x) noexcept;
180-
chunked_fifo(const chunked_fifo& X) = delete;
181+
chunked_fifo(const chunked_fifo&);
181182
~chunked_fifo();
182-
chunked_fifo& operator=(const chunked_fifo&) = delete;
183+
chunked_fifo& operator=(const chunked_fifo&);
183184
chunked_fifo& operator=(chunked_fifo&&) noexcept;
185+
inline bool operator==(const chunked_fifo& rhs) const;
184186
inline void push_back(const T& data);
185187
inline void push_back(T&& data);
186188
T& back() noexcept;
@@ -297,6 +299,25 @@ chunked_fifo<T, items_per_chunk>::chunked_fifo(chunked_fifo&& x) noexcept
297299
x._nfree_chunks = 0;
298300
}
299301

302+
template <typename T, size_t items_per_chunk>
303+
inline
304+
chunked_fifo<T, items_per_chunk>::chunked_fifo(const chunked_fifo& rhs)
305+
: chunked_fifo() {
306+
std::copy_n(rhs.begin(), rhs.size(), std::back_inserter(*this));
307+
}
308+
309+
template <typename T, size_t items_per_chunk>
310+
inline
311+
chunked_fifo<T, items_per_chunk>&
312+
chunked_fifo<T, items_per_chunk>::operator=(const chunked_fifo& rhs) {
313+
if (&rhs != this) {
314+
clear();
315+
std::copy_n(rhs.begin(), rhs.size(), std::back_inserter(*this));
316+
shrink_to_fit();
317+
}
318+
return *this;
319+
}
320+
300321
template <typename T, size_t items_per_chunk>
301322
inline
302323
chunked_fifo<T, items_per_chunk>&
@@ -456,6 +477,11 @@ chunked_fifo<T, items_per_chunk>::emplace_back(Args&&... args) {
456477
++_back_chunk->end;
457478
}
458479

480+
template <typename T, size_t items_per_chunk>
481+
inline bool chunked_fifo<T, items_per_chunk>::operator==(const chunked_fifo& rhs) const {
482+
return size() == rhs.size() && std::equal(begin(), end(), rhs.begin());
483+
}
484+
459485
template <typename T, size_t items_per_chunk>
460486
inline void
461487
chunked_fifo<T, items_per_chunk>::push_back(const T& data) {

include/seastar/http/function_handlers.hh

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -82,31 +82,22 @@ public:
8282
function_handler(const request_function & _handle, const sstring& type)
8383
: _f_handle(
8484
[_handle](std::unique_ptr<http::request> req, std::unique_ptr<http::reply> rep) {
85-
rep->_content += _handle(*req.get());
86-
return make_ready_future<std::unique_ptr<http::reply>>(std::move(rep));
85+
return append_result(std::move(rep), _handle(*req.get()));
8786
}), _type(type) {
8887
}
8988

9089
function_handler(const json_request_function& _handle)
9190
: _f_handle(
9291
[_handle](std::unique_ptr<http::request> req, std::unique_ptr<http::reply> rep) {
93-
json::json_return_type res = _handle(*req.get());
94-
rep->_content += res._res;
95-
return make_ready_future<std::unique_ptr<http::reply>>(std::move(rep));
92+
return append_result(std::move(rep), _handle(*req.get()));
9693
}), _type("json") {
9794
}
9895

9996
function_handler(const future_json_function& _handle)
10097
: _f_handle(
10198
[_handle](std::unique_ptr<http::request> req, std::unique_ptr<http::reply> rep) {
10299
return _handle(std::move(req)).then([rep = std::move(rep)](json::json_return_type&& res) mutable {
103-
if (res._body_writer) {
104-
rep->write_body("json", std::move(res._body_writer));
105-
} else {
106-
rep->_content += res._res;
107-
108-
}
109-
return make_ready_future<std::unique_ptr<http::reply>>(std::move(rep));
100+
return append_result(std::move(rep), std::move(res));
110101
});
111102
}), _type("json") {
112103
}
@@ -122,6 +113,19 @@ public:
122113
});
123114
}
124115

116+
private:
117+
// send the json payload of result to reply, return the reply pointer
118+
static future<std::unique_ptr<http::reply>> append_result(
119+
std::unique_ptr<http::reply>&& reply,
120+
json::json_return_type&& result) {
121+
if (result._body_writer) {
122+
reply->write_body("json", std::move(result._body_writer));
123+
} else {
124+
reply->_content += result._res;
125+
}
126+
return make_ready_future<std::unique_ptr<http::reply>>(std::move(reply));
127+
}
128+
125129
protected:
126130
future_handler_function _f_handle;
127131
sstring _type;

include/seastar/json/formatter.hh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ namespace internal {
4343

4444
template<typename T>
4545
concept is_map = requires {
46-
typename T::mapped_type;
46+
typename std::remove_reference_t<T>::mapped_type;
4747
};
4848

4949
template<typename T>
@@ -357,8 +357,8 @@ public:
357357
*/
358358
template<std::ranges::input_range Range>
359359
requires (!internal::is_string_like<Range>)
360-
static future<> write(output_stream<char>& s, const Range& range) {
361-
return do_with(std::move(range), [&s] (const auto& range) {
360+
static future<> write(output_stream<char>& s, Range&& range) {
361+
return do_with(std::forward<Range>(range), [&s] (const auto& range) {
362362
if constexpr (internal::is_map<Range>) {
363363
return write(s, state::map, std::ranges::begin(range), std::ranges::end(range));
364364
} else {

include/seastar/json/json_elements.hh

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,14 @@
2424
#ifndef SEASTAR_MODULE
2525
#include <string>
2626
#include <vector>
27-
#include <time.h>
28-
#include <sstream>
2927
#endif
3028

29+
#include <seastar/core/chunked_fifo.hh>
3130
#include <seastar/core/do_with.hh>
31+
#include <seastar/core/iostream.hh>
3232
#include <seastar/core/loop.hh>
33-
#include <seastar/json/formatter.hh>
3433
#include <seastar/core/sstring.hh>
35-
#include <seastar/core/iostream.hh>
34+
#include <seastar/json/formatter.hh>
3635
#include <seastar/util/modules.hh>
3736

3837
namespace seastar {
@@ -151,13 +150,15 @@ private:
151150
};
152151

153152
/**
154-
* json_list is based on std vector implementation.
153+
* json_list_template is an array type based on a
154+
* container type passed as a template parameter, as we want to
155+
* have flavors based on both vector and chunked_fifo.
155156
*
156157
* When values are added with push it is set the "set" flag to true
157158
* hence will be included in the parsed object
158159
*/
159-
template<class T>
160-
class json_list : public json_base_element {
160+
template <class T, class Container>
161+
class json_list_template : public json_base_element {
161162
public:
162163

163164
/**
@@ -188,7 +189,7 @@ public:
188189
* iteration and that it's elements can be assigned to the list elements
189190
*/
190191
template<class C>
191-
json_list& operator=(const C& list) {
192+
json_list_template& operator=(const C& list) {
192193
_elements.clear();
193194
for (auto i : list) {
194195
push(i);
@@ -198,9 +199,16 @@ public:
198199
virtual future<> write(output_stream<char>& s) const override {
199200
return formatter::write(s, _elements);
200201
}
201-
std::vector<T> _elements;
202+
203+
Container _elements;
202204
};
203205

206+
template <typename T>
207+
using json_list = json_list_template<T, std::vector<T>>;
208+
209+
template <typename T>
210+
using json_chunked_list = json_list_template<T, seastar::chunked_fifo<T>>;
211+
204212
class jsonable {
205213
public:
206214
jsonable() = default;
@@ -368,8 +376,8 @@ std::function<future<>(output_stream<char>&&)> stream_range_as_array(Container v
368376
template<class T>
369377
std::function<future<>(output_stream<char>&&)> stream_object(T val) {
370378
return [val = std::move(val)](output_stream<char>&& s) mutable {
371-
return do_with(output_stream<char>(std::move(s)), T(std::move(val)), [](output_stream<char>& s, const T& val){
372-
return formatter::write(s, val).finally([&s] {
379+
return do_with(output_stream<char>(std::move(s)), T(std::move(val)), [](output_stream<char>& s, T& val){
380+
return formatter::write(s, std::move(val)).finally([&s] {
373381
return s.close();
374382
});
375383
});

scripts/seastar-json2code.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,14 @@ def valid_type(param):
9797
trace_err("Type [", param, "] not defined")
9898
return param
9999

100+
# Map from json list types to the C++ implementing type
101+
LIST_TO_IMPL = {"array": "json_list", "chunked_array": "json_chunked_list"}
100102

101-
def type_change(param, member):
102-
if param == "array":
103+
def is_array_type(type: str):
104+
return type in LIST_TO_IMPL
105+
106+
def type_change(param: str, member):
107+
if is_array_type(param):
103108
if "items" not in member:
104109
trace_err("array without item declaration in ", param)
105110
return ""
@@ -111,7 +116,8 @@ def type_change(param, member):
111116
else:
112117
trace_err("array items with no type or ref declaration ", param)
113118
return ""
114-
return "json_list< " + valid_type(t) + " >"
119+
return LIST_TO_IMPL[param] + "< " + valid_type(t) + " >"
120+
115121
return "json_element< " + valid_type(param) + " >"
116122

117123

@@ -371,7 +377,7 @@ def is_model_valid(name, model):
371377
properties = getitem(model[name], "properties", name)
372378
for var in properties:
373379
type = getitem(properties[var], "type", name + ":" + var)
374-
if type == "array":
380+
if is_array_type(type):
375381
items = getitem(properties[var], "items", name + ":" + var)
376382
try:
377383
type = getitem(items, "type", name + ":" + var + ":items")
@@ -677,9 +683,8 @@ def parse_file(param, combined):
677683
if (combined):
678684
fprintln(combined, '#include "', base_file_name, ".cc", '"')
679685
create_h_file(data, hfile_name, api_name, init_method, base_api)
680-
except:
681-
type, value, tb = sys.exc_info()
682-
print("Error while parsing JSON file '" + param + "' error ", value.message)
686+
except Exception as e:
687+
print("Error while parsing JSON file '" + param + "' error " + str(e))
683688
sys.exit(-1)
684689

685690
if "indir" in config and config.indir != '':

tests/unit/api.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@
4747
"VAL2",
4848
"VAL3"
4949
]
50+
},
51+
{
52+
"name": "use_streaming",
53+
"description": "Whether to return the response as a stream_object",
54+
"required": true,
55+
"allowMultiple": false,
56+
"type": "boolean",
57+
"paramType": "query"
5058
}
5159
]
5260
}
@@ -74,6 +82,18 @@
7482
"VAL2",
7583
"VAL3"
7684
]
85+
},
86+
"array_var": {
87+
"type": "array",
88+
"items": {
89+
"type": "int"
90+
}
91+
},
92+
"chunked_array_var": {
93+
"type": "chunked_array",
94+
"items": {
95+
"type": "int"
96+
}
7797
}
7898
}
7999
}

0 commit comments

Comments
 (0)