ThreadSchedule 1.0.0
Modern C++ thread management library
Loading...
Searching...
No Matches
thread_wrapper.hpp
1#pragma once
2
3#include "expected.hpp"
4#include "scheduler_policy.hpp"
5#include <optional>
6#include <string>
7#include <thread>
8
9#ifdef _WIN32
10#include <libloaderapi.h>
11#include <windows.h>
12#else
13#include <dirent.h>
14#include <fstream>
15#include <sys/prctl.h>
16#include <sys/resource.h>
17#include <sys/syscall.h>
18#include <unistd.h>
19#endif
20
21namespace threadschedule
22{
23
24namespace detail
25{
27{
28};
30{
31};
32
33template <typename ThreadType, typename OwnershipTag>
35
36// Owning storage: no extra overhead
37template <typename ThreadType>
38class ThreadStorage<ThreadType, OwningTag>
39{
40 protected:
41 ThreadStorage() = default;
42
43 [[nodiscard]] auto underlying() noexcept -> ThreadType&
44 {
45 return thread_;
46 }
47 [[nodiscard]] auto underlying() const noexcept -> ThreadType const&
48 {
49 return thread_;
50 }
51
52 ThreadType thread_;
53};
54
55// Non-owning storage: reference to external thread
56template <typename ThreadType>
57class ThreadStorage<ThreadType, NonOwningTag>
58{
59 protected:
60 ThreadStorage() = default;
61 explicit ThreadStorage(ThreadType& t) : external_thread_(&t)
62 {
63 }
64
65 [[nodiscard]] auto underlying() noexcept -> ThreadType&
66 {
67 return *external_thread_;
68 }
69 [[nodiscard]] auto underlying() const noexcept -> ThreadType const&
70 {
71 return *external_thread_;
72 }
73
74 ThreadType* external_thread_ = nullptr; // non-owning
75};
76} // namespace detail
77
81template <typename ThreadType, typename OwnershipTag = detail::OwningTag>
82class BaseThreadWrapper : protected detail::ThreadStorage<ThreadType, OwnershipTag>
83{
84 public:
85 using native_handle_type = typename ThreadType::native_handle_type;
86 using id = typename ThreadType::id;
87
88 BaseThreadWrapper() = default;
89 explicit BaseThreadWrapper(ThreadType& t) : detail::ThreadStorage<ThreadType, OwnershipTag>(t)
90 {
91 }
92 virtual ~BaseThreadWrapper() = default;
93
94 // Thread management
95 void join()
96 {
97 if (underlying().joinable())
98 {
99 underlying().join();
100 }
101 }
102
103 void detach()
104 {
105 if (underlying().joinable())
106 {
107 underlying().detach();
108 }
109 }
110
111 [[nodiscard]] auto joinable() const noexcept -> bool
112 {
113 return underlying().joinable();
114 }
115 [[nodiscard]] auto get_id() const noexcept -> id
116 {
117 return underlying().get_id();
118 }
119 [[nodiscard]] auto native_handle() noexcept -> native_handle_type
120 {
121 return underlying().native_handle();
122 }
123
124 // Extended functionality
125 [[nodiscard]] auto set_name(std::string const& name) -> expected<void, std::error_code>
126 {
127#ifdef _WIN32
128 // Windows supports longer thread names. Try SetThreadDescription dynamically.
129 auto const handle = native_handle();
130 std::wstring wide_name(name.begin(), name.end());
131
132 using SetThreadDescriptionFn = HRESULT(WINAPI*)(HANDLE, PCWSTR);
133 HMODULE hMod = GetModuleHandleW(L"kernel32.dll");
134 if (hMod)
135 {
136 auto set_desc = reinterpret_cast<SetThreadDescriptionFn>(
137 reinterpret_cast<void*>(GetProcAddress(hMod, "SetThreadDescription")));
138 if (set_desc)
139 {
140 if (SUCCEEDED(set_desc(handle, wide_name.c_str())))
142 return expected<void, std::error_code>(unexpect, std::make_error_code(std::errc::invalid_argument));
143 }
144 }
145 // Fallback unavailable
146 return expected<void, std::error_code>(unexpect, std::make_error_code(std::errc::function_not_supported));
147#else
148 if (name.length() > 15)
149 return expected<void, std::error_code>(unexpect, std::make_error_code(std::errc::invalid_argument));
150
151 auto const handle = native_handle();
152 if (pthread_setname_np(handle, name.c_str()) == 0)
153 return {};
154 return expected<void, std::error_code>(unexpect, std::error_code(errno, std::generic_category()));
155#endif
156 }
157
158 [[nodiscard]] auto get_name() const -> std::optional<std::string>
159 {
160#ifdef _WIN32
161 const auto handle = const_cast<BaseThreadWrapper*>(this)->native_handle();
162 using GetThreadDescriptionFn = HRESULT(WINAPI*)(HANDLE, PWSTR*);
163 HMODULE hMod = GetModuleHandleW(L"kernel32.dll");
164 if (hMod)
165 {
166 auto get_desc = reinterpret_cast<GetThreadDescriptionFn>(
167 reinterpret_cast<void*>(GetProcAddress(hMod, "GetThreadDescription")));
168 if (get_desc)
169 {
170 PWSTR thread_name = nullptr;
171 HRESULT hr = get_desc(handle, &thread_name);
172 if (SUCCEEDED(hr) && thread_name)
173 {
174 int size = WideCharToMultiByte(CP_UTF8, 0, thread_name, -1, nullptr, 0, nullptr, nullptr);
175 if (size > 0)
176 {
177 std::string result(size - 1, '\0');
178 WideCharToMultiByte(CP_UTF8, 0, thread_name, -1, &result[0], size, nullptr, nullptr);
179 LocalFree(thread_name);
180 return result;
181 }
182 LocalFree(thread_name);
183 }
184 }
185 }
186 return std::nullopt;
187#else
188 char name[16]; // Linux limit + 1
189 auto const handle = const_cast<BaseThreadWrapper*>(this)->native_handle();
190
191 if (pthread_getname_np(handle, name, sizeof(name)) == 0)
192 {
193 return std::string(name);
194 }
195 return std::nullopt;
196#endif
197 }
198
199 [[nodiscard]] auto set_priority(ThreadPriority priority) -> expected<void, std::error_code>
200 {
201#ifdef _WIN32
202 const auto handle = native_handle();
203 // Map ThreadPriority to Windows priority
204 // Windows thread priorities range from -15 (THREAD_PRIORITY_IDLE) to +15 (THREAD_PRIORITY_TIME_CRITICAL)
205 // We'll map the priority value to Windows constants
206 int win_priority;
207 int prio_val = priority.value();
208
209 if (prio_val <= -10)
210 {
211 win_priority = THREAD_PRIORITY_IDLE;
212 }
213 else if (prio_val <= -5)
214 {
215 win_priority = THREAD_PRIORITY_LOWEST;
216 }
217 else if (prio_val < 0)
218 {
219 win_priority = THREAD_PRIORITY_BELOW_NORMAL;
220 }
221 else if (prio_val == 0)
222 {
223 win_priority = THREAD_PRIORITY_NORMAL;
224 }
225 else if (prio_val <= 5)
226 {
227 win_priority = THREAD_PRIORITY_ABOVE_NORMAL;
228 }
229 else if (prio_val <= 10)
230 {
231 win_priority = THREAD_PRIORITY_HIGHEST;
232 }
233 else
234 {
235 win_priority = THREAD_PRIORITY_TIME_CRITICAL;
236 }
237
238 if (SetThreadPriority(handle, win_priority) != 0)
239 return {};
240 return unexpected(std::make_error_code(std::errc::operation_not_permitted));
241#else
242 const auto handle = native_handle();
243 int const policy = SCHED_OTHER;
244
245 auto params_result = SchedulerParams::create_for_policy(SchedulingPolicy::OTHER, priority);
246
247 if (!params_result.has_value())
248 {
249 return unexpected(params_result.error());
250 }
251
252 if (pthread_setschedparam(handle, policy, &params_result.value()) == 0)
253 return {};
254 return unexpected(std::error_code(errno, std::generic_category()));
255#endif
256 }
257
258 [[nodiscard]] auto set_scheduling_policy(SchedulingPolicy policy, ThreadPriority priority)
260 {
261#ifdef _WIN32
262 // Windows doesn't have the same scheduling policy concept as Linux
263 // We'll just set the priority and return success
264 return set_priority(priority);
265#else
266 const auto handle = native_handle();
267 int const policy_int = static_cast<int>(policy);
268
269 auto params_result = SchedulerParams::create_for_policy(policy, priority);
270 if (!params_result.has_value())
271 {
272 return unexpected(params_result.error());
273 }
274
275 if (pthread_setschedparam(handle, policy_int, &params_result.value()) == 0)
276 return {};
277 return unexpected(std::error_code(errno, std::generic_category()));
278#endif
279 }
280
281 [[nodiscard]] auto set_affinity(ThreadAffinity const& affinity) -> expected<void, std::error_code>
282 {
283#ifdef _WIN32
284 const auto handle = native_handle();
285 // Prefer Group Affinity if available
286 using SetThreadGroupAffinityFn = BOOL(WINAPI*)(HANDLE, const GROUP_AFFINITY*, PGROUP_AFFINITY);
287 HMODULE hMod = GetModuleHandleW(L"kernel32.dll");
288 if (hMod)
289 {
290 auto set_group_affinity = reinterpret_cast<SetThreadGroupAffinityFn>(
291 reinterpret_cast<void*>(GetProcAddress(hMod, "SetThreadGroupAffinity")));
292 if (set_group_affinity && affinity.has_any())
293 {
294 GROUP_AFFINITY ga{};
295 ga.Mask = static_cast<KAFFINITY>(affinity.get_mask());
296 ga.Group = affinity.get_group();
297 if (set_group_affinity(handle, &ga, nullptr) != 0)
298 return {};
299 return unexpected(std::make_error_code(std::errc::operation_not_permitted));
300 }
301 }
302 // Fallback to legacy mask (single-group systems)
303 DWORD_PTR mask = static_cast<DWORD_PTR>(affinity.get_mask());
304 if (SetThreadAffinityMask(handle, mask) != 0)
305 return {};
306 return unexpected(std::make_error_code(std::errc::operation_not_permitted));
307#else
308 const auto handle = native_handle();
309 if (pthread_setaffinity_np(handle, sizeof(cpu_set_t), &affinity.native_handle()) == 0)
310 return {};
311 return unexpected(std::error_code(errno, std::generic_category()));
312#endif
313 }
314
315 [[nodiscard]] auto get_affinity() const -> std::optional<ThreadAffinity>
316 {
317#ifdef _WIN32
318 const auto handle = const_cast<BaseThreadWrapper*>(this)->native_handle();
319 using GetThreadGroupAffinityFn = BOOL(WINAPI*)(HANDLE, PGROUP_AFFINITY);
320 HMODULE hMod = GetModuleHandleW(L"kernel32.dll");
321 if (hMod)
322 {
323 auto get_group_affinity = reinterpret_cast<GetThreadGroupAffinityFn>(
324 reinterpret_cast<void*>(GetProcAddress(hMod, "GetThreadGroupAffinity")));
325 if (get_group_affinity)
326 {
327 GROUP_AFFINITY ga{};
328 if (get_group_affinity(handle, &ga) != 0)
329 {
330 ThreadAffinity affinity;
331 for (int i = 0; i < 64; ++i)
332 {
333 if ((ga.Mask & (static_cast<KAFFINITY>(1) << i)) != 0)
334 {
335 affinity.add_cpu(static_cast<int>(ga.Group) * 64 + i);
336 }
337 }
338 return affinity.has_any() ? affinity : std::nullopt;
339 }
340 }
341 }
342 return std::nullopt;
343#else
344 ThreadAffinity affinity;
345 auto const handle = const_cast<BaseThreadWrapper*>(this)->native_handle();
346
347 if (pthread_getaffinity_np(handle, sizeof(cpu_set_t), &affinity.native_handle()) == 0)
348 {
349 return affinity;
350 }
351 return std::nullopt;
352#endif
353 }
354
355 // Nice value (process-level, affects all threads)
356 static auto set_nice_value(int nice_value) -> bool
357 {
358#ifdef _WIN32
359 // Windows has process priority classes, not nice values
360 // We'll use SetPriorityClass for the process
361 DWORD priority_class;
362 if (nice_value <= -15)
363 {
364 priority_class = HIGH_PRIORITY_CLASS;
365 }
366 else if (nice_value <= -10)
367 {
368 priority_class = ABOVE_NORMAL_PRIORITY_CLASS;
369 }
370 else if (nice_value < 10)
371 {
372 priority_class = NORMAL_PRIORITY_CLASS;
373 }
374 else if (nice_value < 19)
375 {
376 priority_class = BELOW_NORMAL_PRIORITY_CLASS;
377 }
378 else
379 {
380 priority_class = IDLE_PRIORITY_CLASS;
381 }
382 return SetPriorityClass(GetCurrentProcess(), priority_class) != 0;
383#else
384 return setpriority(PRIO_PROCESS, 0, nice_value) == 0;
385#endif
386 }
387
388 static auto get_nice_value() -> std::optional<int>
389 {
390#ifdef _WIN32
391 // Get Windows process priority class and map to nice value
392 DWORD priority_class = GetPriorityClass(GetCurrentProcess());
393 if (priority_class == 0)
394 {
395 return std::nullopt;
396 }
397
398 // Map Windows priority class to nice value
399 switch (priority_class)
400 {
401 case HIGH_PRIORITY_CLASS:
402 return -15;
403 case ABOVE_NORMAL_PRIORITY_CLASS:
404 return -10;
405 case NORMAL_PRIORITY_CLASS:
406 return 0;
407 case BELOW_NORMAL_PRIORITY_CLASS:
408 return 10;
409 case IDLE_PRIORITY_CLASS:
410 return 19;
411 default:
412 return 0;
413 }
414#else
415 errno = 0;
416 int const nice = getpriority(PRIO_PROCESS, 0);
417 if (errno == 0)
418 {
419 return nice;
420 }
421 return std::nullopt;
422#endif
423 }
424
425 protected:
426 using detail::ThreadStorage<ThreadType, OwnershipTag>::underlying;
427 using detail::ThreadStorage<ThreadType, OwnershipTag>::ThreadStorage;
428};
429
433class ThreadWrapper : public BaseThreadWrapper<std::thread, detail::OwningTag>
434{
435 public:
436 ThreadWrapper() = default;
437
438 // Construct by taking ownership of an existing std::thread (move)
439 ThreadWrapper(std::thread&& t) noexcept
440 {
441 this->underlying() = std::move(t);
442 }
443
444 template <typename F, typename... Args>
445 explicit ThreadWrapper(F&& f, Args&&... args) : BaseThreadWrapper()
446 {
447 this->underlying() = std::thread(std::forward<F>(f), std::forward<Args>(args)...);
448 }
449
450 ThreadWrapper(ThreadWrapper const&) = delete;
451 auto operator=(ThreadWrapper const&) -> ThreadWrapper& = delete;
452
453 ThreadWrapper(ThreadWrapper&& other) noexcept
454 {
455 this->underlying() = std::move(other.underlying());
456 }
457
458 auto operator=(ThreadWrapper&& other) noexcept -> ThreadWrapper&
459 {
460 if (this != &other)
461 {
462 if (this->underlying().joinable())
463 {
464 this->underlying().join();
465 }
466 this->underlying() = std::move(other.underlying());
467 }
468 return *this;
469 }
470
471 ~ThreadWrapper() override
472 {
473 if (this->underlying().joinable())
474 {
475 this->underlying().join();
476 }
477 }
478
479 // Ownership transfer to std::thread for APIs that take plain std::thread
480 auto release() noexcept -> std::thread
481 {
482 return std::move(this->underlying());
483 }
484
485 explicit operator std::thread() && noexcept
486 {
487 return std::move(this->underlying());
488 }
489
490 // Factory methods
491 template <typename F, typename... Args>
492 static auto create_with_config(std::string const& name, SchedulingPolicy policy, ThreadPriority priority, F&& f,
493 Args&&... args) -> ThreadWrapper
494 {
495
496 ThreadWrapper wrapper(std::forward<F>(f), std::forward<Args>(args)...);
497 if (auto r = wrapper.set_name(name); !r.has_value())
498 {
499 }
500 if (auto r = wrapper.set_scheduling_policy(policy, priority); !r.has_value())
501 {
502 }
503 return wrapper;
504 }
505};
506
507// Non-owning view over std::thread
508class ThreadWrapperView : public BaseThreadWrapper<std::thread, detail::NonOwningTag>
509{
510 public:
511 ThreadWrapperView(std::thread& t) : BaseThreadWrapper<std::thread, detail::NonOwningTag>(t)
512 {
513 }
514
515 // Non-owning access to the underlying std::thread
516 auto get() noexcept -> std::thread&
517 {
518 return this->underlying();
519 }
520 [[nodiscard]] auto get() const noexcept -> std::thread const&
521 {
522 return this->underlying();
523 }
524};
525
529#if __cplusplus >= 202002L
530class JThreadWrapper : public BaseThreadWrapper<std::jthread, detail::OwningTag>
531{
532 public:
533 JThreadWrapper() = default;
534
535 // Construct by taking ownership of an existing std::jthread (move)
536 JThreadWrapper(std::jthread&& t) noexcept : BaseThreadWrapper()
537 {
538 this->underlying() = std::move(t);
539 }
540
541 // Ownership transfer to std::jthread for APIs that take plain std::jthread
542 auto release() noexcept -> std::jthread
543 {
544 return std::move(this->underlying());
545 }
546
547 explicit operator std::jthread() && noexcept
548 {
549 return std::move(this->underlying());
550 }
551
552 template <typename F, typename... Args>
553 explicit JThreadWrapper(F&& f, Args&&... args) : BaseThreadWrapper()
554 {
555 this->underlying() = std::jthread(std::forward<F>(f), std::forward<Args>(args)...);
556 }
557
558 JThreadWrapper(JThreadWrapper const&) = delete;
559 JThreadWrapper& operator=(JThreadWrapper const&) = delete;
560
561 JThreadWrapper(JThreadWrapper&& other) noexcept : BaseThreadWrapper()
562 {
563 this->underlying() = std::move(other.underlying());
564 }
565
566 JThreadWrapper& operator=(JThreadWrapper&& other) noexcept
567 {
568 if (this != &other)
569 {
570 this->underlying() = std::move(other.underlying());
571 }
572 return *this;
573 }
574
575 // jthread-specific functionality
576 void request_stop()
577 {
578 this->underlying().request_stop();
579 }
580 bool stop_requested()
581 {
582 return this->underlying().get_stop_token().stop_requested();
583 }
584 std::stop_token get_stop_token() const
585 {
586 return this->underlying().get_stop_token();
587 }
588 std::stop_source get_stop_source()
589 {
590 return this->underlying().get_stop_source();
591 }
592
593 // Factory methods
594 template <typename F, typename... Args>
595 static JThreadWrapper create_with_config(std::string const& name, SchedulingPolicy policy, ThreadPriority priority,
596 F&& f, Args&&... args)
597 {
598
599 JThreadWrapper wrapper(std::forward<F>(f), std::forward<Args>(args)...);
600 if (auto r = wrapper.set_name(name); !r.has_value())
601 {
602 }
603 if (auto r = wrapper.set_scheduling_policy(policy, priority); !r.has_value())
604 {
605 }
606 return wrapper;
607 }
608};
609
610// Non-owning view over std::jthread (C++20)
611class JThreadWrapperView : public BaseThreadWrapper<std::jthread, detail::NonOwningTag>
612{
613 public:
614 JThreadWrapperView(std::jthread& t) : BaseThreadWrapper<std::jthread, detail::NonOwningTag>(t)
615 {
616 }
617
618 void request_stop()
619 {
620 this->underlying().request_stop();
621 }
622 bool stop_requested()
623 {
624 return this->underlying().get_stop_token().stop_requested();
625 }
626 std::stop_token get_stop_token() const
627 {
628 return this->underlying().get_stop_token();
629 }
630 std::stop_source get_stop_source()
631 {
632 return this->underlying().get_stop_source();
633 }
634
635 // Non-owning access to the underlying std::jthread
636 auto get() noexcept -> std::jthread&
637 {
638 return this->underlying();
639 }
640 [[nodiscard]] auto get() const noexcept -> std::jthread const&
641 {
642 return this->underlying();
643 }
644};
645#else
646// Fallback for compilers without C++20 support
647using JThreadWrapper = ThreadWrapper;
648using JThreadWrapperView = ThreadWrapperView;
649#endif
650
651class ThreadByNameView
652{
653 public:
654#ifdef _WIN32
655 using native_handle_type = void*; // unsupported placeholder
656#else
657 using native_handle_type = pid_t; // Linux TID
658#endif
659
660 explicit ThreadByNameView(const std::string& name)
661 {
662#ifdef _WIN32
663 // Not supported on Windows in this implementation
664 (void)name;
665#else
666 DIR* dir = opendir("/proc/self/task");
667 if (dir == nullptr)
668 return;
669 struct dirent* entry = nullptr;
670 while ((entry = readdir(dir)) != nullptr)
671 {
672 if (entry->d_name[0] == '.')
673 continue;
674 std::string tid_str(entry->d_name);
675 std::string path = std::string("/proc/self/task/") + tid_str + "/comm";
676 std::ifstream in(path);
677 if (!in)
678 continue;
679 std::string current;
680 std::getline(in, current);
681 if (!current.empty() && current.back() == '\n')
682 current.pop_back();
683 if (current == name)
684 {
685 handle_ = static_cast<pid_t>(std::stoi(tid_str));
686 break;
687 }
688 }
689 closedir(dir);
690#endif
691 }
692
693 [[nodiscard]] auto found() const noexcept -> bool
694 {
695#ifdef _WIN32
696 return false;
697#else
698 return handle_ > 0;
699#endif
700 }
701
702 [[nodiscard]] auto set_name(std::string const& name) const -> expected<void, std::error_code>
703 {
704#ifdef _WIN32
705 return unexpected(std::make_error_code(std::errc::function_not_supported));
706#else
707 if (!found())
708 return unexpected(std::make_error_code(std::errc::no_such_process));
709 if (name.length() > 15)
710 return unexpected(std::make_error_code(std::errc::invalid_argument));
711 std::string path = std::string("/proc/self/task/") + std::to_string(handle_) + "/comm";
712 std::ofstream out(path);
713 if (!out)
714 return unexpected(std::error_code(errno, std::generic_category()));
715 out << name;
716 out.flush();
717 if (!out)
718 return unexpected(std::error_code(errno, std::generic_category()));
719 return {};
720#endif
721 }
722
723 [[nodiscard]] auto get_name() const -> std::optional<std::string>
724 {
725#ifdef _WIN32
726 return std::nullopt;
727#else
728 if (!found())
729 return std::nullopt;
730 std::string path = std::string("/proc/self/task/") + std::to_string(handle_) + "/comm";
731 std::ifstream in(path);
732 if (!in)
733 return std::nullopt;
734 std::string current;
735 std::getline(in, current);
736 if (!current.empty() && current.back() == '\n')
737 current.pop_back();
738 return current;
739#endif
740 }
741
742 [[nodiscard]] auto native_handle() const noexcept -> native_handle_type
743 {
744 return handle_;
745 }
746
747 [[nodiscard]] auto set_priority(ThreadPriority priority) const -> expected<void, std::error_code>
748 {
749#ifdef _WIN32
750 return unexpected(std::make_error_code(std::errc::function_not_supported));
751#else
752 if (!found())
753 return unexpected(std::make_error_code(std::errc::no_such_process));
754 int const policy = SCHED_OTHER;
755 auto params_result = SchedulerParams::create_for_policy(SchedulingPolicy::OTHER, priority);
756 if (!params_result.has_value())
757 return unexpected(params_result.error());
758 if (sched_setscheduler(handle_, policy, &params_result.value()) == 0)
759 return {};
760 return unexpected(std::error_code(errno, std::generic_category()));
761#endif
762 }
763
764 [[nodiscard]] auto set_scheduling_policy(SchedulingPolicy policy, ThreadPriority priority) const
766 {
767#ifdef _WIN32
768 return unexpected(std::make_error_code(std::errc::function_not_supported));
769#else
770 if (!found())
771 return unexpected(std::make_error_code(std::errc::no_such_process));
772 int policy_int = static_cast<int>(policy);
773 auto params_result = SchedulerParams::create_for_policy(policy, priority);
774 if (!params_result.has_value())
775 return unexpected(params_result.error());
776 if (sched_setscheduler(handle_, policy_int, &params_result.value()) == 0)
777 return {};
778 return unexpected(std::error_code(errno, std::generic_category()));
779#endif
780 }
781
782 [[nodiscard]] auto set_affinity(ThreadAffinity const& affinity) const -> expected<void, std::error_code>
783 {
784#ifdef _WIN32
785 return unexpected(std::make_error_code(std::errc::function_not_supported));
786#else
787 if (!found())
788 return unexpected(std::make_error_code(std::errc::no_such_process));
789 if (sched_setaffinity(handle_, sizeof(cpu_set_t), &affinity.native_handle()) == 0)
790 return {};
791 return unexpected(std::error_code(errno, std::generic_category()));
792#endif
793 }
794
795 private:
796#ifdef _WIN32
797 native_handle_type handle_ = nullptr;
798#else
799 native_handle_type handle_ = 0;
800#endif
801};
802
803// Static hardware information
805{
806 public:
807 static auto hardware_concurrency() -> unsigned int
808 {
809 return std::thread::hardware_concurrency();
810 }
811
812 static auto get_thread_id()
813 {
814#ifdef _WIN32
815 return GetCurrentThreadId();
816#else
817 return static_cast<pid_t>(syscall(SYS_gettid));
818#endif
819 }
820
821 static auto get_current_policy() -> std::optional<SchedulingPolicy>
822 {
823#ifdef _WIN32
824 // Windows doesn't have Linux-style scheduling policies
825 // Return OTHER as a default
826 return SchedulingPolicy::OTHER;
827#else
828 const int policy = sched_getscheduler(0);
829 if (policy == -1)
830 {
831 return std::nullopt;
832 }
833 return static_cast<SchedulingPolicy>(policy);
834#endif
835 }
836
837 static auto get_current_priority() -> std::optional<int>
838 {
839#ifdef _WIN32
840 HANDLE thread = GetCurrentThread();
841 int priority = GetThreadPriority(thread);
842 if (priority == THREAD_PRIORITY_ERROR_RETURN)
843 {
844 return std::nullopt;
845 }
846 return priority;
847#else
848 sched_param param;
849 if (sched_getparam(0, &param) == 0)
850 {
851 return param.sched_priority;
852 }
853 return std::nullopt;
854#endif
855 }
856};
857
858} // namespace threadschedule
Base thread wrapper with common functionality.
Thread priority wrapper with validation.
Enhanced std::thread wrapper.