/* * Copyright (C) 2019-present ScyllaDB */ /* * SPDX-License-Identifier: AGPL-3.0-or-later */ #pragma once #include #include #include #include "observable.hh" #include "seastarx.hh" namespace utils { // This file contains two templates, updateable_value_source and updateable_value. // // The two are analogous to T and const T& respectively, with the following additional // functionality: // // - updateable_value contains a copy of T, so it can be accessed without indirection // - updateable_value and updateable_value_source track each other, so if they move, // the references are updated // - an observe() function is provided (to both) that can be used to attach a callback // that is called whenever the value changes template class updateable_value_source; class updateable_value_source_base; // Base class for updateable_value, containing functionality for tracking // the update source. Used to reduce template bloat and not meant to be used // directly. class updateable_value_base { protected: const updateable_value_source_base* _source = nullptr; public: updateable_value_base() = default; explicit updateable_value_base(const updateable_value_source_base& source); ~updateable_value_base(); updateable_value_base(const updateable_value_base&); updateable_value_base& operator=(const updateable_value_base&); updateable_value_base(updateable_value_base&&) noexcept; updateable_value_base& operator=(updateable_value_base&&) noexcept; updateable_value_base& operator=(std::nullptr_t); friend class updateable_value_source_base; }; // A T that can be updated at runtime; uses updateable_value_base to track // the source as the object is moved or copied. Copying across shards is supported. template class updateable_value : public updateable_value_base { T _value = {}; private: const updateable_value_source* source() const; public: updateable_value() = default; explicit updateable_value(T value) : _value(std::move(value)) {} explicit updateable_value(const updateable_value_source& source); updateable_value(const updateable_value& v); updateable_value& operator=(T value); updateable_value& operator=(const updateable_value&); updateable_value(updateable_value&&) noexcept; updateable_value& operator=(updateable_value&&) noexcept; const T& operator()() const { return _value; } operator const T& () const { return _value; } const T& get() const { return _value; } observer observe(std::function callback) const; friend class updateable_value_source_base; template friend class updateable_value_source; }; // Contains the mechanisms to track updateable_value_base. Used to reduce template // bloat and not meant to be used directly. class updateable_value_source_base { protected: // This class contains two different types of state: values and // references to updateable_value_base. We consider adding and removing // such references const operations since they don't change the logical // state of the object (they don't allow changing the carried value). mutable std::vector _refs; // all connected updateable_values on this shard void for_each_ref(std::function func); protected: ~updateable_value_source_base(); void add_ref(updateable_value_base* ref) const; void del_ref(updateable_value_base* ref) const; void update_ref(updateable_value_base* old_ref, updateable_value_base* new_ref) const; friend class updateable_value_base; }; template class updateable_value_source : public updateable_value_source_base { T _value; mutable observable _updater; void for_each_ref(std::function*)> func) { updateable_value_source_base::for_each_ref([func = std::move(func)] (updateable_value_base* ref) { func(static_cast*>(ref)); }); }; private: void add_ref(updateable_value* ref) const { updateable_value_source_base::add_ref(ref); } void del_ref(updateable_value* ref) const { updateable_value_source_base::del_ref(ref); } void update_ref(updateable_value* old_ref, updateable_value* new_ref) const { updateable_value_source_base::update_ref(old_ref, new_ref); } public: explicit updateable_value_source(T value = T{}) : _value(std::move(value)) {} updateable_value_source(const updateable_value_source& x) : updateable_value_source(x.get()) { // We can't copy x's _refs and therefore also _updater. So this is an imperfect copy. // This copy constructor therefore breaks updates made to the original copy; it only // exists because unit tests copy configs like mad. } void set(T value) { if (value == _value) { return; } _value = std::move(value); for_each_ref([&] (updateable_value* ref) { ref->_value = _value; }); _updater(_value); } const T& get() const { return _value; } const T& operator()() const { return _value; } observable& as_observable() const { return _updater; } observer observe(std::function callback) const { return _updater.observe(std::move(callback)); } friend class updateable_value_base; }; template updateable_value::updateable_value(const updateable_value_source& source) : updateable_value_base(source) , _value(source.get()) { } template updateable_value::updateable_value(const updateable_value& v) : updateable_value_base(v), _value(v._value) { } template updateable_value& updateable_value::operator=(T value) { updateable_value_base::operator=(nullptr); _value = std::move(value); return *this; } template updateable_value& updateable_value::operator=(const updateable_value& v) { if (this != &v) { // Copy early to trigger exceptions, later move auto new_val = v._value; updateable_value_base::operator=(v); _value = std::move(new_val); } return *this; } template updateable_value::updateable_value(updateable_value&& v) noexcept : updateable_value_base(v) , _value(std::move(v._value)) { } template updateable_value& updateable_value::operator=(updateable_value&& v) noexcept { if (this != &v) { updateable_value_base::operator=(std::move(v)); _value = std::move(v._value); } return *this; } template inline const updateable_value_source* updateable_value::source() const { return static_cast*>(_source); } template observer updateable_value::observe(std::function callback) const { return source()->observe(std::move(callback)); } }