summaryrefslogtreecommitdiffstats
path: root/sholder.hh
diff options
context:
space:
mode:
Diffstat (limited to 'sholder.hh')
-rw-r--r--sholder.hh145
1 files changed, 145 insertions, 0 deletions
diff --git a/sholder.hh b/sholder.hh
new file mode 100644
index 0000000..2a4f758
--- /dev/null
+++ b/sholder.hh
@@ -0,0 +1,145 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+#include <memory>
+#include <atomic>
+
+#include "lock.hh"
+
+/** This is sort of a light-weight RCU idea.
+ Suitable for when you frequently consult some "readonly" state, which infrequently
+ gets changed. One way of dealing with this is fully locking access to the state, but
+ this is rather wasteful.
+
+ Instead, in the code below, the frequent users of the state get a "readonly" copy of it,
+ which they can consult. On access, we atomically compare if the local copy is still current
+ with the global one. If it isn't we do the lock thing, and create a new local copy.
+
+ Meanwhile, to upgrade the global state, methods are offered that do appropriate locking
+ and upgrade the 'generation' counter, signaling to the local copies that they need to be
+ refreshed on the next access.
+
+ Two ways to change the global copy are available:
+ getCopy(), which delivers a deep copy of the current state, followed by setState()
+ modify(), which accepts a (lambda)function that modifies the state
+
+ NOTE: The actual destruction of the 'old' state happens when the last local state
+ relinquishes its access to the state.
+
+ "read-only"
+ Sometimes, a 'state' can contain parts that can safely be modified by multiple users, for
+ example, atomic counters. In such cases, it may be useful to explicitly declare such counters
+ as mutable. */
+
+template<typename T> class GlobalStateHolder;
+
+template<typename T>
+class LocalStateHolder
+{
+public:
+ explicit LocalStateHolder(GlobalStateHolder<T>* source) : d_source(source)
+ {}
+
+ const T* operator->() // fast const-only access, but see "read-only" above
+ {
+ if(d_source->getGeneration() != d_generation) {
+ d_source->getState(&d_state, & d_generation);
+ }
+
+ return d_state.get();
+ }
+ const T& operator*() // fast const-only access, but see "read-only" above
+ {
+ return *operator->();
+ }
+
+ void reset()
+ {
+ d_generation=0;
+ d_state.reset();
+ }
+private:
+ std::shared_ptr<T> d_state;
+ unsigned int d_generation{0};
+ const GlobalStateHolder<T>* d_source;
+};
+
+template<typename T>
+class GlobalStateHolder
+{
+public:
+ GlobalStateHolder() : d_state(std::make_shared<T>())
+ {}
+ LocalStateHolder<T> getLocal()
+ {
+ return LocalStateHolder<T>(this);
+ }
+
+ void setState(const T& state) //!< Safely & slowly change the global state
+ {
+ std::shared_ptr<T> newState = std::make_shared<T>(state);
+ {
+ *(d_state.lock()) = std::move(newState);
+ d_generation++;
+ }
+ }
+
+ void setState(T&& state) //!< Safely & slowly change the global state
+ {
+ std::shared_ptr<T> newState = std::make_shared<T>(std::move(state));
+ {
+ *(d_state.lock()) = std::move(newState);
+ d_generation++;
+ }
+ }
+
+ T getCopy() const //!< Safely & slowly get a copy of the global state
+ {
+ return *(*(d_state.lock()));
+ }
+
+ //! Safely & slowly modify the global state
+ template<typename F>
+ void modify(F act) {
+ auto state = d_state.lock();
+ auto newState = *(*state); // and yes, these three steps are necessary, can't ever modify state in place, even when locked!
+ act(newState);
+ *state = std::make_shared<T>(std::move(newState));
+ ++d_generation;
+ }
+
+ typedef T value_type;
+private:
+ unsigned int getGeneration() const
+ {
+ return d_generation;
+ }
+ void getState(std::shared_ptr<T>* state, unsigned int* generation) const
+ {
+ *state = *d_state.lock();
+ *generation = d_generation;
+ }
+ friend class LocalStateHolder<T>;
+
+ mutable LockGuarded<std::shared_ptr<T>> d_state;
+ std::atomic<unsigned int> d_generation{1};
+};