ThreadSchedule 1.0.0
Modern C++ thread management library
Loading...
Searching...
No Matches
thread_registry.hpp
1#pragma once
2
3#include "expected.hpp"
4#include "scheduler_policy.hpp"
5#include "thread_wrapper.hpp" // for ThreadInfo, ThreadAffinity
6#include <functional>
7#include <memory>
8#include <mutex>
9#include <optional>
10#include <shared_mutex>
11#include <string>
12#include <thread>
13#include <tuple>
14#include <unordered_map>
15#include <utility>
16#include <vector>
17
18#ifdef _WIN32
19#include <windows.h>
20#else
21#include <pthread.h>
22#include <sched.h>
23#include <sys/types.h>
24#endif
25
26namespace threadschedule
27{
28
29// Optional export macro for building a runtime (shared/dll) variant
30#if defined(_WIN32) || defined(_WIN64)
31#if defined(THREADSCHEDULE_EXPORTS)
32#define THREADSCHEDULE_API __declspec(dllexport)
33#else
34#define THREADSCHEDULE_API __declspec(dllimport)
35#endif
36#else
37#define THREADSCHEDULE_API
38#endif
39
40#ifdef _WIN32
41using Tid = unsigned long; // DWORD thread id
42#else
43using Tid = pid_t; // Linux TID via gettid()
44#endif
45
82{
83 Tid tid{};
84 std::thread::id stdId;
85 std::string name;
86 std::string componentTag;
87 bool alive{true};
88 std::shared_ptr<class ThreadControlBlock> control;
89};
90
129class ThreadControlBlock
130{
131 public:
132 ThreadControlBlock() = default;
133 ThreadControlBlock(ThreadControlBlock const&) = delete;
134 auto operator=(ThreadControlBlock const&) -> ThreadControlBlock& = delete;
135 ThreadControlBlock(ThreadControlBlock&&) = delete;
136 auto operator=(ThreadControlBlock&&) -> ThreadControlBlock& = delete;
137
138 ~ThreadControlBlock()
139 {
140#ifdef _WIN32
141 if (handle_)
142 {
143 CloseHandle(handle_);
144 handle_ = nullptr;
145 }
146#endif
147 }
148
149 [[nodiscard]] auto tid() const noexcept -> Tid
150 {
151 return tid_;
152 }
153 [[nodiscard]] auto std_id() const noexcept -> std::thread::id
154 {
155 return stdId_;
156 }
157 // Removed name/component metadata from control block; metadata lives in RegisteredThreadInfo
158
159 [[nodiscard]] auto set_affinity(ThreadAffinity const& affinity) const -> expected<void, std::error_code>
160 {
161#ifdef _WIN32
162 if (!handle_)
163 return unexpected(std::make_error_code(std::errc::no_such_process));
164 using SetThreadGroupAffinityFn = BOOL(WINAPI*)(HANDLE, const GROUP_AFFINITY*, PGROUP_AFFINITY);
165 HMODULE hMod = GetModuleHandleW(L"kernel32.dll");
166 if (hMod)
167 {
168 auto set_group_affinity = reinterpret_cast<SetThreadGroupAffinityFn>(
169 reinterpret_cast<void*>(GetProcAddress(hMod, "SetThreadGroupAffinity")));
170 if (set_group_affinity && affinity.has_any())
171 {
172 GROUP_AFFINITY ga{};
173 ga.Mask = static_cast<KAFFINITY>(affinity.get_mask());
174 ga.Group = affinity.get_group();
175 if (set_group_affinity(handle_, &ga, nullptr) != 0)
176 return {};
177 return unexpected(std::make_error_code(std::errc::operation_not_permitted));
178 }
179 }
180 DWORD_PTR mask = static_cast<DWORD_PTR>(affinity.get_mask());
181 if (SetThreadAffinityMask(handle_, mask) != 0)
182 return {};
183 return unexpected(std::make_error_code(std::errc::operation_not_permitted));
184#else
185 if (pthread_setaffinity_np(pthreadHandle_, sizeof(cpu_set_t), &affinity.native_handle()) == 0)
186 return {};
187 return unexpected(std::error_code(errno, std::generic_category()));
188#endif
189 }
190
191 [[nodiscard]] auto set_priority(ThreadPriority priority) const -> expected<void, std::error_code>
192 {
193#ifdef _WIN32
194 if (!handle_)
195 return unexpected(std::make_error_code(std::errc::no_such_process));
196 int win_priority;
197 int prio_val = priority.value();
198 if (prio_val <= -10)
199 win_priority = THREAD_PRIORITY_IDLE;
200 else if (prio_val <= -5)
201 win_priority = THREAD_PRIORITY_LOWEST;
202 else if (prio_val < 0)
203 win_priority = THREAD_PRIORITY_BELOW_NORMAL;
204 else if (prio_val == 0)
205 win_priority = THREAD_PRIORITY_NORMAL;
206 else if (prio_val <= 5)
207 win_priority = THREAD_PRIORITY_ABOVE_NORMAL;
208 else if (prio_val <= 10)
209 win_priority = THREAD_PRIORITY_HIGHEST;
210 else
211 win_priority = THREAD_PRIORITY_TIME_CRITICAL;
212 if (SetThreadPriority(handle_, win_priority) != 0)
213 return {};
214 return unexpected(std::make_error_code(std::errc::operation_not_permitted));
215#else
216 const int policy = SCHED_OTHER;
217 auto params_result = SchedulerParams::create_for_policy(SchedulingPolicy::OTHER, priority);
218 if (!params_result.has_value())
219 return unexpected(params_result.error());
220 if (pthread_setschedparam(pthreadHandle_, policy, &params_result.value()) == 0)
221 return {};
222 return unexpected(std::error_code(errno, std::generic_category()));
223#endif
224 }
225
226 [[nodiscard]] auto set_scheduling_policy(SchedulingPolicy policy, ThreadPriority priority) const
228 {
229#ifdef _WIN32
230 return set_priority(priority);
231#else
232 const int policy_int = static_cast<int>(policy);
233 auto params_result = SchedulerParams::create_for_policy(policy, priority);
234 if (!params_result.has_value())
235 return unexpected(params_result.error());
236 if (pthread_setschedparam(pthreadHandle_, policy_int, &params_result.value()) == 0)
237 return {};
238 return unexpected(std::error_code(errno, std::generic_category()));
239#endif
240 }
241
242 [[nodiscard]] auto set_name(std::string const& name) const -> expected<void, std::error_code>
243 {
244#ifdef _WIN32
245 if (!handle_)
246 return unexpected(std::make_error_code(std::errc::no_such_process));
247 using SetThreadDescriptionFn = HRESULT(WINAPI*)(HANDLE, PCWSTR);
248 HMODULE hMod = GetModuleHandleW(L"kernel32.dll");
249 if (!hMod)
250 return unexpected(std::make_error_code(std::errc::function_not_supported));
251 auto set_desc = reinterpret_cast<SetThreadDescriptionFn>(
252 reinterpret_cast<void*>(GetProcAddress(hMod, "SetThreadDescription")));
253 if (!set_desc)
254 return unexpected(std::make_error_code(std::errc::function_not_supported));
255 std::wstring wide(name.begin(), name.end());
256 if (SUCCEEDED(set_desc(handle_, wide.c_str())))
257 return {};
258 return unexpected(std::make_error_code(std::errc::operation_not_permitted));
259#else
260 if (name.length() > 15)
261 return unexpected(std::make_error_code(std::errc::invalid_argument));
262 if (pthread_setname_np(pthreadHandle_, name.c_str()) == 0)
263 return {};
264 return unexpected(std::error_code(errno, std::generic_category()));
265#endif
266 }
267
268 static auto create_for_current_thread() -> std::shared_ptr<ThreadControlBlock>
269 {
270 auto block = std::make_shared<ThreadControlBlock>();
271 block->tid_ = ThreadInfo::get_thread_id();
272 block->stdId_ = std::this_thread::get_id();
273#ifdef _WIN32
274 HANDLE realHandle = nullptr;
275 DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &realHandle,
276 THREAD_SET_INFORMATION | THREAD_QUERY_INFORMATION, FALSE, 0);
277 block->handle_ = realHandle;
278#else
279 block->pthreadHandle_ = pthread_self();
280#endif
281 return block;
282 }
283
284 private:
285 Tid tid_{};
286 std::thread::id stdId_;
287#ifdef _WIN32
288 HANDLE handle_ = nullptr;
289#else
290 pthread_t pthreadHandle_{};
291#endif
292};
293
336class ThreadRegistry
337{
338 public:
339 ThreadRegistry() = default;
340 ThreadRegistry(ThreadRegistry const&) = delete;
341 auto operator=(ThreadRegistry const&) -> ThreadRegistry& = delete;
342
343 // Register/unregister the CURRENT thread (to be called inside the running thread)
344 void register_current_thread(std::string name = std::string(), std::string componentTag = std::string())
345 {
346 Tid const tid = ThreadInfo::get_thread_id();
348 info.tid = tid;
349 info.stdId = std::this_thread::get_id();
350 info.name = std::move(name);
351 info.componentTag = std::move(componentTag);
352 info.alive = true;
353
354 {
355 std::unique_lock<std::shared_mutex> lock(mutex_);
356 auto it = threads_.find(tid);
357 if (it == threads_.end())
358 {
359 auto stored = info; // copy for callback
360 threads_.emplace(tid, std::move(info));
361 if (onRegister_)
362 {
363 auto cb = onRegister_;
364 lock.unlock();
365 cb(stored);
366 }
367 }
368 else
369 {
370 // Duplicate registration of the same TID is a no-op (first registration wins)
371 }
372 }
373 }
374
375 void register_current_thread(std::shared_ptr<ThreadControlBlock> const& controlBlock,
376 std::string name = std::string(), std::string componentTag = std::string())
377 {
378 if (!controlBlock)
379 return;
381 info.tid = controlBlock->tid();
382 info.stdId = controlBlock->std_id();
383 info.name = std::move(name);
384 info.componentTag = std::move(componentTag);
385 info.alive = true;
386 info.control = controlBlock;
387 std::unique_lock<std::shared_mutex> lock(mutex_);
388 auto it = threads_.find(info.tid);
389 if (it == threads_.end())
390 {
391 auto stored = info; // copy for callback
392 threads_.emplace(info.tid, std::move(info));
393 if (onRegister_)
394 {
395 auto cb = onRegister_;
396 lock.unlock();
397 cb(stored);
398 }
399 }
400 else
401 {
402 // Duplicate registration of the same TID is a no-op (first registration wins)
403 }
404 }
405
406 void unregister_current_thread()
407 {
408 Tid const tid = ThreadInfo::get_thread_id();
409 std::unique_lock<std::shared_mutex> lock(mutex_);
410 auto it = threads_.find(tid);
411 if (it != threads_.end())
412 {
413 it->second.alive = false;
414 auto info = it->second;
415 threads_.erase(it);
416 if (onUnregister_)
417 {
418 auto cb = onUnregister_;
419 lock.unlock();
420 cb(info);
421 }
422 }
423 }
424
425 // Lookup
426 [[nodiscard]] auto get(Tid tid) const -> std::optional<RegisteredThreadInfo>
427 {
428 std::shared_lock<std::shared_mutex> lock(mutex_);
429 auto it = threads_.find(tid);
430 if (it == threads_.end())
431 return std::nullopt;
432 return it->second;
433 }
434
471 class QueryView
472 {
473 public:
474 explicit QueryView(std::vector<RegisteredThreadInfo> entries) : entries_(std::move(entries))
475 {
476 }
477
478 template <typename Predicate>
479 auto filter(Predicate&& pred) const -> QueryView
480 {
481 std::vector<RegisteredThreadInfo> filtered;
482 filtered.reserve(entries_.size());
483 for (auto const& entry : entries_)
484 {
485 if (pred(entry))
486 filtered.push_back(entry);
487 }
488 return QueryView(std::move(filtered));
489 }
490
491 template <typename Fn>
492 void for_each(Fn&& fn) const
493 {
494 for (auto const& entry : entries_)
495 {
496 fn(entry);
497 }
498 }
499
500 [[nodiscard]] auto count() const -> size_t
501 {
502 return entries_.size();
503 }
504
505 [[nodiscard]] auto empty() const -> bool
506 {
507 return entries_.empty();
508 }
509
510 [[nodiscard]] auto entries() const -> std::vector<RegisteredThreadInfo> const&
511 {
512 return entries_;
513 }
514
515 // Transform entries to a vector of another type
516 template <typename Fn>
517 [[nodiscard]] auto map(Fn&& fn) const -> std::vector<std::invoke_result_t<Fn, RegisteredThreadInfo const&>>
518 {
519 std::vector<std::invoke_result_t<Fn, RegisteredThreadInfo const&>> result;
520 result.reserve(entries_.size());
521 for (auto const& entry : entries_)
522 {
523 result.push_back(fn(entry));
524 }
525 return result;
526 }
527
528 // Find first entry matching predicate
529 template <typename Predicate>
530 [[nodiscard]] auto find_if(Predicate&& pred) const -> std::optional<RegisteredThreadInfo>
531 {
532 for (auto const& entry : entries_)
533 {
534 if (pred(entry))
535 return entry;
536 }
537 return std::nullopt;
538 }
539
540 template <typename Predicate>
541 [[nodiscard]] auto any(Predicate&& pred) const -> bool
542 {
543 for (auto const& entry : entries_)
544 {
545 if (pred(entry))
546 return true;
547 }
548 return false;
549 }
550
551 template <typename Predicate>
552 [[nodiscard]] auto all(Predicate&& pred) const -> bool
553 {
554 for (auto const& entry : entries_)
555 {
556 if (!pred(entry))
557 return false;
558 }
559 return true;
560 }
561
562 template <typename Predicate>
563 [[nodiscard]] auto none(Predicate&& pred) const -> bool
564 {
565 return !any(std::forward<Predicate>(pred));
566 }
567
568 [[nodiscard]] auto take(size_t n) const -> QueryView
569 {
570 auto result = entries_;
571 if (result.size() > n)
572 result.resize(n);
573 return QueryView(std::move(result));
574 }
575
576 [[nodiscard]] auto skip(size_t n) const -> QueryView
577 {
578 std::vector<RegisteredThreadInfo> result;
579 if (n < entries_.size())
580 {
581 result.assign(entries_.begin() + n, entries_.end());
582 }
583 return QueryView(std::move(result));
584 }
585
586 private:
587 std::vector<RegisteredThreadInfo> entries_;
588 };
589
590 // Create a query view over all registered threads
591 [[nodiscard]] auto query() const -> QueryView
592 {
593 std::vector<RegisteredThreadInfo> snapshot;
594 std::shared_lock<std::shared_mutex> lock(mutex_);
595 snapshot.reserve(threads_.size());
596 for (auto const& kv : threads_)
597 {
598 snapshot.push_back(kv.second);
599 }
600 return QueryView(std::move(snapshot));
601 }
602
603 template <typename Predicate>
604 [[nodiscard]] auto filter(Predicate&& pred) const -> QueryView
605 {
606 return query().filter(std::forward<Predicate>(pred));
607 }
608
609 [[nodiscard]] auto count() const -> size_t
610 {
611 return query().count();
612 }
613
614 [[nodiscard]] auto empty() const -> bool
615 {
616 return query().empty();
617 }
618
619 template <typename Fn>
620 void for_each(Fn&& fn) const
621 {
622 query().for_each(std::forward<Fn>(fn));
623 }
624
625 template <typename Predicate, typename Fn>
626 void apply(Predicate&& pred, Fn&& fn) const
627 {
628 query().filter(std::forward<Predicate>(pred)).for_each(std::forward<Fn>(fn));
629 }
630
631 template <typename Fn>
632 [[nodiscard]] auto map(Fn&& fn) const -> std::vector<std::invoke_result_t<Fn, RegisteredThreadInfo const&>>
633 {
634 return query().map(std::forward<Fn>(fn));
635 }
636
637 template <typename Predicate>
638 [[nodiscard]] auto find_if(Predicate&& pred) const -> std::optional<RegisteredThreadInfo>
639 {
640 return query().find_if(std::forward<Predicate>(pred));
641 }
642
643 template <typename Predicate>
644 [[nodiscard]] auto any(Predicate&& pred) const -> bool
645 {
646 return query().any(std::forward<Predicate>(pred));
647 }
648
649 template <typename Predicate>
650 [[nodiscard]] auto all(Predicate&& pred) const -> bool
651 {
652 return query().all(std::forward<Predicate>(pred));
653 }
654
655 template <typename Predicate>
656 [[nodiscard]] auto none(Predicate&& pred) const -> bool
657 {
658 return query().none(std::forward<Predicate>(pred));
659 }
660
661 [[nodiscard]] auto take(size_t n) const -> QueryView
662 {
663 return query().take(n);
664 }
665
666 [[nodiscard]] auto skip(size_t n) const -> QueryView
667 {
668 return query().skip(n);
669 }
670
671 [[nodiscard]] auto set_affinity(Tid tid, ThreadAffinity const& affinity) const -> expected<void, std::error_code>
672 {
673 auto blk = lock_block(tid);
674 if (!blk)
675 return unexpected(std::make_error_code(std::errc::no_such_process));
676 return blk->set_affinity(affinity);
677 }
678
679 [[nodiscard]] auto set_priority(Tid tid, ThreadPriority priority) const -> expected<void, std::error_code>
680 {
681 auto blk = lock_block(tid);
682 if (!blk)
683 return unexpected(std::make_error_code(std::errc::no_such_process));
684 return blk->set_priority(priority);
685 }
686
687 [[nodiscard]] auto set_scheduling_policy(Tid tid, SchedulingPolicy policy, ThreadPriority priority) const
688 -> expected<void, std::error_code>
689 {
690 auto blk = lock_block(tid);
691 if (!blk)
692 return unexpected(std::make_error_code(std::errc::no_such_process));
693 return blk->set_scheduling_policy(policy, priority);
694 }
695
696 [[nodiscard]] auto set_name(Tid tid, std::string const& name) const -> expected<void, std::error_code>
697 {
698 auto blk = lock_block(tid);
699 if (!blk)
700 return unexpected(std::make_error_code(std::errc::no_such_process));
701 return blk->set_name(name);
702 }
703
704 // Register/unregister hooks (system integration)
705 void set_on_register(std::function<void(RegisteredThreadInfo const&)> cb)
706 {
707 std::unique_lock<std::shared_mutex> lock(mutex_);
708 onRegister_ = std::move(cb);
709 }
710
711 void set_on_unregister(std::function<void(RegisteredThreadInfo const&)> cb)
712 {
713 std::unique_lock<std::shared_mutex> lock(mutex_);
714 onUnregister_ = std::move(cb);
715 }
716
717 private:
718 [[nodiscard]] auto lock_block(Tid tid) const -> std::shared_ptr<ThreadControlBlock>
719 {
720 std::shared_lock<std::shared_mutex> lock(mutex_);
721 auto it = threads_.find(tid);
722 if (it == threads_.end())
723 return nullptr;
724 return it->second.control;
725 }
726 mutable std::shared_mutex mutex_;
727 std::unordered_map<Tid, RegisteredThreadInfo> threads_;
728
729 // Integration hooks
730 std::function<void(RegisteredThreadInfo const&)> onRegister_;
731 std::function<void(RegisteredThreadInfo const&)> onUnregister_;
732};
733
754
755#if defined(THREADSCHEDULE_RUNTIME)
756THREADSCHEDULE_API auto registry() -> ThreadRegistry&;
757THREADSCHEDULE_API void set_external_registry(ThreadRegistry* reg);
758#else
760inline auto registry_storage() -> ThreadRegistry*&
761{
762 static ThreadRegistry* external = nullptr;
763 return external;
764}
766
776inline auto registry() -> ThreadRegistry&
777{
778 ThreadRegistry*& ext = registry_storage();
779 if (ext != nullptr)
780 return *ext;
781 static ThreadRegistry local;
782 return local;
783}
784
801inline void set_external_registry(ThreadRegistry* reg)
802{
803 registry_storage() = reg;
804}
806#endif
807
817enum class BuildMode : std::uint8_t
818{
819 HEADER_ONLY,
820 RUNTIME
821};
822
823#if defined(THREADSCHEDULE_RUNTIME)
824inline constexpr bool is_runtime_build = true;
825
830THREADSCHEDULE_API auto build_mode() -> BuildMode;
831#else
832inline constexpr bool is_runtime_build = false;
833
838inline auto build_mode() -> BuildMode
839{
840 return BuildMode::HEADER_ONLY;
841}
842#endif
843
848inline auto build_mode_string() -> char const*
849{
850 return is_runtime_build ? "runtime" : "header-only";
851}
852
885{
886 public:
887 void attach(ThreadRegistry* reg)
888 {
889 if (reg == nullptr)
890 return;
891 std::lock_guard<std::mutex> lock(mutex_);
892 registries_.push_back(reg);
893 }
894
895 // Chainable query API
896 [[nodiscard]] auto query() const -> ThreadRegistry::QueryView
897 {
898 std::vector<RegisteredThreadInfo> merged;
899 std::vector<ThreadRegistry*> regs;
900 {
901 std::lock_guard<std::mutex> lock(mutex_);
902 regs = registries_;
903 }
904 for (auto* r : regs)
905 {
906 auto view = r->query();
907 auto const& entries = view.entries();
908 merged.insert(merged.end(), entries.begin(), entries.end());
909 }
910 return ThreadRegistry::QueryView(std::move(merged));
911 }
912
913 template <typename Predicate>
914 [[nodiscard]] auto filter(Predicate&& pred) const -> ThreadRegistry::QueryView
915 {
916 return query().filter(std::forward<Predicate>(pred));
917 }
918
919 [[nodiscard]] auto count() const -> size_t
920 {
921 return query().count();
922 }
923
924 [[nodiscard]] auto empty() const -> bool
925 {
926 return query().empty();
927 }
928
929 template <typename Fn>
930 void for_each(Fn&& fn) const
931 {
932 query().for_each(std::forward<Fn>(fn));
933 }
934
935 template <typename Predicate, typename Fn>
936 void apply(Predicate&& pred, Fn&& fn) const
937 {
938 query().filter(std::forward<Predicate>(pred)).for_each(std::forward<Fn>(fn));
939 }
940
941 template <typename Fn>
942 [[nodiscard]] auto map(Fn&& fn) const -> std::vector<std::invoke_result_t<Fn, RegisteredThreadInfo const&>>
943 {
944 return query().map(std::forward<Fn>(fn));
945 }
946
947 template <typename Predicate>
948 [[nodiscard]] auto find_if(Predicate&& pred) const -> std::optional<RegisteredThreadInfo>
949 {
950 return query().find_if(std::forward<Predicate>(pred));
951 }
952
953 template <typename Predicate>
954 [[nodiscard]] auto any(Predicate&& pred) const -> bool
955 {
956 return query().any(std::forward<Predicate>(pred));
957 }
958
959 template <typename Predicate>
960 [[nodiscard]] auto all(Predicate&& pred) const -> bool
961 {
962 return query().all(std::forward<Predicate>(pred));
963 }
964
965 template <typename Predicate>
966 [[nodiscard]] auto none(Predicate&& pred) const -> bool
967 {
968 return query().none(std::forward<Predicate>(pred));
969 }
970
971 [[nodiscard]] auto take(size_t n) const -> ThreadRegistry::QueryView
972 {
973 return query().take(n);
974 }
975
976 [[nodiscard]] auto skip(size_t n) const -> ThreadRegistry::QueryView
977 {
978 return query().skip(n);
979 }
980
981 private:
982 mutable std::mutex mutex_;
983 std::vector<ThreadRegistry*> registries_;
984};
985
1026class AutoRegisterCurrentThread
1027{
1028 public:
1029 explicit AutoRegisterCurrentThread(std::string const& name = std::string(),
1030 std::string const& componentTag = std::string())
1031 : active_(true), externalReg_(nullptr)
1032 {
1033 auto block = ThreadControlBlock::create_for_current_thread();
1034 (void)block->set_name(name);
1035 registry().register_current_thread(block, name, componentTag);
1036 }
1037
1038 explicit AutoRegisterCurrentThread(ThreadRegistry& reg, std::string const& name = std::string(),
1039 std::string const& componentTag = std::string())
1040 : active_(true), externalReg_(&reg)
1041 {
1042 auto block = ThreadControlBlock::create_for_current_thread();
1043 (void)block->set_name(name);
1044 externalReg_->register_current_thread(block, name, componentTag);
1045 }
1046 ~AutoRegisterCurrentThread()
1047 {
1048 if (active_)
1049 {
1050 if (externalReg_ != nullptr)
1051 externalReg_->unregister_current_thread();
1052 else
1053 registry().unregister_current_thread();
1054 }
1055 }
1056 AutoRegisterCurrentThread(AutoRegisterCurrentThread const&) = delete;
1057 auto operator=(AutoRegisterCurrentThread const&) -> AutoRegisterCurrentThread& = delete;
1058 AutoRegisterCurrentThread(AutoRegisterCurrentThread&& other) noexcept
1059 : active_(other.active_), externalReg_(other.externalReg_)
1060 {
1061 other.active_ = false;
1062 other.externalReg_ = nullptr;
1063 }
1064 auto operator=(AutoRegisterCurrentThread&& other) noexcept -> AutoRegisterCurrentThread&
1065 {
1066 if (this != &other)
1067 {
1068 if (active_)
1069 {
1070 if (externalReg_ != nullptr)
1071 externalReg_->unregister_current_thread();
1072 else
1073 registry().unregister_current_thread();
1074 }
1075 active_ = other.active_;
1076 externalReg_ = other.externalReg_;
1077 other.active_ = false;
1078 other.externalReg_ = nullptr;
1079 }
1080 return *this;
1081 }
1082
1083 private:
1084 bool active_;
1085 ThreadRegistry* externalReg_;
1086};
1087
1088} // namespace threadschedule
1089
1090#ifndef _WIN32
1091namespace threadschedule
1092{
1116inline auto cgroup_attach_tid(std::string const& cgroupDir, Tid tid) -> expected<void, std::error_code>
1117{
1118 std::vector<std::string> candidates = {"cgroup.threads", "tasks", "cgroup.procs"};
1119 for (auto const& file : candidates)
1120 {
1121 std::string path = cgroupDir + "/" + file;
1122 std::ofstream out(path);
1123 if (!out)
1124 continue;
1125 out << tid;
1126 out.flush();
1127 if (out)
1128 return {};
1129 }
1130 return unexpected(std::make_error_code(std::errc::operation_not_permitted));
1131}
1132} // namespace threadschedule
1133#endif
Aggregates multiple ThreadRegistry instances into a single queryable view.
Manages a set of CPU indices to which a thread may be bound.
Value-semantic wrapper for a thread scheduling priority.
Lazy, functional-style query/filter view over a snapshot of registered threads.
Central registry of threads indexed by OS-level thread ID (Tid).
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.
Snapshot of metadata for a single registered thread.