combined: mergers: remove recursion in operator()()

In mutation_reader_merger and clustering_order_reader_merger, the
operator()() is responsible for producing mutation fragments that will
be merged and pushed to the combined reader's buffer. Sometimes, it
might have to advance existing readers, open new and / or close some
existing ones, which requires calling a helper method and then calling
operator()() recursively.

In some unlucky circumstances, a stack overflow can occur:

- Readers have to be opened incrementally,
- Most or all readers must not produce any fragments and need to report
  end of stream without preemption,
- There has to be enough readers opened within the lifetime of the
  combined reader (~500),
- All of the above needs to happen within a single task quota.

In order to prevent such a situation, the code of both reader merger
classes were modified not to perform recursion at all. Most of the code
of the operator()() was moved to maybe_produce_batch which does not
recur if it is not possible for it to produce a fragment, instead it
returns std::nullopt and operator()() calls this method in a loop via
seastar::repeat_until_value.

A regression test is added.

Fixes: scylladb/scylladb#14415

Closes #14452
This commit is contained in:
Piotr Dulikowski
2023-06-29 18:23:05 +02:00
committed by Botond Dénes
parent 5648bfb9a0
commit ee9bfb583c
2 changed files with 72 additions and 16 deletions

View File

@@ -669,6 +669,50 @@ SEASTAR_THREAD_TEST_CASE(test_sm_fast_forwarding_combining_reader_with_galloping
assertions.produces_end_of_stream();
}
class selector_of_empty_readers : public reader_selector {
schema_ptr _schema;
reader_permit _permit;
size_t _remaining;
public:
selector_of_empty_readers(schema_ptr s, reader_permit permit, size_t count)
: reader_selector(s, dht::ring_position_view::min())
, _schema(s)
, _permit(std::move(permit))
, _remaining(count) {
}
virtual std::vector<flat_mutation_reader_v2> create_new_readers(const std::optional<dht::ring_position_view>& pos) override {
if (_remaining == 0) {
return {};
}
--_remaining;
std::vector<flat_mutation_reader_v2> ret;
ret.push_back(make_empty_flat_reader_v2(_schema, _permit));
return ret;
}
virtual std::vector<flat_mutation_reader_v2> fast_forward_to(const dht::partition_range& pr) override {
assert(false); // Fast forward not supported by this reader
return {};
}
};
// Reproduces scylladb/scylladb#14415
SEASTAR_THREAD_TEST_CASE(test_combined_reader_with_incrementally_opened_empty_readers) {
static constexpr size_t empty_reader_count = 10 * 1000;
simple_schema s;
tests::reader_concurrency_semaphore_wrapper semaphore;
auto permit = semaphore.make_permit();
auto reader = make_combined_reader(s.schema(), permit,
std::make_unique<selector_of_empty_readers>(s.schema(), permit, empty_reader_count),
streamed_mutation::forwarding::no,
mutation_reader::forwarding::no);
// Expect that the reader won't produce a stack overflow
assert_that(std::move(reader))
.produces_end_of_stream();
}
SEASTAR_TEST_CASE(combined_mutation_reader_test) {
return sstables::test_env::do_with_async([] (sstables::test_env& env) {
simple_schema s;