15#include <shared_mutex>
19#include <unordered_map>
35#if defined(_WIN32) || defined(_WIN64)
36#if defined(THREADSCHEDULE_EXPORTS)
37#define THREADSCHEDULE_API __declspec(dllexport)
39#define THREADSCHEDULE_API __declspec(dllimport)
42#define THREADSCHEDULE_API
46using Tid =
unsigned long;
93 std::shared_ptr<class ThreadControlBlock>
control;
148 CloseHandle(handle_);
154 [[nodiscard]]
auto tid() const noexcept ->
Tid
158 [[nodiscard]]
auto std_id() const noexcept -> std::thread::
id
163 [[nodiscard]]
auto native_handle()
const
168 return pthreadHandle_;
196 auto block = std::make_shared<ThreadControlBlock>();
198 block->stdId_ = std::this_thread::get_id();
200 HANDLE realHandle =
nullptr;
201 DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &realHandle,
202 THREAD_SET_INFORMATION | THREAD_QUERY_INFORMATION, FALSE, 0);
203 block->handle_ = realHandle;
205 block->pthreadHandle_ = pthread_self();
212 std::thread::id stdId_;
214 HANDLE handle_ =
nullptr;
216 pthread_t pthreadHandle_{};
235template <
typename Derived>
238 auto self()
const -> Derived
const& {
return static_cast<Derived const&
>(*this); }
241 template <
typename Predicate>
242 [[nodiscard]]
auto filter(Predicate&& pred)
const
244 return self().query().filter(std::forward<Predicate>(pred));
247 [[nodiscard]]
auto count() const ->
size_t {
return self().query().count(); }
249 [[nodiscard]]
auto empty() const ->
bool {
return self().query().empty(); }
251 template <
typename Fn>
254 self().query().for_each(std::forward<Fn>(fn));
257 template <
typename Predicate,
typename Fn>
258 void apply(Predicate&& pred, Fn&& fn)
const
260 self().query().filter(std::forward<Predicate>(pred)).for_each(std::forward<Fn>(fn));
263 template <
typename Fn>
264 [[nodiscard]]
auto map(Fn&& fn)
const -> std::vector<std::invoke_result_t<Fn, RegisteredThreadInfo const&>>
266 return self().query().map(std::forward<Fn>(fn));
269 template <
typename Predicate>
270 [[nodiscard]]
auto find_if(Predicate&& pred)
const -> std::optional<RegisteredThreadInfo>
272 return self().query().find_if(std::forward<Predicate>(pred));
275 template <
typename Predicate>
276 [[nodiscard]]
auto any(Predicate&& pred)
const ->
bool
278 return self().query().any(std::forward<Predicate>(pred));
281 template <
typename Predicate>
282 [[nodiscard]]
auto all(Predicate&& pred)
const ->
bool
284 return self().query().all(std::forward<Predicate>(pred));
287 template <
typename Predicate>
288 [[nodiscard]]
auto none(Predicate&& pred)
const ->
bool
290 return self().query().none(std::forward<Predicate>(pred));
293 [[nodiscard]]
auto take(
size_t n)
const {
return self().query().take(n); }
295 [[nodiscard]]
auto skip(
size_t n)
const {
return self().query().skip(n); }
355 info.
stdId = std::this_thread::get_id();
356 info.
name = std::move(name);
359 try_register(std::move(info));
363 std::string name = std::string(), std::string componentTag = std::string())
368 info.
tid = controlBlock->tid();
369 info.
stdId = controlBlock->std_id();
370 info.
name = std::move(name);
374 try_register(std::move(info));
380 std::unique_lock<std::shared_mutex> lock(mutex_);
381 auto it = threads_.find(tid);
382 if (it != threads_.end())
384 it->second.alive =
false;
385 auto info = it->second;
389 auto cb = onUnregister_;
397 [[nodiscard]]
auto get(
Tid tid)
const -> std::optional<RegisteredThreadInfo>
399 std::shared_lock<std::shared_mutex> lock(mutex_);
400 auto it = threads_.find(tid);
401 if (it == threads_.end())
449 template <
typename Predicate>
452 std::vector<RegisteredThreadInfo> filtered;
453 filtered.reserve(entries_.size());
454 for (
auto const& entry : entries_)
457 filtered.push_back(entry);
462 template <
typename Fn>
465 for (
auto const& entry : entries_)
471 [[nodiscard]]
auto count() const ->
size_t
473 return entries_.size();
476 [[nodiscard]]
auto empty() const ->
bool
478 return entries_.empty();
487 template <
typename Fn>
488 [[nodiscard]]
auto map(Fn&& fn)
const -> std::vector<std::invoke_result_t<Fn, RegisteredThreadInfo const&>>
490 std::vector<std::invoke_result_t<Fn, RegisteredThreadInfo const&>> result;
491 result.reserve(entries_.size());
492 for (
auto const& entry : entries_)
494 result.push_back(fn(entry));
500 template <
typename Predicate>
501 [[nodiscard]]
auto find_if(Predicate&& pred)
const -> std::optional<RegisteredThreadInfo>
503 for (
auto const& entry : entries_)
511 template <
typename Predicate>
512 [[nodiscard]]
auto any(Predicate&& pred)
const ->
bool
514 for (
auto const& entry : entries_)
522 template <
typename Predicate>
523 [[nodiscard]]
auto all(Predicate&& pred)
const ->
bool
525 for (
auto const& entry : entries_)
533 template <
typename Predicate>
534 [[nodiscard]]
auto none(Predicate&& pred)
const ->
bool
536 return !
any(std::forward<Predicate>(pred));
541 auto result = entries_;
542 if (result.size() > n)
549 std::vector<RegisteredThreadInfo> result;
550 if (n < entries_.size())
552 result.assign(entries_.begin() + n, entries_.end());
558 std::vector<RegisteredThreadInfo> entries_;
564 std::vector<RegisteredThreadInfo> snapshot;
565 std::shared_lock<std::shared_mutex> lock(mutex_);
566 snapshot.reserve(threads_.size());
567 for (
auto const& kv : threads_)
569 snapshot.push_back(kv.second);
576 auto blk = lock_block(tid);
578 return unexpected(std::make_error_code(std::errc::no_such_process));
579 return blk->set_affinity(affinity);
584 auto blk = lock_block(tid);
586 return unexpected(std::make_error_code(std::errc::no_such_process));
587 return blk->set_priority(priority);
593 auto blk = lock_block(tid);
595 return unexpected(std::make_error_code(std::errc::no_such_process));
596 return blk->set_scheduling_policy(policy, priority);
601 auto blk = lock_block(tid);
603 return unexpected(std::make_error_code(std::errc::no_such_process));
604 return blk->set_name(name);
610 std::unique_lock<std::shared_mutex> lock(mutex_);
611 onRegister_ = std::move(cb);
616 std::unique_lock<std::shared_mutex> lock(mutex_);
617 onUnregister_ = std::move(cb);
623 std::unique_lock<std::shared_mutex> lock(mutex_);
624 auto it = threads_.find(info.
tid);
625 if (it != threads_.end())
628 threads_.emplace(info.
tid, std::move(info));
631 auto cb = onRegister_;
637 [[nodiscard]]
auto lock_block(
Tid tid)
const -> std::shared_ptr<ThreadControlBlock>
639 std::shared_lock<std::shared_mutex> lock(mutex_);
640 auto it = threads_.find(tid);
641 if (it == threads_.end())
643 return it->second.control;
645 mutable std::shared_mutex mutex_;
646 std::unordered_map<Tid, RegisteredThreadInfo> threads_;
648 std::function<void(RegisteredThreadInfo
const&)> onRegister_;
649 std::function<void(RegisteredThreadInfo
const&)> onUnregister_;
673#if defined(THREADSCHEDULE_RUNTIME)
721 registry_storage() = reg;
741#if defined(THREADSCHEDULE_RUNTIME)
809 std::lock_guard<std::mutex> lock(mutex_);
810 registries_.push_back(reg);
815 std::vector<RegisteredThreadInfo> merged;
816 std::vector<ThreadRegistry*> regs;
818 std::lock_guard<std::mutex> lock(mutex_);
823 auto view = r->query();
824 auto const& entries = view.entries();
825 merged.insert(merged.end(), entries.begin(), entries.end());
831 mutable std::mutex mutex_;
832 std::vector<ThreadRegistry*> registries_;
879 std::string
const& componentTag = std::string())
880 : active_(true), externalReg_(nullptr)
883 (void)block->set_name(name);
884 registry().register_current_thread(block, name, componentTag);
888 std::string
const& componentTag = std::string())
889 : active_(true), externalReg_(®)
892 (void)block->set_name(name);
893 externalReg_->register_current_thread(block, name, componentTag);
899 if (externalReg_ !=
nullptr)
900 externalReg_->unregister_current_thread();
902 registry().unregister_current_thread();
908 : active_(other.active_), externalReg_(other.externalReg_)
910 other.active_ =
false;
911 other.externalReg_ =
nullptr;
919 if (externalReg_ !=
nullptr)
920 externalReg_->unregister_current_thread();
922 registry().unregister_current_thread();
924 active_ = other.active_;
925 externalReg_ = other.externalReg_;
926 other.active_ =
false;
927 other.externalReg_ =
nullptr;
967 std::vector<std::string> candidates = {
"cgroup.threads",
"tasks",
"cgroup.procs"};
968 for (
auto const& file : candidates)
970 std::string path = cgroupDir +
"/" + file;
971 std::ofstream out(path);
979 return unexpected(std::make_error_code(std::errc::operation_not_permitted));
AutoRegisterCurrentThread(ThreadRegistry ®, std::string const &name=std::string(), std::string const &componentTag=std::string())
auto operator=(AutoRegisterCurrentThread const &) -> AutoRegisterCurrentThread &=delete
AutoRegisterCurrentThread(AutoRegisterCurrentThread &&other) noexcept
AutoRegisterCurrentThread(AutoRegisterCurrentThread const &)=delete
auto operator=(AutoRegisterCurrentThread &&other) noexcept -> AutoRegisterCurrentThread &
~AutoRegisterCurrentThread()
AutoRegisterCurrentThread(std::string const &name=std::string(), std::string const &componentTag=std::string())
Aggregates multiple ThreadRegistry instances into a single queryable view.
void attach(ThreadRegistry *reg)
auto query() const -> ThreadRegistry::QueryView
Manages a set of CPU indices to which a thread may be bound.
ThreadControlBlock(ThreadControlBlock &&)=delete
auto std_id() const noexcept -> std::thread::id
ThreadControlBlock(ThreadControlBlock const &)=delete
auto set_priority(ThreadPriority priority) const -> expected< void, std::error_code >
static auto create_for_current_thread() -> std::shared_ptr< ThreadControlBlock >
auto set_affinity(ThreadAffinity const &affinity) const -> expected< void, std::error_code >
auto operator=(ThreadControlBlock &&) -> ThreadControlBlock &=delete
auto operator=(ThreadControlBlock const &) -> ThreadControlBlock &=delete
auto set_scheduling_policy(SchedulingPolicy policy, ThreadPriority priority) const -> expected< void, std::error_code >
auto tid() const noexcept -> Tid
ThreadControlBlock()=default
auto set_name(std::string const &name) const -> expected< void, std::error_code >
static auto get_thread_id()
Value-semantic wrapper for a thread scheduling priority.
Lazy, functional-style query/filter view over a snapshot of registered threads.
void for_each(Fn &&fn) const
auto any(Predicate &&pred) const -> bool
auto all(Predicate &&pred) const -> bool
auto map(Fn &&fn) const -> std::vector< std::invoke_result_t< Fn, RegisteredThreadInfo const & > >
auto filter(Predicate &&pred) const -> QueryView
auto take(size_t n) const -> QueryView
auto skip(size_t n) const -> QueryView
auto count() const -> size_t
auto none(Predicate &&pred) const -> bool
QueryView(std::vector< RegisteredThreadInfo > entries)
auto find_if(Predicate &&pred) const -> std::optional< RegisteredThreadInfo >
auto entries() const -> std::vector< RegisteredThreadInfo > const &
auto empty() const -> bool
Central registry of threads indexed by OS-level thread ID (Tid).
void set_on_unregister(std::function< void(RegisteredThreadInfo const &)> cb)
ThreadRegistry(ThreadRegistry const &)=delete
void unregister_current_thread()
auto set_affinity(Tid tid, ThreadAffinity const &affinity) const -> expected< void, std::error_code >
void register_current_thread(std::shared_ptr< ThreadControlBlock > const &controlBlock, std::string name=std::string(), std::string componentTag=std::string())
auto set_scheduling_policy(Tid tid, SchedulingPolicy policy, ThreadPriority priority) const -> expected< void, std::error_code >
void register_current_thread(std::string name=std::string(), std::string componentTag=std::string())
auto query() const -> QueryView
void set_on_register(std::function< void(RegisteredThreadInfo const &)> cb)
auto set_priority(Tid tid, ThreadPriority priority) const -> expected< void, std::error_code >
auto set_name(Tid tid, std::string const &name) const -> expected< void, std::error_code >
auto get(Tid tid) const -> std::optional< RegisteredThreadInfo >
auto operator=(ThreadRegistry const &) -> ThreadRegistry &=delete
CRTP mixin that provides functional-style query facade methods.
void apply(Predicate &&pred, Fn &&fn) const
auto any(Predicate &&pred) const -> bool
auto map(Fn &&fn) const -> std::vector< std::invoke_result_t< Fn, RegisteredThreadInfo const & > >
auto take(size_t n) const
auto all(Predicate &&pred) const -> bool
auto filter(Predicate &&pred) const
auto find_if(Predicate &&pred) const -> std::optional< RegisteredThreadInfo >
void for_each(Fn &&fn) const
auto count() const -> size_t
auto none(Predicate &&pred) const -> bool
auto empty() const -> bool
auto skip(size_t n) const
A result type that holds either a value of type T or an error of type E.
Exception thrown by expected::value() when the object is in the error state.
Polyfill for std::expected (C++23) for pre-C++23 compilers.
auto apply_affinity(pthread_t handle, ThreadAffinity const &affinity) -> expected< void, std::error_code >
auto apply_priority(pthread_t handle, ThreadPriority priority) -> expected< void, std::error_code >
auto apply_scheduling_policy(pthread_t handle, SchedulingPolicy policy, ThreadPriority priority) -> expected< void, std::error_code >
auto apply_name(pthread_t handle, std::string const &name) -> expected< void, std::error_code >
SchedulingPolicy
Enumeration of available thread scheduling policies.
auto cgroup_attach_tid(std::string const &cgroupDir, Tid tid) -> expected< void, std::error_code >
Attaches a thread to a Linux cgroup by writing its TID to the appropriate control file.
BuildMode
Indicates whether the library was compiled in header-only or runtime (shared library) mode.
@ HEADER_ONLY
All symbols are inline / header-only.
@ RUNTIME
Core symbols are compiled into a shared library.
constexpr bool is_runtime_build
true when compiled with THREADSCHEDULE_RUNTIME.
auto registry() -> ThreadRegistry &
Returns a reference to the process-wide ThreadRegistry.
auto build_mode_string() -> char const *
Returns a human-readable C string describing the active build mode.
void set_external_registry(ThreadRegistry *reg)
Injects a custom ThreadRegistry as the global singleton.
auto build_mode() -> BuildMode
Returns the build mode detected at compile time (header-only variant).
Scheduling policies, thread priority, and CPU affinity types.
Snapshot of metadata for a single registered thread.
std::shared_ptr< class ThreadControlBlock > control
#define THREADSCHEDULE_API
Enhanced thread wrappers: ThreadWrapper, JThreadWrapper, and non-owning views.