/* * Copyright (C) 2020-present ScyllaDB */ /* * SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.1 */ #pragma once #include #include #include #include #include #include #include "utils/collection-concepts.hh" using namespace seastar; namespace utils { // Similar to std::merge but it does not stall. Must run inside a seastar // thread. It merges items from list2 into list1. Items from list2 can only be copied. template requires LessComparable void merge_to_gently(std::list& list1, const std::list& list2, Compare comp) { auto first1 = list1.begin(); auto first2 = list2.begin(); auto last1 = list1.end(); auto last2 = list2.end(); while (first2 != last2) { seastar::thread::maybe_yield(); if (first1 == last1) { // Copy remaining items of list2 into list1 list1.insert(last1, *first2); ++first2; continue; } if (comp(*first2, *first1)) { list1.insert(first1, *first2); ++first2; } else { ++first1; } } } // The clear_gently functions are meant for // gently destroying the contents of containers and smart pointers. // The containers can be reused after clear_gently // or may be destroyed. But unlike e.g. std::vector::clear(), // clear_gently will not necessarily keep the object allocation. // // Note that for any type of shared pointer (foreign or not), clear_gently // just reduces the reference count if it's greater than 1 and then returns. // In other words, it behaves like a normal reset(). // But if clear_gently is called on the very last copy, the clear_gently() function // is recursively called on that last copy before destroying the object // to avoid stall in that destruction. template concept HasClearGentlyMethod = requires (T x) { { x.clear_gently() } -> std::same_as>; }; template concept SmartPointer = requires (T x) { { x.get() } -> std::same_as; { *x } -> std::same_as; }; template concept SharedPointer = SmartPointer && requires (T x) { { x.use_count() } -> std::convertible_to; }; template concept StringLike = requires (T x) { std::is_same_v>; }; template concept Iterable = requires (T x) { { x.empty() } -> std::same_as; { x.begin() } -> std::same_as; { x.end() } -> std::same_as; }; template concept Sequence = Iterable && requires (T x, size_t n) { { x.back() } -> std::same_as; { x.pop_back() } -> std::same_as; }; template concept TriviallyClearableSequence = Sequence && std::is_trivially_destructible_v && !HasClearGentlyMethod && requires (T s) { { s.clear() } -> std::same_as; }; template concept Container = Iterable && requires (T x, typename T::iterator it) { x.erase(it); }; template concept Extractable = Iterable && requires (T x, typename T::iterator it) { x.extract(it); }; template concept MapLike = Container && requires (T x) { std::is_same_v>; }; template future<> clear_gently(T& o) noexcept; template requires (SmartPointer || SharedPointer) future<> clear_gently(foreign_ptr& o) noexcept; template future<> clear_gently(T& o) noexcept; template future<> clear_gently(T& o) noexcept; template future<> clear_gently(std::array&a) noexcept; template requires (StringLike || TriviallyClearableSequence) future<> clear_gently(T& s) noexcept; template requires (!StringLike && !TriviallyClearableSequence) future<> clear_gently(T& v) noexcept; template future<> clear_gently(T& c) noexcept; template requires (!StringLike && !Sequence && !MapLike) future<> clear_gently(T& c) noexcept; template requires (!StringLike && !Sequence && !MapLike && !Extractable) future<> clear_gently(T& c) noexcept; template future<> clear_gently(std::optional& opt) noexcept; template future<> clear_gently(seastar::optimized_optional& opt) noexcept; namespace internal { template concept HasClearGentlyImpl = requires (T x) { { clear_gently(x) } -> std::same_as>; }; template requires HasClearGentlyImpl future<> clear_gently(T& x) noexcept { return utils::clear_gently(x); } // This default implementation of clear_gently // is required to "terminate" recursive clear_gently calls // at trivial objects template future<> clear_gently(T&) noexcept { return make_ready_future<>(); } } // namespace internal template future<> clear_gently(T& o) noexcept { return futurize_invoke(std::bind(&T::clear_gently, &o)); } template requires (SmartPointer || SharedPointer) future<> clear_gently(foreign_ptr& o) noexcept { return smp::submit_to(o.get_owner_shard(), [&o] { auto wrapped = o.release(); return internal::clear_gently(wrapped).then([wrapped = std::move(wrapped)] {}); }); } template future<> clear_gently(T& ptr) noexcept { auto o = std::exchange(ptr, nullptr); if (o.use_count() == 1) { return internal::clear_gently(const_cast&>(*o)).then([o = std::move(o)] {}); } return make_ready_future<>(); } template future<> clear_gently(T& ptr) noexcept { if (auto o = std::exchange(ptr, nullptr)) { return internal::clear_gently(const_cast&>(*o)).then([o = std::move(o)] {}); } else { return make_ready_future<>(); } } template future<> clear_gently(std::array& a) noexcept { return do_for_each(a, [] (T& o) { return internal::clear_gently(const_cast&>(o)); }); } // Trivially destructible elements can be safely cleared in bulk template requires (StringLike || TriviallyClearableSequence) future<> clear_gently(T& s) noexcept { // Note: clear() is pointless in this case since it keeps the allocation // and since the values are trivially destructible it achieves nothing. // `s = {}` will free the vector/string allocation. s = {}; return make_ready_future<>(); } // Clear the elements gently and destroy them one-by-one // in reverse order, to avoid copying. template requires (!StringLike && !TriviallyClearableSequence) future<> clear_gently(T& v) noexcept { return do_until([&v] { return v.empty(); }, [&v] { return internal::clear_gently(const_cast&>(v.back())).then([&v] { v.pop_back(); }); }); return make_ready_future<>(); } template future<> clear_gently(T& c) noexcept { return do_until([&c] { return c.empty(); }, [&c] { auto it = c.begin(); return internal::clear_gently(const_cast&>(it->second)).then([&c, it = std::move(it)] () mutable { c.erase(it); }); }); } template requires (!StringLike && !Sequence && !MapLike) future<> clear_gently(T& c) noexcept { return do_until([&c] { return c.empty(); }, [&c] { auto node = c.extract(c.begin()); return internal::clear_gently(const_cast&>(node.value())).finally([node = std::move(node)] {}); }); } template requires (!StringLike && !Sequence && !MapLike && !Extractable) future<> clear_gently(T& c) noexcept { return do_until([&c] { return c.empty(); }, [&c] { auto it = c.begin(); return internal::clear_gently(const_cast&>(*it)).then([&c, it = std::move(it)] () mutable { c.erase(it); }); }); } template future<> clear_gently(std::optional& opt) noexcept { if (opt) { return utils::clear_gently(const_cast&>(*opt)); } else { return make_ready_future<>(); } } template future<> clear_gently(seastar::optimized_optional& opt) noexcept { if (opt) { return utils::clear_gently(*opt); } else { return make_ready_future<>(); } } template future<> dispose_gently(T&& o) noexcept { // No need to extend the smart pointer's lifetime because // the lower-level clear_gently implementation captures it in // a continuation, if needed. return clear_gently(o); } // Note that dispose_gently needs to extend the object's lifetime so // clear_gently can yield. // If the caller can hold on to the object while it's being cleared // e.g. in the coroutine frame, seastar thread, or when the containing // object is held by the top-most caller, it is advised to do so. template requires (std::is_rvalue_reference_v && ...) future<> dispose_gently(T&&... o) noexcept { return do_with(std::move(o)..., [] (auto&... objs) mutable { static auto clear_step = [] (auto& obj) { return clear_gently(obj).then_wrapped([] (future<> f) { // Ignore exceptions because there's nothing we can do about them here // and the objects are destroyed anyway. f.ignore_ready_future(); }); }; auto f = make_ready_future<>(); // Chain clearing of the objects to avoid excessive task creation (..., (f = f.then([&objs] mutable { return clear_step(objs); }))); return f; }); } namespace internal { template concept gently_reservable = requires(T x) { { x.capacity() } -> std::same_as; { x.reserve_partial(10) } -> std::same_as; }; } // namespace internal // reserve_gently gently reserves memory in containers which support partial reserve. future<> reserve_gently(internal::gently_reservable auto& container, size_t size) { return seastar::do_until([&container, size] { return container.capacity() == size; }, [&container, size]() { container.reserve_partial(size); return seastar::make_ready_future(); }); } }