10#if defined(THREADSCHEDULE_HAS_REFLECTION) && THREADSCHEDULE_HAS_REFLECTION
19#include <shared_mutex>
23#include <unordered_map>
39#if defined(_WIN32) || defined(_WIN64)
40#if defined(THREADSCHEDULE_EXPORTS)
41#define THREADSCHEDULE_API __declspec(dllexport)
43#define THREADSCHEDULE_API __declspec(dllimport)
46#define THREADSCHEDULE_API
91 std::shared_ptr<class ThreadControlBlock>
control;
94#if defined(THREADSCHEDULE_HAS_REFLECTION) && THREADSCHEDULE_HAS_REFLECTION
95namespace registered_thread_fields
97consteval auto tid() -> reflect::info {
return reflect::field_info<RegisteredThreadInfo, 0>(); }
98consteval auto stdId() -> reflect::info {
return reflect::field_info<RegisteredThreadInfo, 1>(); }
99consteval auto name() -> reflect::info {
return reflect::field_info<RegisteredThreadInfo, 2>(); }
100consteval auto componentTag() -> reflect::info {
return reflect::field_info<RegisteredThreadInfo, 3>(); }
101consteval auto alive() -> reflect::info {
return reflect::field_info<RegisteredThreadInfo, 4>(); }
102consteval auto control() -> reflect::info {
return reflect::field_info<RegisteredThreadInfo, 5>(); }
160 CloseHandle(handle_);
166 [[nodiscard]]
auto tid() const noexcept ->
Tid
170 [[nodiscard]]
auto std_id() const noexcept -> std::thread::
id
175 [[nodiscard]]
auto native_handle()
const
180 return pthreadHandle_;
208 auto block = std::make_shared<ThreadControlBlock>();
210 block->stdId_ = std::this_thread::get_id();
212 HANDLE realHandle =
nullptr;
213 DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &realHandle,
214 THREAD_SET_INFORMATION | THREAD_QUERY_INFORMATION, FALSE, 0);
215 block->handle_ = realHandle;
217 block->pthreadHandle_ = pthread_self();
224 std::thread::id stdId_;
226 HANDLE handle_ =
nullptr;
228 pthread_t pthreadHandle_{};
235#if defined(THREADSCHEDULE_HAS_REFLECTION) && THREADSCHEDULE_HAS_REFLECTION
236template <reflect::info Field,
typename Owner>
237consteval void validate_reflected_field()
239 reflect::require_field_owner<Field, Owner>();
255template <
typename Derived>
258 auto self()
const -> Derived
const& {
return static_cast<Derived const&
>(*this); }
261 template <
typename Predicate>
262 [[nodiscard]]
auto filter(Predicate&& pred)
const
264 return self().query().filter(std::forward<Predicate>(pred));
267 [[nodiscard]]
auto count() const ->
size_t {
return self().query().count(); }
269 [[nodiscard]]
auto empty() const ->
bool {
return self().query().empty(); }
271 template <
typename Fn>
274 self().query().for_each(std::forward<Fn>(fn));
277 template <
typename Predicate,
typename Fn>
278 void apply(Predicate&& pred, Fn&& fn)
const
280 self().query().filter(std::forward<Predicate>(pred)).for_each(std::forward<Fn>(fn));
283 template <
typename Fn>
284 [[nodiscard]]
auto map(Fn&& fn)
const -> std::vector<std::invoke_result_t<Fn, RegisteredThreadInfo const&>>
286 return self().query().map(std::forward<Fn>(fn));
289 template <
typename Predicate>
290 [[nodiscard]]
auto find_if(Predicate&& pred)
const -> std::optional<RegisteredThreadInfo>
292 return self().query().find_if(std::forward<Predicate>(pred));
295 template <
typename Predicate>
296 [[nodiscard]]
auto any(Predicate&& pred)
const ->
bool
298 return self().query().any(std::forward<Predicate>(pred));
301 template <
typename Predicate>
302 [[nodiscard]]
auto all(Predicate&& pred)
const ->
bool
304 return self().query().all(std::forward<Predicate>(pred));
307 template <
typename Predicate>
308 [[nodiscard]]
auto none(Predicate&& pred)
const ->
bool
310 return self().query().none(std::forward<Predicate>(pred));
313 [[nodiscard]]
auto take(
size_t n)
const {
return self().query().take(n); }
315 [[nodiscard]]
auto skip(
size_t n)
const {
return self().query().skip(n); }
317#if defined(THREADSCHEDULE_HAS_REFLECTION) && THREADSCHEDULE_HAS_REFLECTION
318 template <reflect::info Field,
typename Value>
319 [[nodiscard]]
auto where(Value
const& value)
const
321 return self().query().template where<Field>(value);
324 template <reflect::info Field,
typename Predicate>
325 [[nodiscard]]
auto where_if(Predicate&& pred)
const
327 return self().query().template where_if<Field>(std::forward<Predicate>(pred));
330 template <reflect::info Field,
typename Value>
331 [[nodiscard]]
auto find_by(Value
const& value)
const
333 return self().query().template find_by<Field>(value);
336 template <reflect::info Field,
typename Value>
337 [[nodiscard]]
auto contains(Value
const& value)
const ->
bool
339 return self().query().template contains<Field>(value);
342 template <reflect::info... Fields>
343 [[nodiscard]]
auto project()
const
345 return self().query().template project<Fields...>();
407 info.
stdId = std::this_thread::get_id();
408 info.
name = std::move(name);
411 try_register(std::move(info));
415 std::string name = std::string(), std::string componentTag = std::string())
420 info.
tid = controlBlock->tid();
421 info.
stdId = controlBlock->std_id();
422 info.
name = std::move(name);
426 try_register(std::move(info));
432 std::unique_lock<std::shared_mutex> lock(mutex_);
433 auto it = threads_.find(tid);
434 if (it != threads_.end())
436 it->second.alive =
false;
437 auto info = it->second;
441 auto cb = onUnregister_;
449 [[nodiscard]]
auto get(
Tid tid)
const -> std::optional<RegisteredThreadInfo>
451 std::shared_lock<std::shared_mutex> lock(mutex_);
452 auto it = threads_.find(tid);
453 if (it == threads_.end())
501 template <
typename Predicate>
504 std::vector<RegisteredThreadInfo> filtered;
505 filtered.reserve(entries_.size());
506 for (
auto const& entry : entries_)
509 filtered.push_back(entry);
514 template <
typename Fn>
517 for (
auto const& entry : entries_)
523 [[nodiscard]]
auto count() const ->
size_t
525 return entries_.size();
528 [[nodiscard]]
auto empty() const ->
bool
530 return entries_.empty();
539 template <
typename Fn>
540 [[nodiscard]]
auto map(Fn&& fn)
const -> std::vector<std::invoke_result_t<Fn, RegisteredThreadInfo const&>>
542 std::vector<std::invoke_result_t<Fn, RegisteredThreadInfo const&>> result;
543 result.reserve(entries_.size());
544 for (
auto const& entry : entries_)
546 result.push_back(fn(entry));
552 template <
typename Predicate>
553 [[nodiscard]]
auto find_if(Predicate&& pred)
const -> std::optional<RegisteredThreadInfo>
555 for (
auto const& entry : entries_)
563 template <
typename Predicate>
564 [[nodiscard]]
auto any(Predicate&& pred)
const ->
bool
566 for (
auto const& entry : entries_)
574 template <
typename Predicate>
575 [[nodiscard]]
auto all(Predicate&& pred)
const ->
bool
577 for (
auto const& entry : entries_)
585 template <
typename Predicate>
586 [[nodiscard]]
auto none(Predicate&& pred)
const ->
bool
588 return !
any(std::forward<Predicate>(pred));
593 auto result = entries_;
594 if (result.size() > n)
601 std::vector<RegisteredThreadInfo> result;
602 if (n < entries_.size())
604 result.assign(entries_.begin() + n, entries_.end());
609#if defined(THREADSCHEDULE_HAS_REFLECTION) && THREADSCHEDULE_HAS_REFLECTION
610 template <reflect::info Field,
typename Value>
611 [[nodiscard]]
auto where(Value
const& value)
const ->
QueryView
613 detail::validate_reflected_field<Field, RegisteredThreadInfo>();
614 std::vector<RegisteredThreadInfo> filtered;
615 filtered.reserve(entries_.size());
616 for (
auto const& entry : entries_)
618 if (reflect::get<Field>(entry) == value)
619 filtered.push_back(entry);
624 template <reflect::info Field,
typename Predicate>
625 [[nodiscard]]
auto where_if(Predicate&& pred)
const ->
QueryView
627 detail::validate_reflected_field<Field, RegisteredThreadInfo>();
628 static_assert(std::is_invocable_r_v<bool, Predicate&, reflect::field_type_t<Field>
const&>,
629 "Reflection predicate must accept the selected field type");
630 std::vector<RegisteredThreadInfo> filtered;
631 filtered.reserve(entries_.size());
632 for (
auto const& entry : entries_)
634 if (pred(reflect::get<Field>(entry)))
635 filtered.push_back(entry);
640 template <reflect::info Field,
typename Value>
641 [[nodiscard]]
auto find_by(Value
const& value)
const -> std::optional<RegisteredThreadInfo>
643 detail::validate_reflected_field<Field, RegisteredThreadInfo>();
644 for (
auto const& entry : entries_)
646 if (reflect::get<Field>(entry) == value)
652 template <reflect::info Field,
typename Value>
653 [[nodiscard]]
auto contains(Value
const& value)
const ->
bool
655 return find_by<Field>(value).has_value();
658 template <reflect::info... Fields>
659 [[nodiscard]]
auto project() const -> std::vector<reflect::projection_t<Fields...>>
661 static_assert(
sizeof...(Fields) > 0,
"project requires at least one field");
662 (detail::validate_reflected_field<Fields, RegisteredThreadInfo>(), ...);
664 std::vector<reflect::projection_t<Fields...>> result;
665 result.reserve(entries_.size());
666 for (
auto const& entry : entries_)
668 result.push_back(reflect::project_value<Fields...>(entry));
675 std::vector<RegisteredThreadInfo> entries_;
681 std::vector<RegisteredThreadInfo> snapshot;
682 std::shared_lock<std::shared_mutex> lock(mutex_);
683 snapshot.reserve(threads_.size());
684 for (
auto const& kv : threads_)
686 snapshot.push_back(kv.second);
691#if defined(THREADSCHEDULE_HAS_REFLECTION) && THREADSCHEDULE_HAS_REFLECTION
692 template <reflect::info Field,
typename Value>
693 [[nodiscard]]
auto where(Value
const& value)
const -> QueryView
695 detail::validate_reflected_field<Field, RegisteredThreadInfo>();
696 std::vector<RegisteredThreadInfo> filtered;
697 std::shared_lock<std::shared_mutex> lock(mutex_);
698 filtered.reserve(threads_.size());
699 for (
auto const& [tid, entry] : threads_)
702 if (reflect::get<Field>(entry) == value)
703 filtered.push_back(entry);
705 return QueryView(std::move(filtered));
708 template <reflect::info Field,
typename Predicate>
709 [[nodiscard]]
auto where_if(Predicate&& pred)
const ->
QueryView
711 detail::validate_reflected_field<Field, RegisteredThreadInfo>();
712 static_assert(std::is_invocable_r_v<bool, Predicate&, reflect::field_type_t<Field>
const&>,
713 "Reflection predicate must accept the selected field type");
714 std::vector<RegisteredThreadInfo> filtered;
715 std::shared_lock<std::shared_mutex> lock(mutex_);
716 filtered.reserve(threads_.size());
717 for (
auto const& [tid, entry] : threads_)
720 if (pred(reflect::get<Field>(entry)))
721 filtered.push_back(entry);
726 template <reflect::info Field,
typename Value>
727 [[nodiscard]]
auto find_by(Value
const& value)
const -> std::optional<RegisteredThreadInfo>
729 detail::validate_reflected_field<Field, RegisteredThreadInfo>();
730 std::shared_lock<std::shared_mutex> lock(mutex_);
731 for (
auto const& [tid, entry] : threads_)
734 if (reflect::get<Field>(entry) == value)
740 template <reflect::info Field,
typename Value>
741 [[nodiscard]]
auto contains(Value
const& value)
const ->
bool
743 detail::validate_reflected_field<Field, RegisteredThreadInfo>();
744 std::shared_lock<std::shared_mutex> lock(mutex_);
745 for (
auto const& [tid, entry] : threads_)
748 if (reflect::get<Field>(entry) == value)
754 template <reflect::info... Fields>
755 [[nodiscard]]
auto project() const -> std::vector<reflect::projection_t<Fields...>>
757 static_assert(
sizeof...(Fields) > 0,
"project requires at least one field");
758 (detail::validate_reflected_field<Fields, RegisteredThreadInfo>(), ...);
760 std::vector<reflect::projection_t<Fields...>> result;
761 std::shared_lock<std::shared_mutex> lock(mutex_);
762 result.reserve(threads_.size());
763 for (
auto const& [tid, entry] : threads_)
766 result.push_back(reflect::project_value<Fields...>(entry));
774 auto blk = lock_block(tid);
776 return unexpected(std::make_error_code(std::errc::no_such_process));
777 return blk->set_affinity(affinity);
782 auto blk = lock_block(tid);
784 return unexpected(std::make_error_code(std::errc::no_such_process));
785 return blk->set_priority(priority);
791 auto blk = lock_block(tid);
793 return unexpected(std::make_error_code(std::errc::no_such_process));
794 return blk->set_scheduling_policy(policy, priority);
799 auto blk = lock_block(tid);
801 return unexpected(std::make_error_code(std::errc::no_such_process));
802 return blk->set_name(name);
808 std::unique_lock<std::shared_mutex> lock(mutex_);
809 onRegister_ = std::move(cb);
812 template <
typename Callback,
813 std::enable_if_t<!std::is_same_v<detail::remove_cvref_t<Callback>,
RegistryCallback>,
int> = 0>
816 static_assert(std::is_invocable_r_v<void, Callback&, RegisteredThreadInfo const&>,
817 "Register callback must be invocable with RegisteredThreadInfo const&");
818 std::unique_lock<std::shared_mutex> lock(mutex_);
824 std::unique_lock<std::shared_mutex> lock(mutex_);
825 onUnregister_ = std::move(cb);
828 template <
typename Callback,
829 std::enable_if_t<!std::is_same_v<detail::remove_cvref_t<Callback>,
RegistryCallback>,
int> = 0>
832 static_assert(std::is_invocable_r_v<void, Callback&, RegisteredThreadInfo const&>,
833 "Unregister callback must be invocable with RegisteredThreadInfo const&");
834 std::unique_lock<std::shared_mutex> lock(mutex_);
841 std::unique_lock<std::shared_mutex> lock(mutex_);
842 auto it = threads_.find(info.
tid);
843 if (it != threads_.end())
846 threads_.emplace(info.
tid, std::move(info));
849 auto cb = onRegister_;
855 [[nodiscard]]
auto lock_block(
Tid tid)
const -> std::shared_ptr<ThreadControlBlock>
857 std::shared_lock<std::shared_mutex> lock(mutex_);
858 auto it = threads_.find(tid);
859 if (it == threads_.end())
861 return it->second.control;
863 mutable std::shared_mutex mutex_;
864 std::unordered_map<Tid, RegisteredThreadInfo> threads_;
891#if defined(THREADSCHEDULE_RUNTIME)
939 registry_storage() = reg;
959#if defined(THREADSCHEDULE_RUNTIME)
1027 std::lock_guard<std::mutex> lock(mutex_);
1028 registries_.push_back(reg);
1033 std::vector<RegisteredThreadInfo> merged;
1034 std::vector<ThreadRegistry*> regs;
1036 std::lock_guard<std::mutex> lock(mutex_);
1039 for (
auto* r : regs)
1041 auto view = r->query();
1042 auto const& entries = view.entries();
1043 merged.insert(merged.end(), entries.begin(), entries.end());
1049 mutable std::mutex mutex_;
1050 std::vector<ThreadRegistry*> registries_;
1097 std::string
const& componentTag = std::string())
1098 : active_(true), externalReg_(nullptr)
1101 (void)block->set_name(name);
1102 registry().register_current_thread(block, name, componentTag);
1106 std::string
const& componentTag = std::string())
1107 : active_(true), externalReg_(®)
1110 (void)block->set_name(name);
1111 externalReg_->register_current_thread(block, name, componentTag);
1117 if (externalReg_ !=
nullptr)
1118 externalReg_->unregister_current_thread();
1120 registry().unregister_current_thread();
1126 : active_(other.active_), externalReg_(other.externalReg_)
1128 other.active_ =
false;
1129 other.externalReg_ =
nullptr;
1137 if (externalReg_ !=
nullptr)
1138 externalReg_->unregister_current_thread();
1140 registry().unregister_current_thread();
1142 active_ = other.active_;
1143 externalReg_ = other.externalReg_;
1144 other.active_ =
false;
1145 other.externalReg_ =
nullptr;
1185 std::vector<std::string> candidates = {
"cgroup.threads",
"tasks",
"cgroup.procs"};
1186 for (
auto const& file : candidates)
1188 std::string path = cgroupDir +
"/" + file;
1189 std::ofstream out(path);
1197 return unexpected(std::make_error_code(std::errc::operation_not_permitted));
Feature-gated callable storage aliases for modern C++ builds.
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() -> Tid
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).
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 set_on_unregister(RegistryCallback cb)
void set_on_register(Callback &&cb)
void register_current_thread(std::string name=std::string(), std::string componentTag=std::string())
auto query() const -> QueryView
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 >
void set_on_unregister(Callback &&cb)
auto operator=(ThreadRegistry const &) -> ThreadRegistry &=delete
void set_on_register(RegistryCallback cb)
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.
std::function< Signature > copyable_callable
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 make_copyable_callable(Callable &&callable) -> copyable_callable< Signature >
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.
detail::copyable_callable< void(RegisteredThreadInfo const &)> RegistryCallback
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.