ThreadSchedule 2.2.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 "callable.hpp"
9#include "expected.hpp"
10#if defined(THREADSCHEDULE_HAS_REFLECTION) && THREADSCHEDULE_HAS_REFLECTION
11#include "reflection.hpp"
12#endif
13#include "scheduler_policy.hpp"
14#include "thread_wrapper.hpp" // for ThreadInfo, ThreadAffinity
15#include <functional>
16#include <memory>
17#include <mutex>
18#include <optional>
19#include <shared_mutex>
20#include <string>
21#include <thread>
22#include <tuple>
23#include <unordered_map>
24#include <utility>
25#include <vector>
26
27#ifdef _WIN32
28#include <windows.h>
29#else
30#include <pthread.h>
31#include <sched.h>
32#include <sys/types.h>
33#endif
34
35namespace threadschedule
36{
37
38// Optional export macro for building a runtime (shared/dll) variant
39#if defined(_WIN32) || defined(_WIN64)
40#if defined(THREADSCHEDULE_EXPORTS)
41#define THREADSCHEDULE_API __declspec(dllexport)
42#else
43#define THREADSCHEDULE_API __declspec(dllimport)
44#endif
45#else
46#define THREADSCHEDULE_API
47#endif
48
85{
87 std::thread::id stdId;
88 std::string name;
89 std::string componentTag;
90 bool alive{true};
91 std::shared_ptr<class ThreadControlBlock> control;
92};
93
94#if defined(THREADSCHEDULE_HAS_REFLECTION) && THREADSCHEDULE_HAS_REFLECTION
95namespace registered_thread_fields
96{
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>(); }
103} // namespace registered_thread_fields
104#endif
105
107
147{
148 public:
154
156 {
157#ifdef _WIN32
158 if (handle_)
159 {
160 CloseHandle(handle_);
161 handle_ = nullptr;
162 }
163#endif
164 }
165
166 [[nodiscard]] auto tid() const noexcept -> Tid
167 {
168 return tid_;
169 }
170 [[nodiscard]] auto std_id() const noexcept -> std::thread::id
171 {
172 return stdId_;
173 }
174 private:
175 [[nodiscard]] auto native_handle() const
176 {
177#ifdef _WIN32
178 return handle_;
179#else
180 return pthreadHandle_;
181#endif
182 }
183
184 public:
185 [[nodiscard]] auto set_affinity(ThreadAffinity const& affinity) const -> expected<void, std::error_code>
186 {
187 return detail::apply_affinity(native_handle(), affinity);
188 }
189
190 [[nodiscard]] auto set_priority(ThreadPriority priority) const -> expected<void, std::error_code>
191 {
192 return detail::apply_priority(native_handle(), priority);
193 }
194
195 [[nodiscard]] auto set_scheduling_policy(SchedulingPolicy policy, ThreadPriority priority) const
197 {
198 return detail::apply_scheduling_policy(native_handle(), policy, priority);
199 }
200
201 [[nodiscard]] auto set_name(std::string const& name) const -> expected<void, std::error_code>
202 {
203 return detail::apply_name(native_handle(), name);
204 }
205
206 static auto create_for_current_thread() -> std::shared_ptr<ThreadControlBlock>
207 {
208 auto block = std::make_shared<ThreadControlBlock>();
209 block->tid_ = ThreadInfo::get_thread_id();
210 block->stdId_ = std::this_thread::get_id();
211#ifdef _WIN32
212 HANDLE realHandle = nullptr;
213 DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &realHandle,
214 THREAD_SET_INFORMATION | THREAD_QUERY_INFORMATION, FALSE, 0);
215 block->handle_ = realHandle;
216#else
217 block->pthreadHandle_ = pthread_self();
218#endif
219 return block;
220 }
221
222 private:
223 Tid tid_{};
224 std::thread::id stdId_;
225#ifdef _WIN32
226 HANDLE handle_ = nullptr;
227#else
228 pthread_t pthreadHandle_{};
229#endif
230};
231
232namespace detail
233{
234
235#if defined(THREADSCHEDULE_HAS_REFLECTION) && THREADSCHEDULE_HAS_REFLECTION
236template <reflect::info Field, typename Owner>
237consteval void validate_reflected_field()
238{
239 reflect::require_field_owner<Field, Owner>();
240}
241#endif
242
255template <typename Derived>
257{
258 auto self() const -> Derived const& { return static_cast<Derived const&>(*this); }
259
260 public:
261 template <typename Predicate>
262 [[nodiscard]] auto filter(Predicate&& pred) const
263 {
264 return self().query().filter(std::forward<Predicate>(pred));
265 }
266
267 [[nodiscard]] auto count() const -> size_t { return self().query().count(); }
268
269 [[nodiscard]] auto empty() const -> bool { return self().query().empty(); }
270
271 template <typename Fn>
272 void for_each(Fn&& fn) const
273 {
274 self().query().for_each(std::forward<Fn>(fn));
275 }
276
277 template <typename Predicate, typename Fn>
278 void apply(Predicate&& pred, Fn&& fn) const
279 {
280 self().query().filter(std::forward<Predicate>(pred)).for_each(std::forward<Fn>(fn));
281 }
282
283 template <typename Fn>
284 [[nodiscard]] auto map(Fn&& fn) const -> std::vector<std::invoke_result_t<Fn, RegisteredThreadInfo const&>>
285 {
286 return self().query().map(std::forward<Fn>(fn));
287 }
288
289 template <typename Predicate>
290 [[nodiscard]] auto find_if(Predicate&& pred) const -> std::optional<RegisteredThreadInfo>
291 {
292 return self().query().find_if(std::forward<Predicate>(pred));
293 }
294
295 template <typename Predicate>
296 [[nodiscard]] auto any(Predicate&& pred) const -> bool
297 {
298 return self().query().any(std::forward<Predicate>(pred));
299 }
300
301 template <typename Predicate>
302 [[nodiscard]] auto all(Predicate&& pred) const -> bool
303 {
304 return self().query().all(std::forward<Predicate>(pred));
305 }
306
307 template <typename Predicate>
308 [[nodiscard]] auto none(Predicate&& pred) const -> bool
309 {
310 return self().query().none(std::forward<Predicate>(pred));
311 }
312
313 [[nodiscard]] auto take(size_t n) const { return self().query().take(n); }
314
315 [[nodiscard]] auto skip(size_t n) const { return self().query().skip(n); }
316
317#if defined(THREADSCHEDULE_HAS_REFLECTION) && THREADSCHEDULE_HAS_REFLECTION
318 template <reflect::info Field, typename Value>
319 [[nodiscard]] auto where(Value const& value) const
320 {
321 return self().query().template where<Field>(value);
322 }
323
324 template <reflect::info Field, typename Predicate>
325 [[nodiscard]] auto where_if(Predicate&& pred) const
326 {
327 return self().query().template where_if<Field>(std::forward<Predicate>(pred));
328 }
329
330 template <reflect::info Field, typename Value>
331 [[nodiscard]] auto find_by(Value const& value) const
332 {
333 return self().query().template find_by<Field>(value);
334 }
335
336 template <reflect::info Field, typename Value>
337 [[nodiscard]] auto contains(Value const& value) const -> bool
338 {
339 return self().query().template contains<Field>(value);
340 }
341
342 template <reflect::info... Fields>
343 [[nodiscard]] auto project() const
344 {
345 return self().query().template project<Fields...>();
346 }
347#endif
348};
349
350} // namespace detail
351
396class ThreadRegistry : public detail::QueryFacadeMixin<ThreadRegistry>
397{
398 public:
399 ThreadRegistry() = default;
401 auto operator=(ThreadRegistry const&) -> ThreadRegistry& = delete;
402
403 void register_current_thread(std::string name = std::string(), std::string componentTag = std::string())
404 {
407 info.stdId = std::this_thread::get_id();
408 info.name = std::move(name);
409 info.componentTag = std::move(componentTag);
410 info.alive = true;
411 try_register(std::move(info));
412 }
413
414 void register_current_thread(std::shared_ptr<ThreadControlBlock> const& controlBlock,
415 std::string name = std::string(), std::string componentTag = std::string())
416 {
417 if (!controlBlock)
418 return;
420 info.tid = controlBlock->tid();
421 info.stdId = controlBlock->std_id();
422 info.name = std::move(name);
423 info.componentTag = std::move(componentTag);
424 info.alive = true;
425 info.control = controlBlock;
426 try_register(std::move(info));
427 }
428
430 {
431 Tid const tid = ThreadInfo::get_thread_id();
432 std::unique_lock<std::shared_mutex> lock(mutex_);
433 auto it = threads_.find(tid);
434 if (it != threads_.end())
435 {
436 it->second.alive = false;
437 auto info = it->second;
438 threads_.erase(it);
439 if (onUnregister_)
440 {
441 auto cb = onUnregister_;
442 lock.unlock();
443 cb(info);
444 }
445 }
446 }
447
448 // Lookup
449 [[nodiscard]] auto get(Tid tid) const -> std::optional<RegisteredThreadInfo>
450 {
451 std::shared_lock<std::shared_mutex> lock(mutex_);
452 auto it = threads_.find(tid);
453 if (it == threads_.end())
454 return std::nullopt;
455 return it->second;
456 }
457
495 {
496 public:
497 explicit QueryView(std::vector<RegisteredThreadInfo> entries) : entries_(std::move(entries))
498 {
499 }
500
501 template <typename Predicate>
502 auto filter(Predicate&& pred) const -> QueryView
503 {
504 std::vector<RegisteredThreadInfo> filtered;
505 filtered.reserve(entries_.size());
506 for (auto const& entry : entries_)
507 {
508 if (pred(entry))
509 filtered.push_back(entry);
510 }
511 return QueryView(std::move(filtered));
512 }
513
514 template <typename Fn>
515 void for_each(Fn&& fn) const
516 {
517 for (auto const& entry : entries_)
518 {
519 fn(entry);
520 }
521 }
522
523 [[nodiscard]] auto count() const -> size_t
524 {
525 return entries_.size();
526 }
527
528 [[nodiscard]] auto empty() const -> bool
529 {
530 return entries_.empty();
531 }
532
533 [[nodiscard]] auto entries() const -> std::vector<RegisteredThreadInfo> const&
534 {
535 return entries_;
536 }
537
538 // Transform entries to a vector of another type
539 template <typename Fn>
540 [[nodiscard]] auto map(Fn&& fn) const -> std::vector<std::invoke_result_t<Fn, RegisteredThreadInfo const&>>
541 {
542 std::vector<std::invoke_result_t<Fn, RegisteredThreadInfo const&>> result;
543 result.reserve(entries_.size());
544 for (auto const& entry : entries_)
545 {
546 result.push_back(fn(entry));
547 }
548 return result;
549 }
550
551 // Find first entry matching predicate
552 template <typename Predicate>
553 [[nodiscard]] auto find_if(Predicate&& pred) const -> std::optional<RegisteredThreadInfo>
554 {
555 for (auto const& entry : entries_)
556 {
557 if (pred(entry))
558 return entry;
559 }
560 return std::nullopt;
561 }
562
563 template <typename Predicate>
564 [[nodiscard]] auto any(Predicate&& pred) const -> bool
565 {
566 for (auto const& entry : entries_)
567 {
568 if (pred(entry))
569 return true;
570 }
571 return false;
572 }
573
574 template <typename Predicate>
575 [[nodiscard]] auto all(Predicate&& pred) const -> bool
576 {
577 for (auto const& entry : entries_)
578 {
579 if (!pred(entry))
580 return false;
581 }
582 return true;
583 }
584
585 template <typename Predicate>
586 [[nodiscard]] auto none(Predicate&& pred) const -> bool
587 {
588 return !any(std::forward<Predicate>(pred));
589 }
590
591 [[nodiscard]] auto take(size_t n) const -> QueryView
592 {
593 auto result = entries_;
594 if (result.size() > n)
595 result.resize(n);
596 return QueryView(std::move(result));
597 }
598
599 [[nodiscard]] auto skip(size_t n) const -> QueryView
600 {
601 std::vector<RegisteredThreadInfo> result;
602 if (n < entries_.size())
603 {
604 result.assign(entries_.begin() + n, entries_.end());
605 }
606 return QueryView(std::move(result));
607 }
608
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
612 {
613 detail::validate_reflected_field<Field, RegisteredThreadInfo>();
614 std::vector<RegisteredThreadInfo> filtered;
615 filtered.reserve(entries_.size());
616 for (auto const& entry : entries_)
617 {
618 if (reflect::get<Field>(entry) == value)
619 filtered.push_back(entry);
620 }
621 return QueryView(std::move(filtered));
622 }
623
624 template <reflect::info Field, typename Predicate>
625 [[nodiscard]] auto where_if(Predicate&& pred) const -> QueryView
626 {
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_)
633 {
634 if (pred(reflect::get<Field>(entry)))
635 filtered.push_back(entry);
636 }
637 return QueryView(std::move(filtered));
638 }
639
640 template <reflect::info Field, typename Value>
641 [[nodiscard]] auto find_by(Value const& value) const -> std::optional<RegisteredThreadInfo>
642 {
643 detail::validate_reflected_field<Field, RegisteredThreadInfo>();
644 for (auto const& entry : entries_)
645 {
646 if (reflect::get<Field>(entry) == value)
647 return entry;
648 }
649 return std::nullopt;
650 }
651
652 template <reflect::info Field, typename Value>
653 [[nodiscard]] auto contains(Value const& value) const -> bool
654 {
655 return find_by<Field>(value).has_value();
656 }
657
658 template <reflect::info... Fields>
659 [[nodiscard]] auto project() const -> std::vector<reflect::projection_t<Fields...>>
660 {
661 static_assert(sizeof...(Fields) > 0, "project requires at least one field");
662 (detail::validate_reflected_field<Fields, RegisteredThreadInfo>(), ...);
663
664 std::vector<reflect::projection_t<Fields...>> result;
665 result.reserve(entries_.size());
666 for (auto const& entry : entries_)
667 {
668 result.push_back(reflect::project_value<Fields...>(entry));
669 }
670 return result;
671 }
672#endif
673
674 private:
675 std::vector<RegisteredThreadInfo> entries_;
676 };
677
678 // Create a query view over all registered threads
679 [[nodiscard]] auto query() const -> QueryView
680 {
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_)
685 {
686 snapshot.push_back(kv.second);
687 }
688 return QueryView(std::move(snapshot));
689 }
690
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
694 {
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_)
700 {
701 (void)tid;
702 if (reflect::get<Field>(entry) == value)
703 filtered.push_back(entry);
704 }
705 return QueryView(std::move(filtered));
706 }
707
708 template <reflect::info Field, typename Predicate>
709 [[nodiscard]] auto where_if(Predicate&& pred) const -> QueryView
710 {
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_)
718 {
719 (void)tid;
720 if (pred(reflect::get<Field>(entry)))
721 filtered.push_back(entry);
722 }
723 return QueryView(std::move(filtered));
724 }
725
726 template <reflect::info Field, typename Value>
727 [[nodiscard]] auto find_by(Value const& value) const -> std::optional<RegisteredThreadInfo>
728 {
729 detail::validate_reflected_field<Field, RegisteredThreadInfo>();
730 std::shared_lock<std::shared_mutex> lock(mutex_);
731 for (auto const& [tid, entry] : threads_)
732 {
733 (void)tid;
734 if (reflect::get<Field>(entry) == value)
735 return entry;
736 }
737 return std::nullopt;
738 }
739
740 template <reflect::info Field, typename Value>
741 [[nodiscard]] auto contains(Value const& value) const -> bool
742 {
743 detail::validate_reflected_field<Field, RegisteredThreadInfo>();
744 std::shared_lock<std::shared_mutex> lock(mutex_);
745 for (auto const& [tid, entry] : threads_)
746 {
747 (void)tid;
748 if (reflect::get<Field>(entry) == value)
749 return true;
750 }
751 return false;
752 }
753
754 template <reflect::info... Fields>
755 [[nodiscard]] auto project() const -> std::vector<reflect::projection_t<Fields...>>
756 {
757 static_assert(sizeof...(Fields) > 0, "project requires at least one field");
758 (detail::validate_reflected_field<Fields, RegisteredThreadInfo>(), ...);
759
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_)
764 {
765 (void)tid;
766 result.push_back(reflect::project_value<Fields...>(entry));
767 }
768 return result;
769 }
770#endif
771
772 [[nodiscard]] auto set_affinity(Tid tid, ThreadAffinity const& affinity) const -> expected<void, std::error_code>
773 {
774 auto blk = lock_block(tid);
775 if (!blk)
776 return unexpected(std::make_error_code(std::errc::no_such_process));
777 return blk->set_affinity(affinity);
778 }
779
780 [[nodiscard]] auto set_priority(Tid tid, ThreadPriority priority) const -> expected<void, std::error_code>
781 {
782 auto blk = lock_block(tid);
783 if (!blk)
784 return unexpected(std::make_error_code(std::errc::no_such_process));
785 return blk->set_priority(priority);
786 }
787
788 [[nodiscard]] auto set_scheduling_policy(Tid tid, SchedulingPolicy policy, ThreadPriority priority) const
790 {
791 auto blk = lock_block(tid);
792 if (!blk)
793 return unexpected(std::make_error_code(std::errc::no_such_process));
794 return blk->set_scheduling_policy(policy, priority);
795 }
796
797 [[nodiscard]] auto set_name(Tid tid, std::string const& name) const -> expected<void, std::error_code>
798 {
799 auto blk = lock_block(tid);
800 if (!blk)
801 return unexpected(std::make_error_code(std::errc::no_such_process));
802 return blk->set_name(name);
803 }
804
805 // Register/unregister hooks (system integration)
807 {
808 std::unique_lock<std::shared_mutex> lock(mutex_);
809 onRegister_ = std::move(cb);
810 }
811
812 template <typename Callback,
813 std::enable_if_t<!std::is_same_v<detail::remove_cvref_t<Callback>, RegistryCallback>, int> = 0>
814 void set_on_register(Callback&& cb)
815 {
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_);
819 onRegister_ = detail::make_copyable_callable<void(RegisteredThreadInfo const&)>(std::forward<Callback>(cb));
820 }
821
823 {
824 std::unique_lock<std::shared_mutex> lock(mutex_);
825 onUnregister_ = std::move(cb);
826 }
827
828 template <typename Callback,
829 std::enable_if_t<!std::is_same_v<detail::remove_cvref_t<Callback>, RegistryCallback>, int> = 0>
830 void set_on_unregister(Callback&& cb)
831 {
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_);
835 onUnregister_ = detail::make_copyable_callable<void(RegisteredThreadInfo const&)>(std::forward<Callback>(cb));
836 }
837
838 private:
839 void try_register(RegisteredThreadInfo info)
840 {
841 std::unique_lock<std::shared_mutex> lock(mutex_);
842 auto it = threads_.find(info.tid);
843 if (it != threads_.end())
844 return;
845 auto stored = info;
846 threads_.emplace(info.tid, std::move(info));
847 if (onRegister_)
848 {
849 auto cb = onRegister_;
850 lock.unlock();
851 cb(stored);
852 }
853 }
854
855 [[nodiscard]] auto lock_block(Tid tid) const -> std::shared_ptr<ThreadControlBlock>
856 {
857 std::shared_lock<std::shared_mutex> lock(mutex_);
858 auto it = threads_.find(tid);
859 if (it == threads_.end())
860 return nullptr;
861 return it->second.control;
862 }
863 mutable std::shared_mutex mutex_;
864 std::unordered_map<Tid, RegisteredThreadInfo> threads_;
865
866 RegistryCallback onRegister_;
867 RegistryCallback onUnregister_;
868};
869
890
891#if defined(THREADSCHEDULE_RUNTIME)
894#else
896inline auto registry_storage() -> ThreadRegistry*&
897{
898 static ThreadRegistry* external = nullptr;
899 return external;
900}
902
912inline auto registry() -> ThreadRegistry&
913{
914 ThreadRegistry*& ext = registry_storage();
915 if (ext != nullptr)
916 return *ext;
917 static ThreadRegistry local;
918 return local;
919}
920
938{
939 registry_storage() = reg;
940}
941
942#endif
943
953enum class BuildMode : std::uint8_t
954{
957};
958
959#if defined(THREADSCHEDULE_RUNTIME)
960inline constexpr bool is_runtime_build = true;
961
967#else
968inline constexpr bool is_runtime_build = false;
969
974inline auto build_mode() -> BuildMode
975{
977}
978#endif
979
984inline auto build_mode_string() -> char const*
985{
986 return is_runtime_build ? "runtime" : "header-only";
987}
988
1020class CompositeThreadRegistry : public detail::QueryFacadeMixin<CompositeThreadRegistry>
1021{
1022 public:
1024 {
1025 if (reg == nullptr)
1026 return;
1027 std::lock_guard<std::mutex> lock(mutex_);
1028 registries_.push_back(reg);
1029 }
1030
1031 [[nodiscard]] auto query() const -> ThreadRegistry::QueryView
1032 {
1033 std::vector<RegisteredThreadInfo> merged;
1034 std::vector<ThreadRegistry*> regs;
1035 {
1036 std::lock_guard<std::mutex> lock(mutex_);
1037 regs = registries_;
1038 }
1039 for (auto* r : regs)
1040 {
1041 auto view = r->query();
1042 auto const& entries = view.entries();
1043 merged.insert(merged.end(), entries.begin(), entries.end());
1044 }
1045 return ThreadRegistry::QueryView(std::move(merged));
1046 }
1047
1048 private:
1049 mutable std::mutex mutex_;
1050 std::vector<ThreadRegistry*> registries_;
1051};
1052
1094{
1095 public:
1096 explicit AutoRegisterCurrentThread(std::string const& name = std::string(),
1097 std::string const& componentTag = std::string())
1098 : active_(true), externalReg_(nullptr)
1099 {
1101 (void)block->set_name(name);
1102 registry().register_current_thread(block, name, componentTag);
1103 }
1104
1105 explicit AutoRegisterCurrentThread(ThreadRegistry& reg, std::string const& name = std::string(),
1106 std::string const& componentTag = std::string())
1107 : active_(true), externalReg_(&reg)
1108 {
1110 (void)block->set_name(name);
1111 externalReg_->register_current_thread(block, name, componentTag);
1112 }
1114 {
1115 if (active_)
1116 {
1117 if (externalReg_ != nullptr)
1118 externalReg_->unregister_current_thread();
1119 else
1120 registry().unregister_current_thread();
1121 }
1122 }
1126 : active_(other.active_), externalReg_(other.externalReg_)
1127 {
1128 other.active_ = false;
1129 other.externalReg_ = nullptr;
1130 }
1132 {
1133 if (this != &other)
1134 {
1135 if (active_)
1136 {
1137 if (externalReg_ != nullptr)
1138 externalReg_->unregister_current_thread();
1139 else
1140 registry().unregister_current_thread();
1141 }
1142 active_ = other.active_;
1143 externalReg_ = other.externalReg_;
1144 other.active_ = false;
1145 other.externalReg_ = nullptr;
1146 }
1147 return *this;
1148 }
1149
1150 private:
1151 bool active_;
1152 ThreadRegistry* externalReg_;
1153};
1154
1155} // namespace threadschedule
1156
1157#ifndef _WIN32
1158namespace threadschedule
1159{
1183inline auto cgroup_attach_tid(std::string const& cgroupDir, Tid tid) -> expected<void, std::error_code>
1184{
1185 std::vector<std::string> candidates = {"cgroup.threads", "tasks", "cgroup.procs"};
1186 for (auto const& file : candidates)
1187 {
1188 std::string path = cgroupDir + "/" + file;
1189 std::ofstream out(path);
1190 if (!out)
1191 continue;
1192 out << tid;
1193 out.flush();
1194 if (out)
1195 return {};
1196 }
1197 return unexpected(std::make_error_code(std::errc::operation_not_permitted));
1198}
1199} // namespace threadschedule
1200#endif
Feature-gated callable storage aliases for modern C++ builds.
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 >
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.
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).
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 set_on_unregister(RegistryCallback 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 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.
std::function< Signature > copyable_callable
Definition callable.hpp:40
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 >
Definition callable.hpp:117
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.