]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
common: Add locking template functions and macros
authorAdam C. Emerson <aemerson@redhat.com>
Wed, 4 May 2016 20:39:13 +0000 (16:39 -0400)
committerAdam C. Emerson <aemerson@redhat.com>
Wed, 9 Aug 2017 02:39:20 +0000 (22:39 -0400)
These infer the type of the unique/shared/shunique lock from the type of
the mutex.

Signed-off-by: Adam C. Emerson <aemerson@redhat.com>
src/common/convenience.h [new file with mode: 0644]

diff --git a/src/common/convenience.h b/src/common/convenience.h
new file mode 100644 (file)
index 0000000..b831cbd
--- /dev/null
@@ -0,0 +1,164 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- 
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2004-2006 Sage Weil <sage@newdream.net>
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation.  See file COPYING.
+ *
+ */
+
+#include <mutex>
+#include <memory>
+#include <type_traits>
+#include <utility>
+
+#include <boost/thread/shared_mutex.hpp>
+
+#include "common/backport14.h"
+#include "common/shunique_lock.h"
+
+#include "include/assert.h" // I despise you. Not you the reader, I'm talking
+                            // to the include file.
+
+
+#ifndef CEPH_COMMON_CONVENIENCE_H
+#define CEPH_COMMON_CONVENIENCE_H
+
+namespace ceph {
+
+// Lock Factories
+// ==============
+//
+// I used to, whenever I declared a mutex member variable of a class,
+// declare a pile of types like:
+// ```cpp
+// using unique_lock = ::std::unique_lock<decltype(membermutex)>;
+// ```
+// to avoid having to type that big, long type at every use. It also
+// let me change the mutex type later. It's inelegant and breaks down
+// if you have more than one type of mutex in the same class. So here
+// are some lock factories.
+template<typename Mutex, typename ...Args>
+inline auto uniquely_lock(Mutex&& m, Args&& ...args)
+  -> std::unique_lock<remove_reference_t<Mutex> > {
+  return std::unique_lock<remove_reference_t<Mutex> >(
+    std::forward<Mutex>(m), std::forward<Args>(args)... );
+}
+
+template<typename Mutex, typename ...Args>
+inline auto sharingly_lock(Mutex&& m, Args&& ...args)
+  -> boost::shared_lock<remove_reference_t<Mutex> > {
+  return
+    boost::shared_lock<remove_reference_t<Mutex> >(
+      std::forward<Mutex>(m), std::forward<Args>(args)...);
+}
+
+template<typename Mutex, typename ...Args>
+inline auto shuniquely_lock(std::unique_lock<Mutex>&& m, Args&& ...args)
+  -> shunique_lock<remove_reference_t<Mutex> > {
+  return shunique_lock<remove_reference_t<Mutex> >(
+    std::forward<std::unique_lock<Mutex> >(m), std::forward<Args>(args)...);
+}
+
+template<typename Mutex, typename ...Args>
+inline auto shuniquely_lock(boost::shared_lock<Mutex>&& m, Args&& ...args)
+  -> shunique_lock<remove_reference_t<Mutex> > {
+  return shunique_lock<remove_reference_t<Mutex> >(
+    std::forward<boost::shared_lock<Mutex> >(m),
+    std::forward<Args>(args)...);
+}
+
+template<typename Mutex, typename ...Args>
+inline auto shuniquely_lock(Mutex&& m, Args&& ...args)
+  -> shunique_lock<remove_reference_t<Mutex> > {
+  return shunique_lock<remove_reference_t<Mutex> >(
+    std::forward<Mutex>(m), std::forward<Args>(args)...);
+}
+
+// All right! These two don't work like the others. You cannot do
+// `auto l = guardedly_lock(m)` since copy elision before C++17 is
+// optional. C++17 makes it mandatory so these workarounds won't be
+// needed.
+//
+// To use this, you'll need to do something like:
+// `auto&& l = guardedly_lock(m)`
+// This way, we aren't actually copying or moving a value, we're
+// binding a reference to a temporary which extends its lifetime until
+// the end of the enclosing block.
+//
+// You may in fact want to use
+// `[[gnu::unused]] auto&& l = guardedly_lock(m)`
+// To avoid the unused variable warning. Since reference assignment
+// doesn't have side effects, normally, this just looks to the
+// compiler (whose static analysis is not the sharpest hammer in the
+// drawer) like a reference we bind for no reason and never
+// use. Perhaps future compilers will be smarter about this, but since
+// they'll also implement C++17 people might not care.
+//
+
+template<typename Mutex>
+inline auto guardedly_lock(Mutex&& m)
+  -> std::lock_guard<remove_reference_t<Mutex> > {
+  m.lock();
+  // So the way this works is that Copy List Initialization creates
+  // one and only one Temporary. There is no implicit copy that is
+  // generally optimized away the way there is if we were to just try
+  // something like `return std::lock_guard<Mutex>(m)`.
+  //
+  // The function then returns this temporary as a prvalue. We cannot
+  // bind it to a variable, because that would implicitly copy it
+  // (even if in practice RVO would mean there is no copy), so instead
+  // the user can bind it to a reference. (It has to be either a const
+  // lvalue reference or an rvalue reference.)
+  //
+  // So we get something analogous to all the others with a mildly
+  // wonky syntax. The need to use [[gnu::unused]] is honestly the
+  // worst part. It makes this construction unfortunately rather
+  // long.
+  return { std::forward<Mutex>(m), std::adopt_lock };
+}
+
+template<typename Mutex>
+inline auto guardedly_lock(Mutex&& m, std::adopt_lock_t)
+  -> std::lock_guard<remove_reference_t<Mutex> > {
+  return { std::forward<Mutex>(m), std::adopt_lock };
+}
+
+template<typename Mutex, typename Fun, typename...Args>
+inline auto with_unique_lock(Mutex&& mutex, Fun&& fun, Args&&... args)
+  -> decltype(fun(std::forward<Args>(args)...)) {
+  // Yes I know there's a lock guard inside and not a unique lock, but
+  // the caller doesn't need to know or care about the internal
+  // details, and the semantics are those of unique locking.
+  [[gnu::unused]] auto&& l = guardedly_lock(std::forward<Mutex>(mutex));
+  return std::forward<Fun>(fun)(std::forward<Args>(args)...);
+}
+
+template<typename Mutex, typename Fun, typename...Args>
+inline auto with_shared_lock(Mutex&& mutex, Fun&& fun, Args&&... args)
+  -> decltype(fun(std::forward<Args>(args)...)) {
+  auto l = sharingly_lock(std::forward<Mutex>(mutex));
+  return std::forward<Fun>(fun)(std::forward<Args>(args)...);
+}
+}
+
+// Lock Types
+// ----------
+//
+// Lock factories are nice, but you still have to type out a huge,
+// obnoxious template type when declaring a function that takes or
+// returns a lock class.
+//
+#define UNIQUE_LOCK_T(m) \
+  ::std::unique_lock<ceph::remove_reference_t<decltype(m)>>
+#define SHARED_LOCK_T(m) \
+  ::std::shared_lock<ceph::remove_reference_t<decltype(m)>>
+#define SHUNIQUE_LOCK_T(m) \
+  ::ceph::shunique_lock<ceph::remove_reference_t<decltype(m)>>
+
+#endif // CEPH_COMMON_CONVENIENCE_H