llarp/util/thread/threading.hpp

Namespaces

Name
llarp
[crypto.hpp]
llarp::util

Classes

Name
struct llarp::util::NullMutex
a mutex that does nothing
struct llarp::util::NullLock
a lock that does nothing
class llarp::util::Semaphore
struct llarp::util::ContentionKiller

Defines

Name
DECLARE_LOCK(type, var, ...)

Macros Documentation

define DECLARE_LOCK

#define DECLARE_LOCK(
    type,
    var,
    ...
)
type var __VA_ARGS__

Source code

#pragma once

#include <thread>
#include <shared_mutex>
#include <mutex>
#include <condition_variable>
#include <optional>

#include "annotations.hpp"

#include <iostream>
#include <thread>

#include <sys/types.h>
#include <unistd.h>

#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
  {
    struct CAPABILITY("mutex") NullMutex
    {
#ifdef LOKINET_DEBUG
      mutable std::optional<std::thread::id> m_id;
      void
      lock() const
      {
        if (!m_id)
        {
          m_id = std::this_thread::get_id();
        }
        else if (*m_id != 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
                    << "\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
      {}
    };

    struct SCOPED_CAPABILITY NullLock
    {
      NullLock(NullMutex& mtx) ACQUIRE(mtx)
      {
        mtx.lock();
      }

      ~NullLock() RELEASE()
      {
        (void)this;  // trick clang-tidy
      }
    };

    using Mutex = std::shared_timed_mutex;

    using Lock = std::lock_guard<Mutex>;

    template <typename... Mutex>
    [[nodiscard]] auto
    unique_locks(Mutex&... lockables)
    {
      std::lock(lockables...);
      return std::make_tuple(std::unique_lock{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)
      {
        std::unique_lock lock{m_mutex};
        m_cv.wait(lock, [this] { return m_count > 0; });
        m_count--;
      }

      bool
      waitFor(std::chrono::microseconds timeout) EXCLUDES(m_mutex)
      {
        std::unique_lock 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()
    {
      return ::getpid();
    }

    // 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

Updated on 2026-01-10 at 22:49:45 +0000