mirror of
https://github.com/oxen-io/lokinet.git
synced 2024-10-31 09:20:21 +00:00
b4440094b0
- util::Mutex is now a std::shared_timed_mutex, which is capable of
exclusive and shared locks.
- util::Lock is still present as a std::lock_guard<util::Mutex>.
- the locking annotations are preserved, but updated to the latest
supported by clang rather than using abseil's older/deprecated ones.
- ACQUIRE_LOCK macro is gone since we don't pass mutexes by pointer into
locks anymore (WTF abseil).
- ReleasableLock is gone. Instead there are now some llarp::util helper
methods to obtain unique and/or shared locks:
- `auto lock = util::unique_lock(mutex);` gets an RAII-but-also
unlockable object (std::unique_lock<T>, with T inferred from
`mutex`).
- `auto lock = util::shared_lock(mutex);` gets an RAII shared (i.e.
"reader") lock of the mutex.
- `auto lock = util::unique_locks(mutex1, mutex2, mutex3);` can be
used to atomically lock multiple mutexes at once (returning a
tuple of the locks).
This are templated on the mutex which makes them a bit more flexible
than using a concrete type: they can be used for any type of lockable
mutex, not only util::Mutex. (Some of the code here uses them for
getting locks around a std::mutex). Until C++17, using the RAII types
is painfully verbose:
```C++
// pre-C++17 - needing to figure out the mutex type here is annoying:
std::unique_lock<util::Mutex> lock(mutex);
// pre-C++17 and even more verbose (but at least the type isn't needed):
std::unique_lock<decltype(mutex)> lock(mutex);
// our compromise:
auto lock = util::unique_lock(mutex);
// C++17:
std::unique_lock lock(mutex);
```
All of these functions will also warn (under gcc or clang) if you
discard the return value. You can also do fancy things like
`auto l = util::unique_lock(mutex, std::adopt_lock)` (which lets a
lock take over an already-locked mutex).
- metrics code is gone, which also removes a big pile of code that was
only used by metrics:
- llarp::util::Scheduler
- llarp:🧵:TimerQueue
- llarp::util::Stopwatch
221 lines
5.5 KiB
C++
221 lines
5.5 KiB
C++
#ifndef LLARP_THREADING_HPP
|
|
#define LLARP_THREADING_HPP
|
|
|
|
#include <thread>
|
|
#include <shared_mutex>
|
|
#include <mutex>
|
|
#include <nonstd/optional.hpp>
|
|
|
|
#include "annotations.hpp"
|
|
|
|
#include <iostream>
|
|
#include <thread>
|
|
|
|
#if defined(WIN32) && !defined(__GNUC__)
|
|
#include <process.h>
|
|
using pid_t = int;
|
|
#else
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#ifdef TRACY_ENABLE
|
|
#include "Tracy.hpp"
|
|
#define DECLARE_LOCK(type, var, ...) TracyLockable(type, var)
|
|
#else
|
|
#define DECLARE_LOCK(type, var, ...) type var __VA_ARGS__
|
|
#endif
|
|
|
|
namespace llarp
|
|
{
|
|
namespace util
|
|
{
|
|
/// a mutex that does nothing
|
|
///
|
|
/// this exists to convert mutexes that were initially in use (but may no
|
|
/// longer be necessary) into no-op placeholders (except in debug mode
|
|
/// where they complain loudly when they are actually accessed across
|
|
/// different threads; see below).
|
|
///
|
|
/// the idea is to "turn off" the mutexes and see where they are actually
|
|
/// needed.
|
|
struct CAPABILITY("mutex") NullMutex
|
|
{
|
|
#ifdef LOKINET_DEBUG
|
|
/// in debug mode, we implement lock() to enforce that any lock is only
|
|
/// used from a single thread. the point of this is to identify locks that
|
|
/// are actually needed by dying a painful death when used across threads
|
|
mutable nonstd::optional< std::thread::id > m_id;
|
|
void
|
|
lock() const
|
|
{
|
|
if(!m_id)
|
|
{
|
|
m_id.emplace(std::this_thread::get_id());
|
|
}
|
|
else if(m_id.value() != std::this_thread::get_id())
|
|
{
|
|
std::cerr << "NullMutex " << this
|
|
<< " was used across threads: locked by "
|
|
<< std::this_thread::get_id()
|
|
<< " and was previously locked by " << m_id.value() << "\n";
|
|
// if you're encountering this abort() call, you may have discovered a
|
|
// case where a NullMutex should be reverted to a "real mutex"
|
|
std::abort();
|
|
}
|
|
}
|
|
#else
|
|
void
|
|
lock() const
|
|
{
|
|
}
|
|
#endif
|
|
// Does nothing; once locked the mutex belongs to that thread forever
|
|
void
|
|
unlock() const
|
|
{
|
|
}
|
|
};
|
|
|
|
/// a lock that does nothing
|
|
struct SCOPED_CAPABILITY NullLock
|
|
{
|
|
NullLock(NullMutex& mtx) ACQUIRE(mtx)
|
|
{
|
|
mtx.lock();
|
|
}
|
|
|
|
~NullLock() RELEASE()
|
|
{
|
|
(void)this; // trick clang-tidy
|
|
}
|
|
};
|
|
|
|
/// Default mutex type, supporting shared and exclusive locks.
|
|
using Mutex = std::shared_timed_mutex;
|
|
|
|
/// Basic RAII lock type for the default mutex type.
|
|
using Lock = std::lock_guard< Mutex >;
|
|
|
|
/// Returns a unique lock around the given lockable (typically a mutex)
|
|
/// which gives exclusive control and is unlockable/relockable. Any extra
|
|
/// argument (e.g. std::defer_lock) is forwarded to the unique_lock
|
|
/// constructor.
|
|
template < typename Mutex, typename... Args >
|
|
#ifdef __GNUG__
|
|
[[gnu::warn_unused_result]]
|
|
#endif
|
|
std::unique_lock< Mutex >
|
|
unique_lock(Mutex& lockable, Args&&... args)
|
|
{
|
|
return std::unique_lock< Mutex >(lockable, std::forward< Args >(args)...);
|
|
}
|
|
|
|
/// Returns a shared lock around the given lockable (typically a mutex)
|
|
/// which gives "reader" access (i.e. which can be shared with other reader
|
|
/// locks but not unique locks). Any extra argument (e.g. std::defer_lock)
|
|
/// is forwarded to the std::shared_lock constructor.
|
|
template < typename Mutex, typename... Args >
|
|
#ifdef __GNUG__
|
|
[[gnu::warn_unused_result]]
|
|
#endif
|
|
std::shared_lock< Mutex >
|
|
shared_lock(Mutex& lockable, Args&&... args)
|
|
{
|
|
return std::shared_lock< Mutex >(lockable, std::forward< Args >(args)...);
|
|
}
|
|
|
|
/// Obtains multiple unique locks simultaneously and atomically. Returns a
|
|
/// tuple of all the held locks.
|
|
template < typename... Mutex >
|
|
#ifdef __GNUG__
|
|
[[gnu::warn_unused_result]]
|
|
#endif
|
|
std::tuple< std::unique_lock< Mutex >... >
|
|
unique_locks(Mutex&... lockables)
|
|
{
|
|
std::lock(lockables...);
|
|
return std::make_tuple(
|
|
std::unique_lock< Mutex >(lockables, std::adopt_lock)...);
|
|
}
|
|
|
|
class Semaphore
|
|
{
|
|
private:
|
|
std::mutex m_mutex; // protects m_count
|
|
size_t m_count GUARDED_BY(m_mutex);
|
|
std::condition_variable m_cv;
|
|
|
|
public:
|
|
Semaphore(size_t count) : m_count(count)
|
|
{
|
|
}
|
|
|
|
void
|
|
notify() EXCLUDES(m_mutex)
|
|
{
|
|
{
|
|
std::lock_guard< std::mutex > lock(m_mutex);
|
|
m_count++;
|
|
}
|
|
m_cv.notify_one();
|
|
}
|
|
|
|
void
|
|
wait() EXCLUDES(m_mutex)
|
|
{
|
|
auto lock = unique_lock(m_mutex);
|
|
m_cv.wait(lock, [this] { return m_count > 0; });
|
|
m_count--;
|
|
}
|
|
|
|
bool
|
|
waitFor(std::chrono::microseconds timeout) EXCLUDES(m_mutex)
|
|
{
|
|
auto lock = unique_lock(m_mutex);
|
|
if(!m_cv.wait_for(lock, timeout, [this] { return m_count > 0; }))
|
|
return false;
|
|
|
|
m_count--;
|
|
return true;
|
|
}
|
|
};
|
|
|
|
void
|
|
SetThreadName(const std::string& name);
|
|
|
|
inline pid_t
|
|
GetPid()
|
|
{
|
|
#ifdef WIN32
|
|
return _getpid();
|
|
#else
|
|
return ::getpid();
|
|
#endif
|
|
}
|
|
|
|
// type for detecting contention on a resource
|
|
struct ContentionKiller
|
|
{
|
|
template < typename F >
|
|
void
|
|
TryAccess(F visit) const
|
|
#if defined(LOKINET_DEBUG)
|
|
EXCLUDES(_access)
|
|
#endif
|
|
{
|
|
#if defined(LOKINET_DEBUG)
|
|
NullLock lock(_access);
|
|
#endif
|
|
visit();
|
|
}
|
|
#if defined(LOKINET_DEBUG)
|
|
private:
|
|
mutable NullMutex _access;
|
|
#endif
|
|
};
|
|
} // namespace util
|
|
} // namespace llarp
|
|
|
|
#endif
|