From: Adam C. Emerson Date: Wed, 4 May 2016 20:39:13 +0000 (-0400) Subject: common: Add locking template functions and macros X-Git-Tag: v13.0.0~153^2~5 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=d08191e2daad4a2ef74e06d209dac42fd8e0acef;p=ceph-ci.git common: Add locking template functions and macros These infer the type of the unique/shared/shunique lock from the type of the mutex. Signed-off-by: Adam C. Emerson --- diff --git a/src/common/convenience.h b/src/common/convenience.h new file mode 100644 index 00000000000..b831cbd7260 --- /dev/null +++ b/src/common/convenience.h @@ -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 + * + * 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 +#include +#include +#include + +#include + +#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; +// ``` +// 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 +inline auto uniquely_lock(Mutex&& m, Args&& ...args) + -> std::unique_lock > { + return std::unique_lock >( + std::forward(m), std::forward(args)... ); +} + +template +inline auto sharingly_lock(Mutex&& m, Args&& ...args) + -> boost::shared_lock > { + return + boost::shared_lock >( + std::forward(m), std::forward(args)...); +} + +template +inline auto shuniquely_lock(std::unique_lock&& m, Args&& ...args) + -> shunique_lock > { + return shunique_lock >( + std::forward >(m), std::forward(args)...); +} + +template +inline auto shuniquely_lock(boost::shared_lock&& m, Args&& ...args) + -> shunique_lock > { + return shunique_lock >( + std::forward >(m), + std::forward(args)...); +} + +template +inline auto shuniquely_lock(Mutex&& m, Args&& ...args) + -> shunique_lock > { + return shunique_lock >( + std::forward(m), std::forward(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 +inline auto guardedly_lock(Mutex&& m) + -> std::lock_guard > { + 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(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(m), std::adopt_lock }; +} + +template +inline auto guardedly_lock(Mutex&& m, std::adopt_lock_t) + -> std::lock_guard > { + return { std::forward(m), std::adopt_lock }; +} + +template +inline auto with_unique_lock(Mutex&& mutex, Fun&& fun, Args&&... args) + -> decltype(fun(std::forward(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)); + return std::forward(fun)(std::forward(args)...); +} + +template +inline auto with_shared_lock(Mutex&& mutex, Fun&& fun, Args&&... args) + -> decltype(fun(std::forward(args)...)) { + auto l = sharingly_lock(std::forward(mutex)); + return std::forward(fun)(std::forward(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> +#define SHARED_LOCK_T(m) \ + ::std::shared_lock> +#define SHUNIQUE_LOCK_T(m) \ + ::ceph::shunique_lock> + +#endif // CEPH_COMMON_CONVENIENCE_H