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{
28{
29};
30
32{
33};
34
35template <typename ThreadType, typename OwnershipTag>
37
53template <typename ThreadType>
54class ThreadStorage<ThreadType, OwningTag>
55{
56 protected:
57 ThreadStorage() = default;
58
59 [[nodiscard]] auto underlying() noexcept -> ThreadType&
60 {
61 return thread_;
62 }
63 [[nodiscard]] auto underlying() const noexcept -> ThreadType const&
64 {
65 return thread_;
66 }
67
68 ThreadType thread_;
69};
70
88template <typename ThreadType>
89class ThreadStorage<ThreadType, NonOwningTag>
90{
91 protected:
92 ThreadStorage() = default;
93 explicit ThreadStorage(ThreadType& t) : external_thread_(&t)
94 {
95 }
96
97 [[nodiscard]] auto underlying() noexcept -> ThreadType&
98 {
99 return *external_thread_;
100 }
101 [[nodiscard]] auto underlying() const noexcept -> ThreadType const&
102 {
103 return *external_thread_;
104 }
105
106 ThreadType* external_thread_ = nullptr; // non-owning
107};
108} // namespace detail
109
162template <typename ThreadType, typename OwnershipTag = detail::OwningTag>
163class BaseThreadWrapper : protected detail::ThreadStorage<ThreadType, OwnershipTag>
164{
165 public:
166 using native_handle_type = typename ThreadType::native_handle_type;
167 using id = typename ThreadType::id;
168
169 BaseThreadWrapper() = default;
170 explicit BaseThreadWrapper(ThreadType& t) : detail::ThreadStorage<ThreadType, OwnershipTag>(t)
171 {
172 }
173 virtual ~BaseThreadWrapper() = default;
174
175 // Thread management
176 void join()
177 {
178 if (underlying().joinable())
179 {
180 underlying().join();
181 }
182 }
183
184 void detach()
185 {
186 if (underlying().joinable())
187 {
188 underlying().detach();
189 }
190 }
191
192 [[nodiscard]] auto joinable() const noexcept -> bool
193 {
194 return underlying().joinable();
195 }
196 [[nodiscard]] auto get_id() const noexcept -> id
197 {
198 return underlying().get_id();
199 }
200 [[nodiscard]] auto native_handle() noexcept -> native_handle_type
201 {
202 return underlying().native_handle();
203 }
204
205 // Extended functionality
206 [[nodiscard]] auto set_name(std::string const& name) -> expected<void, std::error_code>
207 {
208#ifdef _WIN32
209 // Windows supports longer thread names. Try SetThreadDescription dynamically.
210 auto const handle = native_handle();
211 std::wstring wide_name(name.begin(), name.end());
212
213 using SetThreadDescriptionFn = HRESULT(WINAPI*)(HANDLE, PCWSTR);
214 HMODULE hMod = GetModuleHandleW(L"kernel32.dll");
215 if (hMod)
216 {
217 auto set_desc = reinterpret_cast<SetThreadDescriptionFn>(
218 reinterpret_cast<void*>(GetProcAddress(hMod, "SetThreadDescription")));
219 if (set_desc)
220 {
221 if (SUCCEEDED(set_desc(handle, wide_name.c_str())))
223 return expected<void, std::error_code>(unexpect, std::make_error_code(std::errc::invalid_argument));
224 }
225 }
226 // Fallback unavailable
227 return expected<void, std::error_code>(unexpect, std::make_error_code(std::errc::function_not_supported));
228#else
229 if (name.length() > 15)
230 return expected<void, std::error_code>(unexpect, std::make_error_code(std::errc::invalid_argument));
231
232 auto const handle = native_handle();
233 if (pthread_setname_np(handle, name.c_str()) == 0)
234 return {};
235 return expected<void, std::error_code>(unexpect, std::error_code(errno, std::generic_category()));
236#endif
237 }
238
239 [[nodiscard]] auto get_name() const -> std::optional<std::string>
240 {
241#ifdef _WIN32
242 const auto handle = const_cast<BaseThreadWrapper*>(this)->native_handle();
243 using GetThreadDescriptionFn = HRESULT(WINAPI*)(HANDLE, PWSTR*);
244 HMODULE hMod = GetModuleHandleW(L"kernel32.dll");
245 if (hMod)
246 {
247 auto get_desc = reinterpret_cast<GetThreadDescriptionFn>(
248 reinterpret_cast<void*>(GetProcAddress(hMod, "GetThreadDescription")));
249 if (get_desc)
250 {
251 PWSTR thread_name = nullptr;
252 HRESULT hr = get_desc(handle, &thread_name);
253 if (SUCCEEDED(hr) && thread_name)
254 {
255 int size = WideCharToMultiByte(CP_UTF8, 0, thread_name, -1, nullptr, 0, nullptr, nullptr);
256 if (size > 0)
257 {
258 std::string result(size - 1, '\0');
259 WideCharToMultiByte(CP_UTF8, 0, thread_name, -1, &result[0], size, nullptr, nullptr);
260 LocalFree(thread_name);
261 return result;
262 }
263 LocalFree(thread_name);
264 }
265 }
266 }
267 return std::nullopt;
268#else
269 char name[16]; // Linux limit + 1
270 auto const handle = const_cast<BaseThreadWrapper*>(this)->native_handle();
271
272 if (pthread_getname_np(handle, name, sizeof(name)) == 0)
273 {
274 return std::string(name);
275 }
276 return std::nullopt;
277#endif
278 }
279
280 [[nodiscard]] auto set_priority(ThreadPriority priority) -> expected<void, std::error_code>
281 {
282#ifdef _WIN32
283 const auto handle = native_handle();
284 // Map ThreadPriority to Windows priority
285 // Windows thread priorities range from -15 (THREAD_PRIORITY_IDLE) to +15 (THREAD_PRIORITY_TIME_CRITICAL)
286 // We'll map the priority value to Windows constants
287 int win_priority;
288 int prio_val = priority.value();
289
290 if (prio_val <= -10)
291 {
292 win_priority = THREAD_PRIORITY_IDLE;
293 }
294 else if (prio_val <= -5)
295 {
296 win_priority = THREAD_PRIORITY_LOWEST;
297 }
298 else if (prio_val < 0)
299 {
300 win_priority = THREAD_PRIORITY_BELOW_NORMAL;
301 }
302 else if (prio_val == 0)
303 {
304 win_priority = THREAD_PRIORITY_NORMAL;
305 }
306 else if (prio_val <= 5)
307 {
308 win_priority = THREAD_PRIORITY_ABOVE_NORMAL;
309 }
310 else if (prio_val <= 10)
311 {
312 win_priority = THREAD_PRIORITY_HIGHEST;
313 }
314 else
315 {
316 win_priority = THREAD_PRIORITY_TIME_CRITICAL;
317 }
318
319 if (SetThreadPriority(handle, win_priority) != 0)
320 return {};
321 return unexpected(std::make_error_code(std::errc::operation_not_permitted));
322#else
323 const auto handle = native_handle();
324 int const policy = SCHED_OTHER;
325
326 auto params_result = SchedulerParams::create_for_policy(SchedulingPolicy::OTHER, priority);
327
328 if (!params_result.has_value())
329 {
330 return unexpected(params_result.error());
331 }
332
333 if (pthread_setschedparam(handle, policy, &params_result.value()) == 0)
334 return {};
335 return unexpected(std::error_code(errno, std::generic_category()));
336#endif
337 }
338
339 [[nodiscard]] auto set_scheduling_policy(SchedulingPolicy policy, ThreadPriority priority)
341 {
342#ifdef _WIN32
343 // Windows doesn't have the same scheduling policy concept as Linux
344 // We'll just set the priority and return success
345 return set_priority(priority);
346#else
347 const auto handle = native_handle();
348 int const policy_int = static_cast<int>(policy);
349
350 auto params_result = SchedulerParams::create_for_policy(policy, priority);
351 if (!params_result.has_value())
352 {
353 return unexpected(params_result.error());
354 }
355
356 if (pthread_setschedparam(handle, policy_int, &params_result.value()) == 0)
357 return {};
358 return unexpected(std::error_code(errno, std::generic_category()));
359#endif
360 }
361
362 [[nodiscard]] auto set_affinity(ThreadAffinity const& affinity) -> expected<void, std::error_code>
363 {
364#ifdef _WIN32
365 const auto handle = native_handle();
366 // Prefer Group Affinity if available
367 using SetThreadGroupAffinityFn = BOOL(WINAPI*)(HANDLE, const GROUP_AFFINITY*, PGROUP_AFFINITY);
368 HMODULE hMod = GetModuleHandleW(L"kernel32.dll");
369 if (hMod)
370 {
371 auto set_group_affinity = reinterpret_cast<SetThreadGroupAffinityFn>(
372 reinterpret_cast<void*>(GetProcAddress(hMod, "SetThreadGroupAffinity")));
373 if (set_group_affinity && affinity.has_any())
374 {
375 GROUP_AFFINITY ga{};
376 ga.Mask = static_cast<KAFFINITY>(affinity.get_mask());
377 ga.Group = affinity.get_group();
378 if (set_group_affinity(handle, &ga, nullptr) != 0)
379 return {};
380 return unexpected(std::make_error_code(std::errc::operation_not_permitted));
381 }
382 }
383 // Fallback to legacy mask (single-group systems)
384 DWORD_PTR mask = static_cast<DWORD_PTR>(affinity.get_mask());
385 if (SetThreadAffinityMask(handle, mask) != 0)
386 return {};
387 return unexpected(std::make_error_code(std::errc::operation_not_permitted));
388#else
389 const auto handle = native_handle();
390 if (pthread_setaffinity_np(handle, sizeof(cpu_set_t), &affinity.native_handle()) == 0)
391 return {};
392 return unexpected(std::error_code(errno, std::generic_category()));
393#endif
394 }
395
396 [[nodiscard]] auto get_affinity() const -> std::optional<ThreadAffinity>
397 {
398#ifdef _WIN32
399 const auto handle = const_cast<BaseThreadWrapper*>(this)->native_handle();
400 using GetThreadGroupAffinityFn = BOOL(WINAPI*)(HANDLE, PGROUP_AFFINITY);
401 HMODULE hMod = GetModuleHandleW(L"kernel32.dll");
402 if (hMod)
403 {
404 auto get_group_affinity = reinterpret_cast<GetThreadGroupAffinityFn>(
405 reinterpret_cast<void*>(GetProcAddress(hMod, "GetThreadGroupAffinity")));
406 if (get_group_affinity)
407 {
408 GROUP_AFFINITY ga{};
409 if (get_group_affinity(handle, &ga) != 0)
410 {
411 ThreadAffinity affinity;
412 for (int i = 0; i < 64; ++i)
413 {
414 if ((ga.Mask & (static_cast<KAFFINITY>(1) << i)) != 0)
415 {
416 affinity.add_cpu(static_cast<int>(ga.Group) * 64 + i);
417 }
418 }
419 if (affinity.has_any())
420 {
421 return affinity;
422 }
423 return std::nullopt;
424 }
425 }
426 return std::nullopt;
427 }
428#else
429 ThreadAffinity affinity;
430 auto const handle = const_cast<BaseThreadWrapper*>(this)->native_handle();
431
432 if (pthread_getaffinity_np(handle, sizeof(cpu_set_t), &affinity.native_handle()) == 0)
433 {
434 return affinity;
435 }
436 return std::nullopt;
437#endif
438 }
439
440 // Nice value (process-level, affects all threads)
441 static auto set_nice_value(int nice_value) -> bool
442 {
443#ifdef _WIN32
444 // Windows has process priority classes, not nice values
445 // We'll use SetPriorityClass for the process
446 DWORD priority_class;
447 if (nice_value <= -15)
448 {
449 priority_class = HIGH_PRIORITY_CLASS;
450 }
451 else if (nice_value <= -10)
452 {
453 priority_class = ABOVE_NORMAL_PRIORITY_CLASS;
454 }
455 else if (nice_value < 10)
456 {
457 priority_class = NORMAL_PRIORITY_CLASS;
458 }
459 else if (nice_value < 19)
460 {
461 priority_class = BELOW_NORMAL_PRIORITY_CLASS;
462 }
463 else
464 {
465 priority_class = IDLE_PRIORITY_CLASS;
466 }
467 return SetPriorityClass(GetCurrentProcess(), priority_class) != 0;
468#else
469 return setpriority(PRIO_PROCESS, 0, nice_value) == 0;
470#endif
471 }
472
473 static auto get_nice_value() -> std::optional<int>
474 {
475#ifdef _WIN32
476 // Get Windows process priority class and map to nice value
477 DWORD priority_class = GetPriorityClass(GetCurrentProcess());
478 if (priority_class == 0)
479 {
480 return std::nullopt;
481 }
482
483 // Map Windows priority class to nice value
484 switch (priority_class)
485 {
486 case HIGH_PRIORITY_CLASS:
487 return -15;
488 case ABOVE_NORMAL_PRIORITY_CLASS:
489 return -10;
490 case NORMAL_PRIORITY_CLASS:
491 return 0;
492 case BELOW_NORMAL_PRIORITY_CLASS:
493 return 10;
494 case IDLE_PRIORITY_CLASS:
495 return 19;
496 default:
497 return 0;
498 }
499#else
500 errno = 0;
501 int const nice = getpriority(PRIO_PROCESS, 0);
502 if (errno == 0)
503 {
504 return nice;
505 }
506 return std::nullopt;
507#endif
508 }
509
510 protected:
511 using detail::ThreadStorage<ThreadType, OwnershipTag>::underlying;
512 using detail::ThreadStorage<ThreadType, OwnershipTag>::ThreadStorage;
513};
514
551class ThreadWrapper : public BaseThreadWrapper<std::thread, detail::OwningTag>
552{
553 public:
554 ThreadWrapper() = default;
555
556 // Construct by taking ownership of an existing std::thread (move)
557 ThreadWrapper(std::thread&& t) noexcept
558 {
559 this->underlying() = std::move(t);
560 }
561
562 template <typename F, typename... Args>
563 explicit ThreadWrapper(F&& f, Args&&... args) : BaseThreadWrapper()
564 {
565 this->underlying() = std::thread(std::forward<F>(f), std::forward<Args>(args)...);
566 }
567
568 ThreadWrapper(ThreadWrapper const&) = delete;
569 auto operator=(ThreadWrapper const&) -> ThreadWrapper& = delete;
570
571 ThreadWrapper(ThreadWrapper&& other) noexcept
572 {
573 this->underlying() = std::move(other.underlying());
574 }
575
576 auto operator=(ThreadWrapper&& other) noexcept -> ThreadWrapper&
577 {
578 if (this != &other)
579 {
580 if (this->underlying().joinable())
581 {
582 this->underlying().join();
583 }
584 this->underlying() = std::move(other.underlying());
585 }
586 return *this;
587 }
588
589 ~ThreadWrapper() override
590 {
591 if (this->underlying().joinable())
592 {
593 this->underlying().join();
594 }
595 }
596
597 // Ownership transfer to std::thread for APIs that take plain std::thread
598 auto release() noexcept -> std::thread
599 {
600 return std::move(this->underlying());
601 }
602
603 explicit operator std::thread() && noexcept
604 {
605 return std::move(this->underlying());
606 }
607
608 // Factory methods
609 template <typename F, typename... Args>
610 static auto create_with_config(std::string const& name, SchedulingPolicy policy, ThreadPriority priority, F&& f,
611 Args&&... args) -> ThreadWrapper
612 {
613
614 ThreadWrapper wrapper(std::forward<F>(f), std::forward<Args>(args)...);
615 if (auto r = wrapper.set_name(name); !r.has_value())
616 {
617 }
618 if (auto r = wrapper.set_scheduling_policy(policy, priority); !r.has_value())
619 {
620 }
621 return wrapper;
622 }
623};
624
644class ThreadWrapperView : public BaseThreadWrapper<std::thread, detail::NonOwningTag>
645{
646 public:
647 ThreadWrapperView(std::thread& t) : BaseThreadWrapper<std::thread, detail::NonOwningTag>(t)
648 {
649 }
650
651 // Non-owning access to the underlying std::thread
652 auto get() noexcept -> std::thread&
653 {
654 return this->underlying();
655 }
656 [[nodiscard]] auto get() const noexcept -> std::thread const&
657 {
658 return this->underlying();
659 }
660};
661
699#if __cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)
700class JThreadWrapper : public BaseThreadWrapper<std::jthread, detail::OwningTag>
701{
702 public:
703 JThreadWrapper() = default;
704
705 // Construct by taking ownership of an existing std::jthread (move)
706 JThreadWrapper(std::jthread&& t) noexcept : BaseThreadWrapper()
707 {
708 this->underlying() = std::move(t);
709 }
710
711 // Ownership transfer to std::jthread for APIs that take plain std::jthread
712 auto release() noexcept -> std::jthread
713 {
714 return std::move(this->underlying());
715 }
716
717 explicit operator std::jthread() && noexcept
718 {
719 return std::move(this->underlying());
720 }
721
722 template <typename F, typename... Args>
723 explicit JThreadWrapper(F&& f, Args&&... args) : BaseThreadWrapper()
724 {
725 this->underlying() = std::jthread(std::forward<F>(f), std::forward<Args>(args)...);
726 }
727
728 JThreadWrapper(JThreadWrapper const&) = delete;
729 auto operator=(JThreadWrapper const&) -> JThreadWrapper& = delete;
730
731 JThreadWrapper(JThreadWrapper&& other) noexcept
732 {
733 this->underlying() = std::move(other.underlying());
734 }
735
736 auto operator=(JThreadWrapper&& other) noexcept -> JThreadWrapper&
737 {
738 if (this != &other)
739 {
740 this->underlying() = std::move(other.underlying());
741 }
742 return *this;
743 }
744
745 // jthread-specific functionality
746 void request_stop()
747 {
748 this->underlying().request_stop();
749 }
750 [[nodiscard]] auto stop_requested() const noexcept -> bool
751 {
752 return this->underlying().get_stop_token().stop_requested();
753 }
754 [[nodiscard]] auto get_stop_token() const noexcept -> std::stop_token
755 {
756 return this->underlying().get_stop_token();
757 }
758 [[nodiscard]] auto get_stop_source() noexcept -> std::stop_source
759 {
760 return this->underlying().get_stop_source();
761 }
762
763 // Factory methods
764 template <typename F, typename... Args>
765 static auto create_with_config(std::string const& name, SchedulingPolicy policy, ThreadPriority priority, F&& f,
766 Args&&... args) -> JThreadWrapper
767 {
768
769 JThreadWrapper wrapper(std::forward<F>(f), std::forward<Args>(args)...);
770 if (auto r = wrapper.set_name(name); !r.has_value())
771 {
772 }
773 if (auto r = wrapper.set_scheduling_policy(policy, priority); !r.has_value())
774 {
775 }
776 return wrapper;
777 }
778};
779
804class JThreadWrapperView : public BaseThreadWrapper<std::jthread, detail::NonOwningTag>
805{
806 public:
807 JThreadWrapperView(std::jthread& t) : BaseThreadWrapper<std::jthread, detail::NonOwningTag>(t)
808 {
809 }
810
811 void request_stop()
812 {
813 this->underlying().request_stop();
814 }
815 [[nodiscard]] auto stop_requested() const noexcept -> bool
816 {
817 return this->underlying().get_stop_token().stop_requested();
818 }
819 [[nodiscard]] auto get_stop_token() const noexcept -> std::stop_token
820 {
821 return this->underlying().get_stop_token();
822 }
823 [[nodiscard]] auto get_stop_source() noexcept -> std::stop_source
824 {
825 return this->underlying().get_stop_source();
826 }
827
828 // Non-owning access to the underlying std::jthread
829 auto get() noexcept -> std::jthread&
830 {
831 return this->underlying();
832 }
833 [[nodiscard]] auto get() const noexcept -> std::jthread const&
834 {
835 return this->underlying();
836 }
837};
838#else
839// Fallback for compilers without C++20 support
840using JThreadWrapper = ThreadWrapper;
841using JThreadWrapperView = ThreadWrapperView;
842#endif // C++20
843
877class ThreadByNameView
878{
879 public:
880#ifdef _WIN32
881 using native_handle_type = void*; // unsupported placeholder
882#else
883 using native_handle_type = pid_t; // Linux TID
884#endif
885
886 explicit ThreadByNameView(const std::string& name)
887 {
888#ifdef _WIN32
889 // Not supported on Windows in this implementation
890 (void)name;
891#else
892 DIR* dir = opendir("/proc/self/task");
893 if (dir == nullptr)
894 return;
895 struct dirent* entry = nullptr;
896 while ((entry = readdir(dir)) != nullptr)
897 {
898 if (entry->d_name[0] == '.')
899 continue;
900 std::string tid_str(entry->d_name);
901 std::string path = std::string("/proc/self/task/") + tid_str + "/comm";
902 std::ifstream in(path);
903 if (!in)
904 continue;
905 std::string current;
906 std::getline(in, current);
907 if (!current.empty() && current.back() == '\n')
908 current.pop_back();
909 if (current == name)
910 {
911 handle_ = static_cast<pid_t>(std::stoi(tid_str));
912 break;
913 }
914 }
915 closedir(dir);
916#endif
917 }
918
919 [[nodiscard]] auto found() const noexcept -> bool
920 {
921#ifdef _WIN32
922 return false;
923#else
924 return handle_ > 0;
925#endif
926 }
927
928 [[nodiscard]] auto set_name(std::string const& name) const -> expected<void, std::error_code>
929 {
930#ifdef _WIN32
931 return unexpected(std::make_error_code(std::errc::function_not_supported));
932#else
933 if (!found())
934 return unexpected(std::make_error_code(std::errc::no_such_process));
935 if (name.length() > 15)
936 return unexpected(std::make_error_code(std::errc::invalid_argument));
937 std::string path = std::string("/proc/self/task/") + std::to_string(handle_) + "/comm";
938 std::ofstream out(path);
939 if (!out)
940 return unexpected(std::error_code(errno, std::generic_category()));
941 out << name;
942 out.flush();
943 if (!out)
944 return unexpected(std::error_code(errno, std::generic_category()));
945 return {};
946#endif
947 }
948
949 [[nodiscard]] auto get_name() const -> std::optional<std::string>
950 {
951#ifdef _WIN32
952 return std::nullopt;
953#else
954 if (!found())
955 return std::nullopt;
956 std::string path = std::string("/proc/self/task/") + std::to_string(handle_) + "/comm";
957 std::ifstream in(path);
958 if (!in)
959 return std::nullopt;
960 std::string current;
961 std::getline(in, current);
962 if (!current.empty() && current.back() == '\n')
963 current.pop_back();
964 return current;
965#endif
966 }
967
968 [[nodiscard]] auto native_handle() const noexcept -> native_handle_type
969 {
970 return handle_;
971 }
972
973 [[nodiscard]] auto set_priority(ThreadPriority priority) const -> expected<void, std::error_code>
974 {
975#ifdef _WIN32
976 return unexpected(std::make_error_code(std::errc::function_not_supported));
977#else
978 if (!found())
979 return unexpected(std::make_error_code(std::errc::no_such_process));
980 int const policy = SCHED_OTHER;
981 auto params_result = SchedulerParams::create_for_policy(SchedulingPolicy::OTHER, priority);
982 if (!params_result.has_value())
983 return unexpected(params_result.error());
984 if (sched_setscheduler(handle_, policy, &params_result.value()) == 0)
985 return {};
986 return unexpected(std::error_code(errno, std::generic_category()));
987#endif
988 }
989
990 [[nodiscard]] auto set_scheduling_policy(SchedulingPolicy policy, ThreadPriority priority) const
992 {
993#ifdef _WIN32
994 return unexpected(std::make_error_code(std::errc::function_not_supported));
995#else
996 if (!found())
997 return unexpected(std::make_error_code(std::errc::no_such_process));
998 int policy_int = static_cast<int>(policy);
999 auto params_result = SchedulerParams::create_for_policy(policy, priority);
1000 if (!params_result.has_value())
1001 return unexpected(params_result.error());
1002 if (sched_setscheduler(handle_, policy_int, &params_result.value()) == 0)
1003 return {};
1004 return unexpected(std::error_code(errno, std::generic_category()));
1005#endif
1006 }
1007
1008 [[nodiscard]] auto set_affinity(ThreadAffinity const& affinity) const -> expected<void, std::error_code>
1009 {
1010#ifdef _WIN32
1011 return unexpected(std::make_error_code(std::errc::function_not_supported));
1012#else
1013 if (!found())
1014 return unexpected(std::make_error_code(std::errc::no_such_process));
1015 if (sched_setaffinity(handle_, sizeof(cpu_set_t), &affinity.native_handle()) == 0)
1016 return {};
1017 return unexpected(std::error_code(errno, std::generic_category()));
1018#endif
1019 }
1020
1021 private:
1022#ifdef _WIN32
1023 native_handle_type handle_ = nullptr;
1024#else
1025 native_handle_type handle_ = 0;
1026#endif
1027};
1028
1046{
1047 public:
1048 static auto hardware_concurrency() -> unsigned int
1049 {
1050 return std::thread::hardware_concurrency();
1051 }
1052
1053 static auto get_thread_id()
1054 {
1055#ifdef _WIN32
1056 return GetCurrentThreadId();
1057#else
1058 return static_cast<pid_t>(syscall(SYS_gettid));
1059#endif
1060 }
1061
1062 static auto get_current_policy() -> std::optional<SchedulingPolicy>
1063 {
1064#ifdef _WIN32
1065 // Windows doesn't have Linux-style scheduling policies
1066 // Return OTHER as a default
1067 return SchedulingPolicy::OTHER;
1068#else
1069 const int policy = sched_getscheduler(0);
1070 if (policy == -1)
1071 {
1072 return std::nullopt;
1073 }
1074 return static_cast<SchedulingPolicy>(policy);
1075#endif
1076 }
1077
1078 static auto get_current_priority() -> std::optional<int>
1079 {
1080#ifdef _WIN32
1081 HANDLE thread = GetCurrentThread();
1082 int priority = GetThreadPriority(thread);
1083 if (priority == THREAD_PRIORITY_ERROR_RETURN)
1084 {
1085 return std::nullopt;
1086 }
1087 return priority;
1088#else
1089 sched_param param;
1090 if (sched_getparam(0, &param) == 0)
1091 {
1092 return param.sched_priority;
1093 }
1094 return std::nullopt;
1095#endif
1096 }
1097};
1098
1099} // namespace threadschedule
Polymorphic base providing common thread management operations.
Manages a set of CPU indices to which a thread may be bound.
Static utility class providing hardware and scheduling introspection.
Value-semantic wrapper for a thread scheduling priority.
Non-owning view over an externally managed std::thread.
Owning wrapper around std::thread with RAII join-on-destroy semantics.
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.
Tag type selecting non-owning (pointer) storage in ThreadStorage.
Tag type selecting owning (value) storage in ThreadStorage.