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
47{
48 Tid tid{};
49 std::thread::id stdId;
50 std::string name;
51 std::string componentTag;
52 bool alive{true};
53 std::shared_ptr<class ThreadControlBlock> control;
54};
55
56class ThreadControlBlock
57{
58 public:
59 ThreadControlBlock() = default;
60 ThreadControlBlock(ThreadControlBlock const&) = delete;
61 auto operator=(ThreadControlBlock const&) -> ThreadControlBlock& = delete;
62 ThreadControlBlock(ThreadControlBlock&&) = delete;
63 auto operator=(ThreadControlBlock&&) -> ThreadControlBlock& = delete;
64
65 ~ThreadControlBlock()
66 {
67#ifdef _WIN32
68 if (handle_)
69 {
70 CloseHandle(handle_);
71 handle_ = nullptr;
72 }
73#endif
74 }
75
76 [[nodiscard]] auto tid() const noexcept -> Tid
77 {
78 return tid_;
79 }
80 [[nodiscard]] auto std_id() const noexcept -> std::thread::id
81 {
82 return stdId_;
83 }
84 // Removed name/component metadata from control block; metadata lives in RegisteredThreadInfo
85
86 [[nodiscard]] auto set_affinity(ThreadAffinity const& affinity) const -> expected<void, std::error_code>
87 {
88#ifdef _WIN32
89 if (!handle_)
90 return unexpected(std::make_error_code(std::errc::no_such_process));
91 using SetThreadGroupAffinityFn = BOOL(WINAPI*)(HANDLE, const GROUP_AFFINITY*, PGROUP_AFFINITY);
92 HMODULE hMod = GetModuleHandleW(L"kernel32.dll");
93 if (hMod)
94 {
95 auto set_group_affinity = reinterpret_cast<SetThreadGroupAffinityFn>(
96 reinterpret_cast<void*>(GetProcAddress(hMod, "SetThreadGroupAffinity")));
97 if (set_group_affinity && affinity.has_any())
98 {
99 GROUP_AFFINITY ga{};
100 ga.Mask = static_cast<KAFFINITY>(affinity.get_mask());
101 ga.Group = affinity.get_group();
102 if (set_group_affinity(handle_, &ga, nullptr) != 0)
103 return {};
104 return unexpected(std::make_error_code(std::errc::operation_not_permitted));
105 }
106 }
107 DWORD_PTR mask = static_cast<DWORD_PTR>(affinity.get_mask());
108 if (SetThreadAffinityMask(handle_, mask) != 0)
109 return {};
110 return unexpected(std::make_error_code(std::errc::operation_not_permitted));
111#else
112 if (pthread_setaffinity_np(pthreadHandle_, sizeof(cpu_set_t), &affinity.native_handle()) == 0)
113 return {};
114 return unexpected(std::error_code(errno, std::generic_category()));
115#endif
116 }
117
118 [[nodiscard]] auto set_priority(ThreadPriority priority) const -> expected<void, std::error_code>
119 {
120#ifdef _WIN32
121 if (!handle_)
122 return unexpected(std::make_error_code(std::errc::no_such_process));
123 int win_priority;
124 int prio_val = priority.value();
125 if (prio_val <= -10)
126 win_priority = THREAD_PRIORITY_IDLE;
127 else if (prio_val <= -5)
128 win_priority = THREAD_PRIORITY_LOWEST;
129 else if (prio_val < 0)
130 win_priority = THREAD_PRIORITY_BELOW_NORMAL;
131 else if (prio_val == 0)
132 win_priority = THREAD_PRIORITY_NORMAL;
133 else if (prio_val <= 5)
134 win_priority = THREAD_PRIORITY_ABOVE_NORMAL;
135 else if (prio_val <= 10)
136 win_priority = THREAD_PRIORITY_HIGHEST;
137 else
138 win_priority = THREAD_PRIORITY_TIME_CRITICAL;
139 if (SetThreadPriority(handle_, win_priority) != 0)
140 return {};
141 return unexpected(std::make_error_code(std::errc::operation_not_permitted));
142#else
143 const int policy = SCHED_OTHER;
144 auto params_result = SchedulerParams::create_for_policy(SchedulingPolicy::OTHER, priority);
145 if (!params_result.has_value())
146 return unexpected(params_result.error());
147 if (pthread_setschedparam(pthreadHandle_, policy, &params_result.value()) == 0)
148 return {};
149 return unexpected(std::error_code(errno, std::generic_category()));
150#endif
151 }
152
153 [[nodiscard]] auto set_scheduling_policy(SchedulingPolicy policy, ThreadPriority priority) const
155 {
156#ifdef _WIN32
157 return set_priority(priority);
158#else
159 const int policy_int = static_cast<int>(policy);
160 auto params_result = SchedulerParams::create_for_policy(policy, priority);
161 if (!params_result.has_value())
162 return unexpected(params_result.error());
163 if (pthread_setschedparam(pthreadHandle_, policy_int, &params_result.value()) == 0)
164 return {};
165 return unexpected(std::error_code(errno, std::generic_category()));
166#endif
167 }
168
169 [[nodiscard]] auto set_name(std::string const& name) const -> expected<void, std::error_code>
170 {
171#ifdef _WIN32
172 if (!handle_)
173 return unexpected(std::make_error_code(std::errc::no_such_process));
174 using SetThreadDescriptionFn = HRESULT(WINAPI*)(HANDLE, PCWSTR);
175 HMODULE hMod = GetModuleHandleW(L"kernel32.dll");
176 if (!hMod)
177 return unexpected(std::make_error_code(std::errc::function_not_supported));
178 auto set_desc = reinterpret_cast<SetThreadDescriptionFn>(
179 reinterpret_cast<void*>(GetProcAddress(hMod, "SetThreadDescription")));
180 if (!set_desc)
181 return unexpected(std::make_error_code(std::errc::function_not_supported));
182 std::wstring wide(name.begin(), name.end());
183 if (SUCCEEDED(set_desc(handle_, wide.c_str())))
184 return {};
185 return unexpected(std::make_error_code(std::errc::operation_not_permitted));
186#else
187 if (name.length() > 15)
188 return unexpected(std::make_error_code(std::errc::invalid_argument));
189 if (pthread_setname_np(pthreadHandle_, name.c_str()) == 0)
190 return {};
191 return unexpected(std::error_code(errno, std::generic_category()));
192#endif
193 }
194
195 static auto create_for_current_thread() -> std::shared_ptr<ThreadControlBlock>
196 {
197 auto block = std::make_shared<ThreadControlBlock>();
198 block->tid_ = ThreadInfo::get_thread_id();
199 block->stdId_ = std::this_thread::get_id();
200#ifdef _WIN32
201 HANDLE realHandle = nullptr;
202 DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &realHandle,
203 THREAD_SET_INFORMATION | THREAD_QUERY_INFORMATION, FALSE, 0);
204 block->handle_ = realHandle;
205#else
206 block->pthreadHandle_ = pthread_self();
207#endif
208 return block;
209 }
210
211 private:
212 Tid tid_{};
213 std::thread::id stdId_;
214#ifdef _WIN32
215 HANDLE handle_ = nullptr;
216#else
217 pthread_t pthreadHandle_{};
218#endif
219};
220
221class ThreadRegistry
222{
223 public:
224 ThreadRegistry() = default;
225 ThreadRegistry(ThreadRegistry const&) = delete;
226 auto operator=(ThreadRegistry const&) -> ThreadRegistry& = delete;
227
228 // Register/unregister the CURRENT thread (to be called inside the running thread)
229 void register_current_thread(std::string name = std::string(), std::string componentTag = std::string())
230 {
231 Tid const tid = ThreadInfo::get_thread_id();
233 info.tid = tid;
234 info.stdId = std::this_thread::get_id();
235 info.name = std::move(name);
236 info.componentTag = std::move(componentTag);
237 info.alive = true;
238
239 {
240 std::unique_lock<std::shared_mutex> lock(mutex_);
241 auto it = threads_.find(tid);
242 if (it == threads_.end())
243 {
244 auto stored = info; // copy for callback
245 threads_.emplace(tid, std::move(info));
246 if (onRegister_)
247 {
248 auto cb = onRegister_;
249 lock.unlock();
250 cb(stored);
251 }
252 }
253 else
254 {
255 // Duplicate registration of the same TID is a no-op (first registration wins)
256 }
257 }
258 }
259
260 void register_current_thread(std::shared_ptr<ThreadControlBlock> const& controlBlock,
261 std::string name = std::string(), std::string componentTag = std::string())
262 {
263 if (!controlBlock)
264 return;
266 info.tid = controlBlock->tid();
267 info.stdId = controlBlock->std_id();
268 info.name = std::move(name);
269 info.componentTag = std::move(componentTag);
270 info.alive = true;
271 info.control = controlBlock;
272 std::unique_lock<std::shared_mutex> lock(mutex_);
273 auto it = threads_.find(info.tid);
274 if (it == threads_.end())
275 {
276 auto stored = info; // copy for callback
277 threads_.emplace(info.tid, std::move(info));
278 if (onRegister_)
279 {
280 auto cb = onRegister_;
281 lock.unlock();
282 cb(stored);
283 }
284 }
285 else
286 {
287 // Duplicate registration of the same TID is a no-op (first registration wins)
288 }
289 }
290
291 void unregister_current_thread()
292 {
293 Tid const tid = ThreadInfo::get_thread_id();
294 std::unique_lock<std::shared_mutex> lock(mutex_);
295 auto it = threads_.find(tid);
296 if (it != threads_.end())
297 {
298 it->second.alive = false;
299 auto info = it->second;
300 threads_.erase(it);
301 if (onUnregister_)
302 {
303 auto cb = onUnregister_;
304 lock.unlock();
305 cb(info);
306 }
307 }
308 }
309
310 // Lookup
311 [[nodiscard]] auto get(Tid tid) const -> std::optional<RegisteredThreadInfo>
312 {
313 std::shared_lock<std::shared_mutex> lock(mutex_);
314 auto it = threads_.find(tid);
315 if (it == threads_.end())
316 return std::nullopt;
317 return it->second;
318 }
319
320 // Chainable query API
321 class QueryView
322 {
323 public:
324 explicit QueryView(std::vector<RegisteredThreadInfo> entries) : entries_(std::move(entries))
325 {
326 }
327
328 template <typename Predicate>
329 auto filter(Predicate&& pred) const -> QueryView
330 {
331 std::vector<RegisteredThreadInfo> filtered;
332 filtered.reserve(entries_.size());
333 for (auto const& entry : entries_)
334 {
335 if (pred(entry))
336 filtered.push_back(entry);
337 }
338 return QueryView(std::move(filtered));
339 }
340
341 template <typename Fn>
342 void for_each(Fn&& fn) const
343 {
344 for (auto const& entry : entries_)
345 {
346 fn(entry);
347 }
348 }
349
350 [[nodiscard]] auto count() const -> size_t
351 {
352 return entries_.size();
353 }
354
355 [[nodiscard]] auto empty() const -> bool
356 {
357 return entries_.empty();
358 }
359
360 [[nodiscard]] auto entries() const -> std::vector<RegisteredThreadInfo> const&
361 {
362 return entries_;
363 }
364
365 // Transform entries to a vector of another type
366 template <typename Fn>
367 [[nodiscard]] auto map(Fn&& fn) const -> std::vector<std::invoke_result_t<Fn, RegisteredThreadInfo const&>>
368 {
369 std::vector<std::invoke_result_t<Fn, RegisteredThreadInfo const&>> result;
370 result.reserve(entries_.size());
371 for (auto const& entry : entries_)
372 {
373 result.push_back(fn(entry));
374 }
375 return result;
376 }
377
378 // Find first entry matching predicate
379 template <typename Predicate>
380 [[nodiscard]] auto find_if(Predicate&& pred) const -> std::optional<RegisteredThreadInfo>
381 {
382 for (auto const& entry : entries_)
383 {
384 if (pred(entry))
385 return entry;
386 }
387 return std::nullopt;
388 }
389
390 template <typename Predicate>
391 [[nodiscard]] auto any(Predicate&& pred) const -> bool
392 {
393 for (auto const& entry : entries_)
394 {
395 if (pred(entry))
396 return true;
397 }
398 return false;
399 }
400
401 template <typename Predicate>
402 [[nodiscard]] auto all(Predicate&& pred) const -> bool
403 {
404 for (auto const& entry : entries_)
405 {
406 if (!pred(entry))
407 return false;
408 }
409 return true;
410 }
411
412 template <typename Predicate>
413 [[nodiscard]] auto none(Predicate&& pred) const -> bool
414 {
415 return !any(std::forward<Predicate>(pred));
416 }
417
418 [[nodiscard]] auto take(size_t n) const -> QueryView
419 {
420 auto result = entries_;
421 if (result.size() > n)
422 result.resize(n);
423 return QueryView(std::move(result));
424 }
425
426 [[nodiscard]] auto skip(size_t n) const -> QueryView
427 {
428 std::vector<RegisteredThreadInfo> result;
429 if (n < entries_.size())
430 {
431 result.assign(entries_.begin() + n, entries_.end());
432 }
433 return QueryView(std::move(result));
434 }
435
436 private:
437 std::vector<RegisteredThreadInfo> entries_;
438 };
439
440 // Create a query view over all registered threads
441 [[nodiscard]] auto query() const -> QueryView
442 {
443 std::vector<RegisteredThreadInfo> snapshot;
444 std::shared_lock<std::shared_mutex> lock(mutex_);
445 snapshot.reserve(threads_.size());
446 for (auto const& kv : threads_)
447 {
448 snapshot.push_back(kv.second);
449 }
450 return QueryView(std::move(snapshot));
451 }
452
453 template <typename Predicate>
454 [[nodiscard]] auto filter(Predicate&& pred) const -> QueryView
455 {
456 return query().filter(std::forward<Predicate>(pred));
457 }
458
459 [[nodiscard]] auto count() const -> size_t
460 {
461 return query().count();
462 }
463
464 [[nodiscard]] auto empty() const -> bool
465 {
466 return query().empty();
467 }
468
469 template <typename Fn>
470 void for_each(Fn&& fn) const
471 {
472 query().for_each(std::forward<Fn>(fn));
473 }
474
475 template <typename Predicate, typename Fn>
476 void apply(Predicate&& pred, Fn&& fn) const
477 {
478 query().filter(std::forward<Predicate>(pred)).for_each(std::forward<Fn>(fn));
479 }
480
481 template <typename Fn>
482 [[nodiscard]] auto map(Fn&& fn) const -> std::vector<std::invoke_result_t<Fn, RegisteredThreadInfo const&>>
483 {
484 return query().map(std::forward<Fn>(fn));
485 }
486
487 template <typename Predicate>
488 [[nodiscard]] auto find_if(Predicate&& pred) const -> std::optional<RegisteredThreadInfo>
489 {
490 return query().find_if(std::forward<Predicate>(pred));
491 }
492
493 template <typename Predicate>
494 [[nodiscard]] auto any(Predicate&& pred) const -> bool
495 {
496 return query().any(std::forward<Predicate>(pred));
497 }
498
499 template <typename Predicate>
500 [[nodiscard]] auto all(Predicate&& pred) const -> bool
501 {
502 return query().all(std::forward<Predicate>(pred));
503 }
504
505 template <typename Predicate>
506 [[nodiscard]] auto none(Predicate&& pred) const -> bool
507 {
508 return query().none(std::forward<Predicate>(pred));
509 }
510
511 [[nodiscard]] auto take(size_t n) const -> QueryView
512 {
513 return query().take(n);
514 }
515
516 [[nodiscard]] auto skip(size_t n) const -> QueryView
517 {
518 return query().skip(n);
519 }
520
521 [[nodiscard]] auto set_affinity(Tid tid, ThreadAffinity const& affinity) const -> expected<void, std::error_code>
522 {
523 auto blk = lock_block(tid);
524 if (!blk)
525 return unexpected(std::make_error_code(std::errc::no_such_process));
526 return blk->set_affinity(affinity);
527 }
528
529 [[nodiscard]] auto set_priority(Tid tid, ThreadPriority priority) const -> expected<void, std::error_code>
530 {
531 auto blk = lock_block(tid);
532 if (!blk)
533 return unexpected(std::make_error_code(std::errc::no_such_process));
534 return blk->set_priority(priority);
535 }
536
537 [[nodiscard]] auto set_scheduling_policy(Tid tid, SchedulingPolicy policy, ThreadPriority priority) const
538 -> expected<void, std::error_code>
539 {
540 auto blk = lock_block(tid);
541 if (!blk)
542 return unexpected(std::make_error_code(std::errc::no_such_process));
543 return blk->set_scheduling_policy(policy, priority);
544 }
545
546 [[nodiscard]] auto set_name(Tid tid, std::string const& name) const -> expected<void, std::error_code>
547 {
548 auto blk = lock_block(tid);
549 if (!blk)
550 return unexpected(std::make_error_code(std::errc::no_such_process));
551 return blk->set_name(name);
552 }
553
554 // Register/unregister hooks (system integration)
555 void set_on_register(std::function<void(RegisteredThreadInfo const&)> cb)
556 {
557 std::unique_lock<std::shared_mutex> lock(mutex_);
558 onRegister_ = std::move(cb);
559 }
560
561 void set_on_unregister(std::function<void(RegisteredThreadInfo const&)> cb)
562 {
563 std::unique_lock<std::shared_mutex> lock(mutex_);
564 onUnregister_ = std::move(cb);
565 }
566
567 private:
568 [[nodiscard]] auto lock_block(Tid tid) const -> std::shared_ptr<ThreadControlBlock>
569 {
570 std::shared_lock<std::shared_mutex> lock(mutex_);
571 auto it = threads_.find(tid);
572 if (it == threads_.end())
573 return nullptr;
574 return it->second.control;
575 }
576 mutable std::shared_mutex mutex_;
577 std::unordered_map<Tid, RegisteredThreadInfo> threads_;
578
579 // Integration hooks
580 std::function<void(RegisteredThreadInfo const&)> onRegister_;
581 std::function<void(RegisteredThreadInfo const&)> onUnregister_;
582};
583
584// Registry access methods
585#if defined(THREADSCHEDULE_RUNTIME)
586// Declarations only; implemented in the runtime translation unit
587THREADSCHEDULE_API auto registry() -> ThreadRegistry&;
588THREADSCHEDULE_API void set_external_registry(ThreadRegistry* reg);
589#else
590inline auto registry_storage() -> ThreadRegistry*&
591{
592 static ThreadRegistry* external = nullptr;
593 return external;
594}
595
596inline auto registry() -> ThreadRegistry&
597{
598 ThreadRegistry*& ext = registry_storage();
599 if (ext != nullptr)
600 return *ext;
601 static ThreadRegistry local;
602 return local;
603}
604
605inline void set_external_registry(ThreadRegistry* reg)
606{
607 registry_storage() = reg;
608}
609#endif
610
611// Composite registry to aggregate multiple registries when explicit merging is desired
613{
614 public:
615 void attach(ThreadRegistry* reg)
616 {
617 if (reg == nullptr)
618 return;
619 std::lock_guard<std::mutex> lock(mutex_);
620 registries_.push_back(reg);
621 }
622
623 // Chainable query API
624 [[nodiscard]] auto query() const -> ThreadRegistry::QueryView
625 {
626 std::vector<RegisteredThreadInfo> merged;
627 std::vector<ThreadRegistry*> regs;
628 {
629 std::lock_guard<std::mutex> lock(mutex_);
630 regs = registries_;
631 }
632 for (auto* r : regs)
633 {
634 auto view = r->query();
635 auto const& entries = view.entries();
636 merged.insert(merged.end(), entries.begin(), entries.end());
637 }
638 return ThreadRegistry::QueryView(std::move(merged));
639 }
640
641 template <typename Predicate>
642 [[nodiscard]] auto filter(Predicate&& pred) const -> ThreadRegistry::QueryView
643 {
644 return query().filter(std::forward<Predicate>(pred));
645 }
646
647 [[nodiscard]] auto count() const -> size_t
648 {
649 return query().count();
650 }
651
652 [[nodiscard]] auto empty() const -> bool
653 {
654 return query().empty();
655 }
656
657 template <typename Fn>
658 void for_each(Fn&& fn) const
659 {
660 query().for_each(std::forward<Fn>(fn));
661 }
662
663 template <typename Predicate, typename Fn>
664 void apply(Predicate&& pred, Fn&& fn) const
665 {
666 query().filter(std::forward<Predicate>(pred)).for_each(std::forward<Fn>(fn));
667 }
668
669 template <typename Fn>
670 [[nodiscard]] auto map(Fn&& fn) const -> std::vector<std::invoke_result_t<Fn, RegisteredThreadInfo const&>>
671 {
672 return query().map(std::forward<Fn>(fn));
673 }
674
675 template <typename Predicate>
676 [[nodiscard]] auto find_if(Predicate&& pred) const -> std::optional<RegisteredThreadInfo>
677 {
678 return query().find_if(std::forward<Predicate>(pred));
679 }
680
681 template <typename Predicate>
682 [[nodiscard]] auto any(Predicate&& pred) const -> bool
683 {
684 return query().any(std::forward<Predicate>(pred));
685 }
686
687 template <typename Predicate>
688 [[nodiscard]] auto all(Predicate&& pred) const -> bool
689 {
690 return query().all(std::forward<Predicate>(pred));
691 }
692
693 template <typename Predicate>
694 [[nodiscard]] auto none(Predicate&& pred) const -> bool
695 {
696 return query().none(std::forward<Predicate>(pred));
697 }
698
699 [[nodiscard]] auto take(size_t n) const -> ThreadRegistry::QueryView
700 {
701 return query().take(n);
702 }
703
704 [[nodiscard]] auto skip(size_t n) const -> ThreadRegistry::QueryView
705 {
706 return query().skip(n);
707 }
708
709 private:
710 mutable std::mutex mutex_;
711 std::vector<ThreadRegistry*> registries_;
712};
713
714// RAII helper to auto-register the current thread
715class AutoRegisterCurrentThread
716{
717 public:
718 explicit AutoRegisterCurrentThread(std::string const& name = std::string(),
719 std::string const& componentTag = std::string())
720 : active_(true), externalReg_(nullptr)
721 {
722 auto block = ThreadControlBlock::create_for_current_thread();
723 (void)block->set_name(name);
724 registry().register_current_thread(block, name, componentTag);
725 }
726
727 explicit AutoRegisterCurrentThread(ThreadRegistry& reg, std::string const& name = std::string(),
728 std::string const& componentTag = std::string())
729 : active_(true), externalReg_(&reg)
730 {
731 auto block = ThreadControlBlock::create_for_current_thread();
732 (void)block->set_name(name);
733 externalReg_->register_current_thread(block, name, componentTag);
734 }
735 ~AutoRegisterCurrentThread()
736 {
737 if (active_)
738 {
739 if (externalReg_ != nullptr)
740 externalReg_->unregister_current_thread();
741 else
742 registry().unregister_current_thread();
743 }
744 }
745 AutoRegisterCurrentThread(AutoRegisterCurrentThread const&) = delete;
746 auto operator=(AutoRegisterCurrentThread const&) -> AutoRegisterCurrentThread& = delete;
747 AutoRegisterCurrentThread(AutoRegisterCurrentThread&& other) noexcept : active_(other.active_)
748 {
749 other.active_ = false;
750 }
751 auto operator=(AutoRegisterCurrentThread&& other) noexcept -> AutoRegisterCurrentThread&
752 {
753 if (this != &other)
754 {
755 active_ = other.active_;
756 other.active_ = false;
757 }
758 return *this;
759 }
760
761 private:
762 bool active_;
763 ThreadRegistry* externalReg_;
764};
765
766} // namespace threadschedule
767
768#ifndef _WIN32
769// Helper: attach a TID to a cgroup directory (cgroup v2 tries cgroup.threads, then tasks, then cgroup.procs)
770namespace threadschedule
771{
772inline auto cgroup_attach_tid(std::string const& cgroupDir, Tid tid) -> expected<void, std::error_code>
773{
774 std::vector<std::string> candidates = {"cgroup.threads", "tasks", "cgroup.procs"};
775 for (auto const& file : candidates)
776 {
777 std::string path = cgroupDir + "/" + file;
778 std::ofstream out(path);
779 if (!out)
780 continue;
781 out << tid;
782 out.flush();
783 if (out)
784 return {};
785 }
786 return unexpected(std::make_error_code(std::errc::operation_not_permitted));
787}
788} // namespace threadschedule
789#endif
Thread priority wrapper with validation.