dncurrency/nano/lib/locks.hpp

331 lines
6.9 KiB
C++

#pragma once
#define USING_NANO_TIMED_LOCKS (NANO_TIMED_LOCKS > 0)
#if USING_NANO_TIMED_LOCKS
#include <nano/lib/timer.hpp>
#endif
#include <condition_variable>
#include <mutex>
namespace nano
{
class mutex;
extern nano::mutex * mutex_to_filter;
extern nano::mutex mutex_to_filter_mutex;
bool should_be_filtered (const char * name);
bool any_filters_registered ();
enum class mutexes
{
active,
block_arrival,
block_processor,
block_uniquer,
blockstore_cache,
confirmation_height_processor,
election_winner_details,
gap_cache,
network_filter,
observer_set,
request_aggregator,
state_block_signature_verification,
telemetry,
vote_generator,
vote_processor,
vote_uniquer,
votes_cache,
work_pool
};
char const * mutex_identifier (mutexes mutex);
class mutex
{
public:
mutex () = default;
mutex (const char * name_a)
#if USING_NANO_TIMED_LOCKS
:
name (name_a)
#endif
{
#if USING_NANO_TIMED_LOCKS
// This mutex should be filtered
if (name && should_be_filtered (name))
{
std::lock_guard guard (mutex_to_filter_mutex);
mutex_to_filter = this;
}
#endif
}
#if USING_NANO_TIMED_LOCKS
~mutex ()
{
// Unfilter this destroyed mutex
if (name && should_be_filtered (name))
{
// Unregister the mutex
std::lock_guard guard (mutex_to_filter_mutex);
mutex_to_filter = nullptr;
}
}
#endif
void lock ()
{
mutex_m.lock ();
}
void unlock ()
{
mutex_m.unlock ();
}
bool try_lock ()
{
return mutex_m.try_lock ();
}
#if USING_NANO_TIMED_LOCKS
const char * get_name () const
{
return name ? name : "";
}
#endif
private:
#if USING_NANO_TIMED_LOCKS
const char * name{ nullptr };
#endif
std::mutex mutex_m;
};
#if USING_NANO_TIMED_LOCKS
template <typename Mutex>
void output (const char * str, std::chrono::milliseconds time, Mutex & mutex);
template <typename Mutex>
void output_if_held_long_enough (nano::timer<std::chrono::milliseconds> & timer, Mutex & mutex);
#ifndef NANO_TIMED_LOCKS_IGNORE_BLOCKED
template <typename Mutex>
void output_if_blocked_long_enough (nano::timer<std::chrono::milliseconds> & timer, Mutex & mutex);
#endif
template <typename Mutex>
class lock_guard final
{
public:
explicit lock_guard (Mutex & mutex_a) :
guard (mutex_a)
{
}
lock_guard (const lock_guard &) = delete;
lock_guard & operator= (const lock_guard &) = delete;
private:
std::lock_guard<Mutex> guard;
};
template <>
class lock_guard<nano::mutex> final
{
public:
explicit lock_guard (nano::mutex & mutex_a);
~lock_guard () noexcept;
lock_guard (const lock_guard &) = delete;
lock_guard & operator= (const lock_guard &) = delete;
private:
nano::mutex & mut;
nano::timer<std::chrono::milliseconds> timer;
};
template <typename Mutex, typename = std::enable_if_t<std::is_same<Mutex, nano::mutex>::value>>
class unique_lock final
{
public:
unique_lock () = default;
explicit unique_lock (Mutex & mutex_a);
unique_lock (Mutex & mutex_a, std::defer_lock_t) noexcept;
unique_lock (unique_lock && other) = delete;
unique_lock & operator= (unique_lock && other) noexcept;
~unique_lock () noexcept;
unique_lock (const unique_lock &) = delete;
unique_lock & operator= (const unique_lock &) = delete;
void lock ();
bool try_lock ();
void unlock ();
bool owns_lock () const noexcept;
explicit operator bool () const noexcept;
Mutex * mutex () const noexcept;
private:
Mutex * mut{ nullptr };
bool owns{ false };
nano::timer<std::chrono::milliseconds> timer;
void validate () const;
void lock_impl ();
friend class condition_variable;
};
/** Assumes std implementations of std::condition_variable never actually call nano::unique_lock::lock/unlock,
but instead use OS intrinsics with the mutex handle directly. Due to this we also do not account for any
time the condition variable is blocked on another holder of the mutex. */
class condition_variable final
{
public:
condition_variable () = default;
condition_variable (condition_variable const &) = delete;
condition_variable & operator= (condition_variable const &) = delete;
void notify_one () noexcept;
void notify_all () noexcept;
void wait (nano::unique_lock<nano::mutex> & lt);
template <typename Pred>
void wait (nano::unique_lock<nano::mutex> & lk, Pred pred)
{
while (!pred ())
{
wait (lk);
}
}
template <typename Clock, typename Duration>
std::cv_status wait_until (nano::unique_lock<nano::mutex> & lk, std::chrono::time_point<Clock, Duration> const & timeout_time)
{
if (!lk.mut || !lk.owns)
{
throw (std::system_error (std::make_error_code (std::errc::operation_not_permitted)));
}
output_if_held_long_enough (lk.timer, *lk.mut);
// Start again in case cnd.wait calls unique_lock::lock/unlock () depending on some implementations
lk.timer.start ();
auto cv_status = cnd.wait_until (lk, timeout_time);
lk.timer.restart ();
return cv_status;
}
template <typename Clock, typename Duration, typename Pred>
bool wait_until (nano::unique_lock<nano::mutex> & lk, std::chrono::time_point<Clock, Duration> const & timeout_time, Pred pred)
{
while (!pred ())
{
if (wait_until (lk, timeout_time) == std::cv_status::timeout)
{
return pred ();
}
}
return true;
}
template <typename Rep, typename Period>
void wait_for (nano::unique_lock<nano::mutex> & lk, std::chrono::duration<Rep, Period> const & rel_time)
{
wait_until (lk, std::chrono::steady_clock::now () + rel_time);
}
template <typename Rep, typename Period, typename Pred>
bool wait_for (nano::unique_lock<nano::mutex> & lk, std::chrono::duration<Rep, Period> const & rel_time, Pred pred)
{
return wait_until (lk, std::chrono::steady_clock::now () + rel_time, std::move (pred));
}
private:
std::condition_variable_any cnd;
};
#else
template <typename Mutex>
using lock_guard = std::lock_guard<Mutex>;
template <typename Mutex>
using unique_lock = std::unique_lock<Mutex>;
// For consistency wrapping the less well known _any variant which can be used with any lockable type
using condition_variable = std::condition_variable_any;
#endif
/** A general purpose monitor template */
template <class T>
class locked
{
public:
using value_type = T;
template <typename... Args>
locked (Args &&... args) :
obj (std::forward<Args> (args)...)
{
}
struct scoped_lock final
{
scoped_lock (locked * owner_a) :
owner (owner_a)
{
owner->mutex.lock ();
}
~scoped_lock ()
{
owner->mutex.unlock ();
}
T * operator-> ()
{
return &owner->obj;
}
T & get () const
{
return owner->obj;
}
T & operator* () const
{
return get ();
}
locked * owner{ nullptr };
};
scoped_lock operator-> ()
{
return scoped_lock (this);
}
T & operator= (T const & other)
{
nano::unique_lock<nano::mutex> lk (mutex);
obj = other;
return obj;
}
operator T () const
{
return obj;
}
/** Returns a scoped lock wrapper, allowing multiple calls to the underlying object under the same lock */
scoped_lock lock ()
{
return scoped_lock (this);
}
private:
T obj;
nano::mutex mutex;
};
}