before this change, we rely on `using namespace seastar` to use
`seastar::format()` without qualifying the `format()` with its
namespace. this works fine until we changed the parameter type
of format string `seastar::format()` from `const char*` to
`fmt::format_string<...>`. this change practically invited
`seastar::format()` to the club of `std::format()` and `fmt::format()`,
where all members accept a templated parameter as its `fmt`
parameter. and `seastar::format()` is not the best candidate anymore.
despite that argument-dependent lookup (ADT for short) favors the
function which is in the same namespace as its parameter, but
`using namespace` makes `seastar::format()` more competitive,
so both `std::format()` and `seastar::format()` are considered
as the condidates.
that is what is happening scylladb in quite a few caller sites of
`format()`, hence ADT is not able to tell which function the winner
in the name lookup:
```
/__w/scylladb/scylladb/mutation/mutation_fragment_stream_validator.cc:265:12: error: call to 'format' is ambiguous
265 | return format("{} ({}.{} {})", _name_view, s.ks_name(), s.cf_name(), s.id());
| ^~~~~~
/usr/bin/../lib/gcc/x86_64-redhat-linux/14/../../../../include/c++/14/format:4290:5: note: candidate function [with _Args = <const std::basic_string_view<char> &, const seastar::basic_sstring<char, unsigned int, 15> &, const seastar::basic_sstring<char, unsigned int, 15> &, const utils::tagged_uuid<table_id_tag> &>]
4290 | format(format_string<_Args...> __fmt, _Args&&... __args)
| ^
/__w/scylladb/scylladb/seastar/include/seastar/core/print.hh:143:1: note: candidate function [with A = <const std::basic_string_view<char> &, const seastar::basic_sstring<char, unsigned int, 15> &, const seastar::basic_sstring<char, unsigned int, 15> &, const utils::tagged_uuid<table_id_tag> &>]
143 | format(fmt::format_string<A...> fmt, A&&... a) {
| ^
```
in this change, we
change all `format()` to either `fmt::format()` or `seastar::format()`
with following rules:
- if the caller expects an `sstring` or `std::string_view`, change to
`seastar::format()`
- if the caller expects an `std::string`, change to `fmt::format()`.
because, `sstring::operator std::basic_string` would incur a deep
copy.
we will need another change to enable scylladb to compile with the
latest seastar. namely, to pass the format string as a templated
parameter down to helper functions which format their parameters.
to miminize the scope of this change, let's include that change when
bumping up the seastar submodule. as that change will depend on
the seastar change.
Signed-off-by: Kefu Chai <kefu.chai@scylladb.com>
134 lines
5.8 KiB
C++
134 lines
5.8 KiB
C++
/*
|
|
* Copyright (C) 2015-present ScyllaDB
|
|
*/
|
|
|
|
/*
|
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
*/
|
|
|
|
|
|
#include <fmt/ranges.h>
|
|
#include <seastar/core/thread.hh>
|
|
#include "test/lib/scylla_test_case.hh"
|
|
|
|
#include "test/lib/cql_test_env.hh"
|
|
#include "service/storage_proxy.hh"
|
|
#include "query_ranges_to_vnodes.hh"
|
|
#include "schema/schema_builder.hh"
|
|
|
|
// Returns random keys sorted in ring order.
|
|
// The schema must have a single bytes_type partition key column.
|
|
static std::vector<dht::ring_position> make_ring(schema_ptr s, int n_keys) {
|
|
std::vector<dht::ring_position> ring;
|
|
for (int i = 0; i < 10; ++i) {
|
|
auto pk = partition_key::from_single_value(*s, to_bytes(format("key{:d}", i)));
|
|
ring.emplace_back(dht::decorate_key(*s, pk));
|
|
}
|
|
std::sort(ring.begin(), ring.end(), dht::ring_position_less_comparator(*s));
|
|
return ring;
|
|
}
|
|
|
|
SEASTAR_TEST_CASE(test_get_restricted_ranges) {
|
|
return do_with_cql_env_thread([](cql_test_env& e) {
|
|
auto s = schema_builder("ks", "cf")
|
|
.with_column("pk", bytes_type, column_kind::partition_key)
|
|
.with_column("v", bytes_type, column_kind::regular_column)
|
|
.build();
|
|
|
|
std::vector<dht::ring_position> ring = make_ring(s, 10);
|
|
|
|
auto check = [&s](locator::token_metadata_ptr tmptr, dht::partition_range input,
|
|
dht::partition_range_vector expected) {
|
|
query_ranges_to_vnodes_generator ranges_to_vnodes(locator::make_splitter(tmptr), s, {input});
|
|
auto actual = ranges_to_vnodes(1000);
|
|
if (!std::equal(actual.begin(), actual.end(), expected.begin(), [&s](auto&& r1, auto&& r2) {
|
|
return r1.equal(r2, dht::ring_position_comparator(*s));
|
|
})) {
|
|
BOOST_FAIL(fmt::format("Ranges differ, expected {} but got {}", expected, actual));
|
|
}
|
|
};
|
|
|
|
{
|
|
// Ring with minimum token
|
|
auto tmptr = locator::make_token_metadata_ptr(locator::token_metadata::config{});
|
|
const auto host_id = locator::host_id{utils::UUID(0, 1)};
|
|
tmptr->update_topology(host_id, locator::endpoint_dc_rack{"dc1", "rack1"}, locator::node::state::normal);
|
|
tmptr->update_normal_tokens(std::unordered_set<dht::token>({dht::minimum_token()}), host_id).get();
|
|
|
|
check(tmptr, dht::partition_range::make_singular(ring[0]), {
|
|
dht::partition_range::make_singular(ring[0])
|
|
});
|
|
|
|
check(tmptr, dht::partition_range({ring[2]}, {ring[3]}), {
|
|
dht::partition_range({ring[2]}, {ring[3]})
|
|
});
|
|
}
|
|
|
|
{
|
|
auto tmptr = locator::make_token_metadata_ptr(locator::token_metadata::config{});
|
|
const auto id1 = locator::host_id{utils::UUID(0, 1)};
|
|
const auto id2 = locator::host_id{utils::UUID(0, 2)};
|
|
tmptr->update_topology(id1, locator::endpoint_dc_rack{"dc1", "rack1"}, locator::node::state::normal);
|
|
tmptr->update_normal_tokens(std::unordered_set<dht::token>({ring[2].token()}), id1).get();
|
|
tmptr->update_topology(id2, locator::endpoint_dc_rack{"dc1", "rack1"}, locator::node::state::normal);
|
|
tmptr->update_normal_tokens(std::unordered_set<dht::token>({ring[5].token()}), id2).get();
|
|
|
|
check(tmptr, dht::partition_range::make_singular(ring[0]), {
|
|
dht::partition_range::make_singular(ring[0])
|
|
});
|
|
|
|
check(tmptr, dht::partition_range::make_singular(ring[2]), {
|
|
dht::partition_range::make_singular(ring[2])
|
|
});
|
|
|
|
check(tmptr, dht::partition_range({{dht::ring_position::ending_at(ring[2].token()), false}}, {ring[3]}), {
|
|
dht::partition_range({{dht::ring_position::ending_at(ring[2].token()), false}}, {ring[3]})
|
|
});
|
|
|
|
check(tmptr, dht::partition_range({ring[3]}, {ring[4]}), {
|
|
dht::partition_range({ring[3]}, {ring[4]})
|
|
});
|
|
|
|
check(tmptr, dht::partition_range({ring[2]}, {ring[3]}), {
|
|
dht::partition_range({ring[2]}, {dht::ring_position::ending_at(ring[2].token())}),
|
|
dht::partition_range({{dht::ring_position::ending_at(ring[2].token()), false}}, {ring[3]})
|
|
});
|
|
|
|
check(tmptr, dht::partition_range({{ring[2], false}}, {ring[3]}), {
|
|
dht::partition_range({{ring[2], false}}, {dht::ring_position::ending_at(ring[2].token())}),
|
|
dht::partition_range({{dht::ring_position::ending_at(ring[2].token()), false}}, {ring[3]})
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
SEASTAR_THREAD_TEST_CASE(test_split_stats) {
|
|
auto ep1 = gms::inet_address("127.0.0.1");
|
|
auto sg1 = create_scheduling_group("apa1", 100).get();
|
|
auto sg2 = create_scheduling_group("apa2", 100).get();
|
|
|
|
std::optional<service::storage_proxy_stats::split_stats> stats1, stats2;
|
|
|
|
// pretending to be abstract_write_response_handler type.
|
|
// created in various scheduling groups, in which they
|
|
// instantiate group-local split_stats.
|
|
with_scheduling_group(sg1, [&] {
|
|
stats1.emplace("tuta", "nils", "en nils", "nilsa", true);
|
|
}).get();
|
|
|
|
with_scheduling_group(sg2, [&] {
|
|
stats2.emplace("tuta", "nils", "en nils", "nilsa", true);
|
|
}).get();
|
|
|
|
// simulating the calling of storage_proxy::on_down, from gossip
|
|
// on node dropping out. If inside a write operation, we'll pick up
|
|
// write handlers and to "timeout_cb" on them, which in turn might
|
|
// call get_ep_stat, which eventually calls register_metrics for
|
|
// the DC written to.
|
|
// Point being is that either the above should not happen, or
|
|
// split_stats should be resilient to being called from different
|
|
// scheduling group.
|
|
stats1->register_metrics_for("DC1", ep1);
|
|
stats2->register_metrics_for("DC1", ep1);
|
|
}
|