The sharded<Service> template was designed with cooperating service classes
in mind. This cooperation is in the form of providing a stop() method for
orderly shutdown.
The sharded<> template is also useful for non-cooperating services: for example,
std::unordered_map<std::string can be broadcast to all shards with
sharded<std::unordered_map<sstring, sstring>> my_map;
my_map.start(initializer).get();
...
my_map.invoke_on_all([] (my_map_type& mm) { mm["x"] = "y"; }).get();
...
my_map.stop().get();
All is required is that the "Service" doesn't perform asynchronous operations (as
is usual for plain data types) or that the user stops them in some other way. But
this currently doesn't compile because the Service::stop() method is required.
To allow that, this patch only calls stop() if it is detected. It also adds a
customization point that allows the user to redirect stop() to a free function.
This can be useful if a class already contains a stop() that is used for something
else.
---
v2:
- replace traits class by auto-detection + explicit customization point
- unit tests
include/seastar/core/sharded.hh | 66 ++++++++++++++++++++++++++++++++-
tests/unit/sharded_test.cc | 40 ++++++++++++++++++++
2 files changed, 105 insertions(+), 1 deletion(-)
diff --git a/include/seastar/core/sharded.hh b/include/seastar/core/sharded.hh
index 7712105d6d..d3e9f51d95 100644
--- a/include/seastar/core/sharded.hh
+++ b/include/seastar/core/sharded.hh
@@ -474,10 +474,74 @@ class sharded {
}
return inst;
}
};
+namespace internal {
+
+// Helper check if Service::stop exists
+struct sharded_has_stop {
+ // If a member names "stop" exists, try to call it, even if it doesn't
+ // have the correct signature. This is so that we don't ignore a function
+ // named stop() just because the signature is incorrect, and instead
+ // force the user to resolve the ambiguity.
+ template <typename Service>
+ constexpr static auto check(Service*) -> std::enable_if_t<(sizeof(&Service::stop) >= 0), bool> {
+ return true;
+ }
+
+ // Fallback in case Service::stop doesn't exist.
+ static constexpr auto check(...) -> bool {
+ return false;
+ }
+};
+
+template <bool stop_exists>
+struct sharded_call_stop {
+ template <typename Service>
+ static future<> call(Service& instance);
+};
+
+template <>
+template <typename Service>
+future<> sharded_call_stop<true>::call(Service& instance) {
+ return instance.stop();
+}
+
+template <>
+template <typename Service>
+future<> sharded_call_stop<false>::call(Service& instance) {
+ return make_ready_future<>();
+}
+
+}
+
+
+/// Stop a \ref sharded service instance
+///
+/// This is a customization point. The default implementation will call
+/// instance.stop() if it exists (the member function must return a future<>).
+/// If instance.stop() does not exist, the default implementation does nothing.
+///
+/// You may customize this function for your instance type by declaring an
+/// overload in the same namespace as \c Service.
+///
+/// \tparam Service a type used in a \ref sharded instance.
+/// \return a \ref future that becomes available when the instance is stopped
+/// and ready for destruction. This typically involves signalling
+/// background operations that they need to terminate, and waiting
+/// until they do.
+///
+/// \see sharded
+/// \see gate
+template <typename Service>
+future<>
+stop_sharded_instance(Service& instance) {
+ constexpr bool has_stop = internal::sharded_has_stop::check(&instance);
+ return internal::sharded_call_stop<has_stop>::call(instance);
+}
+
/// @}
template <typename Service>
sharded<Service>::~sharded() {
assert(_instances.empty());
@@ -577,11 +641,11 @@ sharded<Service>::stop() {
return smp::submit_to(c, [this] () mutable {
auto inst = _instances[engine().cpu_id()].service;
if (!inst) {
return make_ready_future<>();
}
- return inst->stop();
+ return stop_sharded_instance(*inst);
});
}).then([this] {
return internal::sharded_parallel_for_each(_instances.size(), [this] (unsigned c) {
return smp::submit_to(c, [this] {
_instances[engine().cpu_id()].service = nullptr;
diff --git a/tests/unit/sharded_test.cc b/tests/unit/sharded_test.cc
index d227840dfb..ee61555d9a 100644
--- a/tests/unit/sharded_test.cc
+++ b/tests/unit/sharded_test.cc
@@ -47,5 +47,45 @@ class invoke_on_during_stop final : public peering_sharded_service<invoke_on_dur
SEASTAR_THREAD_TEST_CASE(invoke_on_during_stop_test) {
sharded<invoke_on_during_stop> s;
s.start().get();
s.stop().get();
}
+
+namespace {
+
+struct plain_old_data {
+ int x;
+};
+
+}
+
+// Really a compile test, check that we detect that plain_old_data::stop()
+// doesn't exist and not call it.
+SEASTAR_THREAD_TEST_CASE(test_plain_old_data) {
+ sharded<plain_old_data> pod;
+ pod.start().get();
+ pod.stop().get();
+}
+
+namespace {
+
+struct custom_stop {
+ int x;
+};
+
+std::atomic<int> stop_count;
+
+future<> stop_sharded_instance(custom_stop& cs) {
+ stop_count.fetch_add(1);
+ return make_ready_future<>();
+}
+
+}
+
+// Check that we honor the stop_sharded_instance() customization point.
+SEASTAR_THREAD_TEST_CASE(test_custom_stop) {
+ sharded<custom_stop> cs;
+ cs.start().get();
+ cs.stop().get();
+ BOOST_REQUIRE_EQUAL(stop_count.load(), smp::count);
+}
+
--
2.21.0