ThreadSchedule 2.0.0
Modern C++ thread management library
Loading...
Searching...
No Matches
thread_registry.hpp
Go to the documentation of this file.
1#pragma once
2
7
8#include "expected.hpp"
10#include "thread_wrapper.hpp" // for ThreadInfo, ThreadAffinity
11#include <functional>
12#include <memory>
13#include <mutex>
14#include <optional>
15#include <shared_mutex>
16#include <string>
17#include <thread>
18#include <tuple>
19#include <unordered_map>
20#include <utility>
21#include <vector>
22
23#ifdef _WIN32
24#include <windows.h>
25#else
26#include <pthread.h>
27#include <sched.h>
28#include <sys/types.h>
29#endif
30
31namespace threadschedule
32{
33
34// Optional export macro for building a runtime (shared/dll) variant
35#if defined(_WIN32) || defined(_WIN64)
36#if defined(THREADSCHEDULE_EXPORTS)
37#define THREADSCHEDULE_API __declspec(dllexport)
38#else
39#define THREADSCHEDULE_API __declspec(dllimport)
40#endif
41#else
42#define THREADSCHEDULE_API
43#endif
44
45#ifdef _WIN32
46using Tid = unsigned long; // DWORD thread id
47#else
48using Tid = pid_t; // Linux TID via gettid()
49#endif
50
87{
89 std::thread::id stdId;
90 std::string name;
91 std::string componentTag;
92 bool alive{true};
93 std::shared_ptr<class ThreadControlBlock> control;
94};
95
135{
136 public:
142
144 {
145#ifdef _WIN32
146 if (handle_)
147 {
148 CloseHandle(handle_);
149 handle_ = nullptr;
150 }
151#endif
152 }
153
154 [[nodiscard]] auto tid() const noexcept -> Tid
155 {
156 return tid_;
157 }
158 [[nodiscard]] auto std_id() const noexcept -> std::thread::id
159 {
160 return stdId_;
161 }
162 private:
163 [[nodiscard]] auto native_handle() const
164 {
165#ifdef _WIN32
166 return handle_;
167#else
168 return pthreadHandle_;
169#endif
170 }
171
172 public:
173 [[nodiscard]] auto set_affinity(ThreadAffinity const& affinity) const -> expected<void, std::error_code>
174 {
175 return detail::apply_affinity(native_handle(), affinity);
176 }
177
178 [[nodiscard]] auto set_priority(ThreadPriority priority) const -> expected<void, std::error_code>
179 {
180 return detail::apply_priority(native_handle(), priority);
181 }
182
183 [[nodiscard]] auto set_scheduling_policy(SchedulingPolicy policy, ThreadPriority priority) const
185 {
186 return detail::apply_scheduling_policy(native_handle(), policy, priority);
187 }
188
189 [[nodiscard]] auto set_name(std::string const& name) const -> expected<void, std::error_code>
190 {
191 return detail::apply_name(native_handle(), name);
192 }
193
194 static auto create_for_current_thread() -> std::shared_ptr<ThreadControlBlock>
195 {
196 auto block = std::make_shared<ThreadControlBlock>();
197 block->tid_ = ThreadInfo::get_thread_id();
198 block->stdId_ = std::this_thread::get_id();
199#ifdef _WIN32
200 HANDLE realHandle = nullptr;
201 DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &realHandle,
202 THREAD_SET_INFORMATION | THREAD_QUERY_INFORMATION, FALSE, 0);
203 block->handle_ = realHandle;
204#else
205 block->pthreadHandle_ = pthread_self();
206#endif
207 return block;
208 }
209
210 private:
211 Tid tid_{};
212 std::thread::id stdId_;
213#ifdef _WIN32
214 HANDLE handle_ = nullptr;
215#else
216 pthread_t pthreadHandle_{};
217#endif
218};
219
220namespace detail
221{
222
235template <typename Derived>
237{
238 auto self() const -> Derived const& { return static_cast<Derived const&>(*this); }
239
240 public:
241 template <typename Predicate>
242 [[nodiscard]] auto filter(Predicate&& pred) const
243 {
244 return self().query().filter(std::forward<Predicate>(pred));
245 }
246
247 [[nodiscard]] auto count() const -> size_t { return self().query().count(); }
248
249 [[nodiscard]] auto empty() const -> bool { return self().query().empty(); }
250
251 template <typename Fn>
252 void for_each(Fn&& fn) const
253 {
254 self().query().for_each(std::forward<Fn>(fn));
255 }
256
257 template <typename Predicate, typename Fn>
258 void apply(Predicate&& pred, Fn&& fn) const
259 {
260 self().query().filter(std::forward<Predicate>(pred)).for_each(std::forward<Fn>(fn));
261 }
262
263 template <typename Fn>
264 [[nodiscard]] auto map(Fn&& fn) const -> std::vector<std::invoke_result_t<Fn, RegisteredThreadInfo const&>>
265 {
266 return self().query().map(std::forward<Fn>(fn));
267 }
268
269 template <typename Predicate>
270 [[nodiscard]] auto find_if(Predicate&& pred) const -> std::optional<RegisteredThreadInfo>
271 {
272 return self().query().find_if(std::forward<Predicate>(pred));
273 }
274
275 template <typename Predicate>
276 [[nodiscard]] auto any(Predicate&& pred) const -> bool
277 {
278 return self().query().any(std::forward<Predicate>(pred));
279 }
280
281 template <typename Predicate>
282 [[nodiscard]] auto all(Predicate&& pred) const -> bool
283 {
284 return self().query().all(std::forward<Predicate>(pred));
285 }
286
287 template <typename Predicate>
288 [[nodiscard]] auto none(Predicate&& pred) const -> bool
289 {
290 return self().query().none(std::forward<Predicate>(pred));
291 }
292
293 [[nodiscard]] auto take(size_t n) const { return self().query().take(n); }
294
295 [[nodiscard]] auto skip(size_t n) const { return self().query().skip(n); }
296};
297
298} // namespace detail
299
344class ThreadRegistry : public detail::QueryFacadeMixin<ThreadRegistry>
345{
346 public:
347 ThreadRegistry() = default;
349 auto operator=(ThreadRegistry const&) -> ThreadRegistry& = delete;
350
351 void register_current_thread(std::string name = std::string(), std::string componentTag = std::string())
352 {
355 info.stdId = std::this_thread::get_id();
356 info.name = std::move(name);
357 info.componentTag = std::move(componentTag);
358 info.alive = true;
359 try_register(std::move(info));
360 }
361
362 void register_current_thread(std::shared_ptr<ThreadControlBlock> const& controlBlock,
363 std::string name = std::string(), std::string componentTag = std::string())
364 {
365 if (!controlBlock)
366 return;
368 info.tid = controlBlock->tid();
369 info.stdId = controlBlock->std_id();
370 info.name = std::move(name);
371 info.componentTag = std::move(componentTag);
372 info.alive = true;
373 info.control = controlBlock;
374 try_register(std::move(info));
375 }
376
378 {
379 Tid const tid = ThreadInfo::get_thread_id();
380 std::unique_lock<std::shared_mutex> lock(mutex_);
381 auto it = threads_.find(tid);
382 if (it != threads_.end())
383 {
384 it->second.alive = false;
385 auto info = it->second;
386 threads_.erase(it);
387 if (onUnregister_)
388 {
389 auto cb = onUnregister_;
390 lock.unlock();
391 cb(info);
392 }
393 }
394 }
395
396 // Lookup
397 [[nodiscard]] auto get(Tid tid) const -> std::optional<RegisteredThreadInfo>
398 {
399 std::shared_lock<std::shared_mutex> lock(mutex_);
400 auto it = threads_.find(tid);
401 if (it == threads_.end())
402 return std::nullopt;
403 return it->second;
404 }
405
443 {
444 public:
445 explicit QueryView(std::vector<RegisteredThreadInfo> entries) : entries_(std::move(entries))
446 {
447 }
448
449 template <typename Predicate>
450 auto filter(Predicate&& pred) const -> QueryView
451 {
452 std::vector<RegisteredThreadInfo> filtered;
453 filtered.reserve(entries_.size());
454 for (auto const& entry : entries_)
455 {
456 if (pred(entry))
457 filtered.push_back(entry);
458 }
459 return QueryView(std::move(filtered));
460 }
461
462 template <typename Fn>
463 void for_each(Fn&& fn) const
464 {
465 for (auto const& entry : entries_)
466 {
467 fn(entry);
468 }
469 }
470
471 [[nodiscard]] auto count() const -> size_t
472 {
473 return entries_.size();
474 }
475
476 [[nodiscard]] auto empty() const -> bool
477 {
478 return entries_.empty();
479 }
480
481 [[nodiscard]] auto entries() const -> std::vector<RegisteredThreadInfo> const&
482 {
483 return entries_;
484 }
485
486 // Transform entries to a vector of another type
487 template <typename Fn>
488 [[nodiscard]] auto map(Fn&& fn) const -> std::vector<std::invoke_result_t<Fn, RegisteredThreadInfo const&>>
489 {
490 std::vector<std::invoke_result_t<Fn, RegisteredThreadInfo const&>> result;
491 result.reserve(entries_.size());
492 for (auto const& entry : entries_)
493 {
494 result.push_back(fn(entry));
495 }
496 return result;
497 }
498
499 // Find first entry matching predicate
500 template <typename Predicate>
501 [[nodiscard]] auto find_if(Predicate&& pred) const -> std::optional<RegisteredThreadInfo>
502 {
503 for (auto const& entry : entries_)
504 {
505 if (pred(entry))
506 return entry;
507 }
508 return std::nullopt;
509 }
510
511 template <typename Predicate>
512 [[nodiscard]] auto any(Predicate&& pred) const -> bool
513 {
514 for (auto const& entry : entries_)
515 {
516 if (pred(entry))
517 return true;
518 }
519 return false;
520 }
521
522 template <typename Predicate>
523 [[nodiscard]] auto all(Predicate&& pred) const -> bool
524 {
525 for (auto const& entry : entries_)
526 {
527 if (!pred(entry))
528 return false;
529 }
530 return true;
531 }
532
533 template <typename Predicate>
534 [[nodiscard]] auto none(Predicate&& pred) const -> bool
535 {
536 return !any(std::forward<Predicate>(pred));
537 }
538
539 [[nodiscard]] auto take(size_t n) const -> QueryView
540 {
541 auto result = entries_;
542 if (result.size() > n)
543 result.resize(n);
544 return QueryView(std::move(result));
545 }
546
547 [[nodiscard]] auto skip(size_t n) const -> QueryView
548 {
549 std::vector<RegisteredThreadInfo> result;
550 if (n < entries_.size())
551 {
552 result.assign(entries_.begin() + n, entries_.end());
553 }
554 return QueryView(std::move(result));
555 }
556
557 private:
558 std::vector<RegisteredThreadInfo> entries_;
559 };
560
561 // Create a query view over all registered threads
562 [[nodiscard]] auto query() const -> QueryView
563 {
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_)
568 {
569 snapshot.push_back(kv.second);
570 }
571 return QueryView(std::move(snapshot));
572 }
573
574 [[nodiscard]] auto set_affinity(Tid tid, ThreadAffinity const& affinity) const -> expected<void, std::error_code>
575 {
576 auto blk = lock_block(tid);
577 if (!blk)
578 return unexpected(std::make_error_code(std::errc::no_such_process));
579 return blk->set_affinity(affinity);
580 }
581
582 [[nodiscard]] auto set_priority(Tid tid, ThreadPriority priority) const -> expected<void, std::error_code>
583 {
584 auto blk = lock_block(tid);
585 if (!blk)
586 return unexpected(std::make_error_code(std::errc::no_such_process));
587 return blk->set_priority(priority);
588 }
589
590 [[nodiscard]] auto set_scheduling_policy(Tid tid, SchedulingPolicy policy, ThreadPriority priority) const
592 {
593 auto blk = lock_block(tid);
594 if (!blk)
595 return unexpected(std::make_error_code(std::errc::no_such_process));
596 return blk->set_scheduling_policy(policy, priority);
597 }
598
599 [[nodiscard]] auto set_name(Tid tid, std::string const& name) const -> expected<void, std::error_code>
600 {
601 auto blk = lock_block(tid);
602 if (!blk)
603 return unexpected(std::make_error_code(std::errc::no_such_process));
604 return blk->set_name(name);
605 }
606
607 // Register/unregister hooks (system integration)
608 void set_on_register(std::function<void(RegisteredThreadInfo const&)> cb)
609 {
610 std::unique_lock<std::shared_mutex> lock(mutex_);
611 onRegister_ = std::move(cb);
612 }
613
614 void set_on_unregister(std::function<void(RegisteredThreadInfo const&)> cb)
615 {
616 std::unique_lock<std::shared_mutex> lock(mutex_);
617 onUnregister_ = std::move(cb);
618 }
619
620 private:
621 void try_register(RegisteredThreadInfo info)
622 {
623 std::unique_lock<std::shared_mutex> lock(mutex_);
624 auto it = threads_.find(info.tid);
625 if (it != threads_.end())
626 return;
627 auto stored = info;
628 threads_.emplace(info.tid, std::move(info));
629 if (onRegister_)
630 {
631 auto cb = onRegister_;
632 lock.unlock();
633 cb(stored);
634 }
635 }
636
637 [[nodiscard]] auto lock_block(Tid tid) const -> std::shared_ptr<ThreadControlBlock>
638 {
639 std::shared_lock<std::shared_mutex> lock(mutex_);
640 auto it = threads_.find(tid);
641 if (it == threads_.end())
642 return nullptr;
643 return it->second.control;
644 }
645 mutable std::shared_mutex mutex_;
646 std::unordered_map<Tid, RegisteredThreadInfo> threads_;
647
648 std::function<void(RegisteredThreadInfo const&)> onRegister_;
649 std::function<void(RegisteredThreadInfo const&)> onUnregister_;
650};
651
672
673#if defined(THREADSCHEDULE_RUNTIME)
676#else
678inline auto registry_storage() -> ThreadRegistry*&
679{
680 static ThreadRegistry* external = nullptr;
681 return external;
682}
684
694inline auto registry() -> ThreadRegistry&
695{
696 ThreadRegistry*& ext = registry_storage();
697 if (ext != nullptr)
698 return *ext;
699 static ThreadRegistry local;
700 return local;
701}
702
720{
721 registry_storage() = reg;
722}
723
724#endif
725
735enum class BuildMode : std::uint8_t
736{
739};
740
741#if defined(THREADSCHEDULE_RUNTIME)
742inline constexpr bool is_runtime_build = true;
743
749#else
750inline constexpr bool is_runtime_build = false;
751
756inline auto build_mode() -> BuildMode
757{
759}
760#endif
761
766inline auto build_mode_string() -> char const*
767{
768 return is_runtime_build ? "runtime" : "header-only";
769}
770
802class CompositeThreadRegistry : public detail::QueryFacadeMixin<CompositeThreadRegistry>
803{
804 public:
806 {
807 if (reg == nullptr)
808 return;
809 std::lock_guard<std::mutex> lock(mutex_);
810 registries_.push_back(reg);
811 }
812
813 [[nodiscard]] auto query() const -> ThreadRegistry::QueryView
814 {
815 std::vector<RegisteredThreadInfo> merged;
816 std::vector<ThreadRegistry*> regs;
817 {
818 std::lock_guard<std::mutex> lock(mutex_);
819 regs = registries_;
820 }
821 for (auto* r : regs)
822 {
823 auto view = r->query();
824 auto const& entries = view.entries();
825 merged.insert(merged.end(), entries.begin(), entries.end());
826 }
827 return ThreadRegistry::QueryView(std::move(merged));
828 }
829
830 private:
831 mutable std::mutex mutex_;
832 std::vector<ThreadRegistry*> registries_;
833};
834
876{
877 public:
878 explicit AutoRegisterCurrentThread(std::string const& name = std::string(),
879 std::string const& componentTag = std::string())
880 : active_(true), externalReg_(nullptr)
881 {
883 (void)block->set_name(name);
884 registry().register_current_thread(block, name, componentTag);
885 }
886
887 explicit AutoRegisterCurrentThread(ThreadRegistry& reg, std::string const& name = std::string(),
888 std::string const& componentTag = std::string())
889 : active_(true), externalReg_(&reg)
890 {
892 (void)block->set_name(name);
893 externalReg_->register_current_thread(block, name, componentTag);
894 }
896 {
897 if (active_)
898 {
899 if (externalReg_ != nullptr)
900 externalReg_->unregister_current_thread();
901 else
902 registry().unregister_current_thread();
903 }
904 }
908 : active_(other.active_), externalReg_(other.externalReg_)
909 {
910 other.active_ = false;
911 other.externalReg_ = nullptr;
912 }
914 {
915 if (this != &other)
916 {
917 if (active_)
918 {
919 if (externalReg_ != nullptr)
920 externalReg_->unregister_current_thread();
921 else
922 registry().unregister_current_thread();
923 }
924 active_ = other.active_;
925 externalReg_ = other.externalReg_;
926 other.active_ = false;
927 other.externalReg_ = nullptr;
928 }
929 return *this;
930 }
931
932 private:
933 bool active_;
934 ThreadRegistry* externalReg_;
935};
936
937} // namespace threadschedule
938
939#ifndef _WIN32
940namespace threadschedule
941{
965inline auto cgroup_attach_tid(std::string const& cgroupDir, Tid tid) -> expected<void, std::error_code>
966{
967 std::vector<std::string> candidates = {"cgroup.threads", "tasks", "cgroup.procs"};
968 for (auto const& file : candidates)
969 {
970 std::string path = cgroupDir + "/" + file;
971 std::ofstream out(path);
972 if (!out)
973 continue;
974 out << tid;
975 out.flush();
976 if (out)
977 return {};
978 }
979 return unexpected(std::make_error_code(std::errc::operation_not_permitted));
980}
981} // namespace threadschedule
982#endif
AutoRegisterCurrentThread(ThreadRegistry &reg, 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(std::string const &name=std::string(), std::string const &componentTag=std::string())
Aggregates multiple ThreadRegistry instances into a single queryable view.
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
auto set_name(std::string const &name) const -> expected< void, std::error_code >
Value-semantic wrapper for a thread scheduling priority.
Lazy, functional-style query/filter view over a snapshot of registered threads.
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 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 &
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
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 all(Predicate &&pred) const -> bool
auto find_if(Predicate &&pred) const -> std::optional< RegisteredThreadInfo >
auto none(Predicate &&pred) const -> bool
A result type that holds either a value of type T or an error of type E.
Definition expected.hpp:215
Exception thrown by expected::value() when the object is in the error state.
Definition expected.hpp:162
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.