ThreadSchedule 3.0.0
Modern C++ thread management library
Loading...
Searching...
No Matches
native.hpp
Go to the documentation of this file.
1#pragma once
2
8#include "../../expected.hpp"
9#include <algorithm>
10#include <array>
11#include <atomic>
12#include <cstdint>
13#include <fstream>
14#include <memory>
15#include <optional>
16#include <sstream>
17#include <string>
18#include <system_error>
19#include <vector>
20
21#ifdef _WIN32
22# include "../unique_handle.hpp"
23# include "../windows_api.hpp"
24#endif
25
26#ifdef _WIN32
27# if defined(__MINGW32__)
28# include <pthread.h>
29# endif
30#else
31# include <pthread.h>
32# include <sched.h>
33# include <sys/resource.h>
34# include <sys/types.h>
35#endif
36
38{
39// expected/result are provided by expected.hpp
40
41#ifdef _WIN32
42using native_thread_id = unsigned long; // DWORD thread id
43#else
44using native_thread_id = pid_t; // Linux TID via gettid()
45#endif
46
84enum class native_scheduling_policy : std::uint_fast8_t
85{
86#ifdef _WIN32
87 // Windows doesn't have the same scheduling policies as Linux
88 // We'll use generic values
89 other = 0,
90 fifo = 1,
91 rr = 2,
92 batch = 3,
93 idle = 4
94#else
95 other = SCHED_OTHER,
96 fifo = SCHED_FIFO,
97 rr = SCHED_RR,
98 batch = SCHED_BATCH,
99 idle = SCHED_IDLE,
100# ifdef SCHED_DEADLINE
101 deadline = SCHED_DEADLINE
102# endif
103#endif
104};
105
140{
141public:
142 constexpr explicit native_thread_priority(int priority = 0)
143 : priority_(std::clamp(priority, min_priority, max_priority))
144 {
145 }
146
147 [[nodiscard]] constexpr auto
148 value() const noexcept -> int
149 {
150 return priority_;
151 }
152 [[nodiscard]] constexpr auto
153 is_valid() const noexcept -> bool
154 {
155 return priority_ >= min_priority && priority_ <= max_priority;
156 }
157
158 [[nodiscard]] static constexpr auto
160 {
161 return native_thread_priority(max_nice_priority);
162 }
163 [[nodiscard]] static constexpr auto
165 {
166 return native_thread_priority(0);
167 }
168 [[nodiscard]] static constexpr auto
170 {
171 return native_thread_priority(min_nice_priority);
172 }
173 [[nodiscard]] static constexpr auto
175 {
176 return native_thread_priority(min_realtime_priority);
177 }
178 [[nodiscard]] static constexpr auto
180 {
181 return native_thread_priority(max_realtime_priority);
182 }
183
184 [[nodiscard]] constexpr auto
185 operator==(native_thread_priority const& other) const noexcept -> bool
186 {
187 return priority_ == other.priority_;
188 }
189 [[nodiscard]] constexpr auto
190 operator!=(native_thread_priority const& other) const noexcept -> bool
191 {
192 return priority_ != other.priority_;
193 }
194 [[nodiscard]] constexpr auto
195 operator<(native_thread_priority const& other) const noexcept -> bool
196 {
197 return priority_ < other.priority_;
198 }
199 [[nodiscard]] constexpr auto
200 operator<=(native_thread_priority const& other) const noexcept -> bool
201 {
202 return priority_ <= other.priority_;
203 }
204 [[nodiscard]] constexpr auto
205 operator>(native_thread_priority const& other) const noexcept -> bool
206 {
207 return priority_ > other.priority_;
208 }
209 [[nodiscard]] constexpr auto
210 operator>=(native_thread_priority const& other) const noexcept -> bool
211 {
212 return priority_ >= other.priority_;
213 }
214
215 [[nodiscard]] auto
216 to_string() const -> std::string
217 {
218 std::ostringstream oss;
219 oss << "native_thread_priority(" << priority_ << ")";
220 return oss.str();
221 }
222
223private:
224 static constexpr int min_nice_priority = -20;
225 static constexpr int max_nice_priority = 19;
226 static constexpr int min_realtime_priority = 1;
227 static constexpr int max_realtime_priority = 99;
228 static constexpr int min_priority = min_nice_priority;
229 static constexpr int max_priority = max_realtime_priority;
230 int priority_;
231};
232
233enum class native_scheduling_intent : std::uint_fast8_t
234{
236 normal,
240};
241
242enum class native_priority_model : std::uint_fast8_t
243{
244 intent,
249};
250
259
267
268namespace native_schedule
269{
270[[nodiscard]] constexpr auto
276
277[[nodiscard]] constexpr auto
283
284[[nodiscard]] constexpr auto
290
291[[nodiscard]] constexpr auto
297
298[[nodiscard]] constexpr auto
304
305[[nodiscard]] constexpr auto
311
312[[nodiscard]] constexpr auto
318
319[[nodiscard]] constexpr auto
324
325[[nodiscard]] constexpr auto
327{
328#ifdef _WIN32
329 bool const valid = priority == THREAD_PRIORITY_IDLE || priority == THREAD_PRIORITY_LOWEST
330 || priority == THREAD_PRIORITY_BELOW_NORMAL || priority == THREAD_PRIORITY_NORMAL
331 || priority == THREAD_PRIORITY_ABOVE_NORMAL || priority == THREAD_PRIORITY_HIGHEST
332 || priority == THREAD_PRIORITY_TIME_CRITICAL;
333#else
334 bool const valid = false;
335#endif
338}
339} // namespace native_schedule
340constexpr auto
342{
344}
345
346constexpr auto
348{
350}
351
352[[nodiscard]] constexpr auto
354{
355 if (config.model == native_priority_model::posix_realtime)
356 {
357 auto policy = is_realtime_policy(config.policy) ? config.policy : native_scheduling_policy::rr;
358 return { policy, config.priority, config.model, config.valid };
359 }
360
361 if (config.model != native_priority_model::intent)
362 return { config.policy, config.priority, config.model, config.valid };
363
364 switch (config.intent)
365 {
367 return { native_scheduling_policy::idle, native_thread_priority::lowest(), config.model, config.valid };
369 return { native_scheduling_policy::other, native_thread_priority{ -5 }, config.model, config.valid };
371 return { native_scheduling_policy::other, native_thread_priority::highest(), config.model, config.valid };
373 return { is_realtime_policy(config.policy) ? config.policy : native_scheduling_policy::rr, config.priority,
374 config.model, config.valid };
376 default:
377 return { native_scheduling_policy::other, native_thread_priority::normal(), config.model, config.valid };
378 }
379}
380
381#ifdef _WIN32
382inline auto
383map_priority_to_win32(int prio_val) -> int
384{
385 if (prio_val <= -10)
386 return THREAD_PRIORITY_HIGHEST;
387 if (prio_val < 0)
388 return THREAD_PRIORITY_ABOVE_NORMAL;
389 if (prio_val == 0)
390 return THREAD_PRIORITY_NORMAL;
391 if (prio_val < 10)
392 return THREAD_PRIORITY_BELOW_NORMAL;
393 if (prio_val < 19)
394 return THREAD_PRIORITY_LOWEST;
395 return THREAD_PRIORITY_IDLE;
396}
397#endif
433{
434public:
436 {
437#ifdef _WIN32
438 group_ = 0;
439 mask_ = 0;
440#else
441 CPU_ZERO(&cpuset_);
442#endif
443 }
444
445 explicit native_thread_affinity(std::vector<int> const& cpus) : native_thread_affinity()
446 {
447 for (int cpu : cpus)
448 {
449 add_cpu(cpu);
450 }
451 }
452
453 // Adds a CPU index. On Windows, indices >= 64 select group = cpu/64
454 // automatically.
455 void
456 add_cpu(int cpu)
457 {
458#ifdef _WIN32
459 if (cpu < 0)
460 return;
461 WORD g = static_cast<WORD>(cpu / 64);
462 int bit = cpu % 64;
463 if (!has_any())
464 {
465 group_ = g;
466 }
467 if (g != group_)
468 {
469 // Single-group affinity object: ignore CPUs from other groups
470 return;
471 }
472 mask_ |= (static_cast<unsigned long long>(1) << bit);
473#else
474 if (cpu >= 0 && cpu < CPU_SETSIZE)
475 {
476 CPU_SET(cpu, &cpuset_);
477 }
478#endif
479 }
480
481 void
482 remove_cpu(int cpu)
483 {
484#ifdef _WIN32
485 if (cpu < 0)
486 return;
487 WORD g = static_cast<WORD>(cpu / 64);
488 int bit = cpu % 64;
489 if (g == group_)
490 {
491 mask_ &= ~(static_cast<unsigned long long>(1) << bit);
492 }
493#else
494 if (cpu >= 0 && cpu < CPU_SETSIZE)
495 {
496 CPU_CLR(cpu, &cpuset_);
497 }
498#endif
499 }
500
501 [[nodiscard]] auto
502 is_set(int cpu) const -> bool
503 {
504#ifdef _WIN32
505 if (cpu < 0)
506 return false;
507 WORD g = static_cast<WORD>(cpu / 64);
508 int bit = cpu % 64;
509 return g == group_ && (mask_ & (static_cast<unsigned long long>(1) << bit)) != 0;
510#else
511 return cpu >= 0 && cpu < CPU_SETSIZE && CPU_ISSET(cpu, &cpuset_);
512#endif
513 }
514
515 [[nodiscard]] auto
516 has_cpu(int cpu) const -> bool
517 {
518 return is_set(cpu);
519 }
520
521 void
523 {
524#ifdef _WIN32
525 mask_ = 0;
526#else
527 CPU_ZERO(&cpuset_);
528#endif
529 }
530
531 [[nodiscard]] auto
532 get_cpus() const -> std::vector<int>
533 {
534 std::vector<int> cpus;
535#ifdef _WIN32
536 for (int i = 0; i < 64; ++i)
537 {
538 if (mask_ & (static_cast<unsigned long long>(1) << i))
539 {
540 cpus.push_back(static_cast<int>(group_) * 64 + i);
541 }
542 }
543#else
544 for (int i = 0; i < CPU_SETSIZE; ++i)
545 {
546 if (CPU_ISSET(i, &cpuset_))
547 {
548 cpus.push_back(i);
549 }
550 }
551#endif
552 return cpus;
553 }
554
555#ifdef _WIN32
556 [[nodiscard]] unsigned long long
557 get_mask() const
558 {
559 return mask_;
560 }
561 [[nodiscard]] WORD
562 get_group() const
563 {
564 return group_;
565 }
566 [[nodiscard]] bool
567 has_any() const
568 {
569 return mask_ != 0;
570 }
571#else
572 [[nodiscard]] auto
573 native_handle() const -> cpu_set_t const&
574 {
575 return cpuset_;
576 }
577#endif
578
579 [[nodiscard]] auto
580 to_string() const -> std::string
581 {
582 auto cpus = get_cpus();
583 std::ostringstream oss;
584 oss << "native_thread_affinity({";
585 for (size_t i = 0; i < cpus.size(); ++i)
586 {
587 if (i > 0)
588 oss << ", ";
589 oss << cpus[i];
590 }
591 oss << "})";
592 return oss.str();
593 }
594
595private:
596#ifdef _WIN32
597 WORD group_;
598 unsigned long long mask_;
599#else
600 cpu_set_t cpuset_;
601#endif
602};
603
605{
606 std::optional<std::string> name;
607 std::optional<native_scheduling_config> scheduling;
608 std::optional<native_thread_affinity> affinity;
609};
610
651{
652public:
653#ifdef _WIN32
654 // Windows doesn't use sched_param, but we'll define a compatible type
655 struct sched_param_win
656 {
657 int sched_priority;
658 };
659
662 {
663 sched_param_win param{};
664 if (detail::is_realtime_policy(policy))
665 {
666 if (priority.value() >= 75 || priority.value() <= -10)
667 param.sched_priority = THREAD_PRIORITY_HIGHEST;
668 else
669 param.sched_priority = THREAD_PRIORITY_ABOVE_NORMAL;
670 return param;
671 }
672
673 param.sched_priority = detail::map_priority_to_win32(priority.value());
674 return param;
675 }
676
678 get_priority_range([[maybe_unused]] native_scheduling_policy policy)
679 {
680 // Windows thread priorities range from -15 to +15
681 return 30;
682 }
683#else
684 static auto
687 {
688 sched_param param{};
689
690# ifdef SCHED_DEADLINE
691 if (policy == native_scheduling_policy::deadline)
692 return unexpected(std::make_error_code(std::errc::function_not_supported));
693# endif
694
695 int const policy_int = static_cast<int>(policy);
696 int const min_prio = sched_get_priority_min(policy_int);
697 int const max_prio = sched_get_priority_max(policy_int);
698
699 if (min_prio == -1 || max_prio == -1)
700 {
701 return unexpected(std::make_error_code(std::errc::invalid_argument));
702 }
703
704 if (min_prio == max_prio)
705 {
706 param.sched_priority = min_prio;
707 return param;
708 }
709
710 if (detail::is_realtime_policy(policy) && priority.value() > 0)
711 {
712 param.sched_priority = std::clamp(priority.value(), min_prio, max_prio);
713 return param;
714 }
715
716 constexpr int highest_nice_value = -20;
717 constexpr int lowest_nice_value = 19;
718 constexpr int user_span = lowest_nice_value - highest_nice_value;
719 int const native_span = max_prio - min_prio;
720 int const offset = std::clamp(priority.value(), highest_nice_value, lowest_nice_value) - highest_nice_value;
721 param.sched_priority = max_prio - (((offset * native_span) + (user_span / 2)) / user_span);
722 return param;
723 }
724
725 static auto
727 {
728# ifdef SCHED_DEADLINE
729 if (policy == native_scheduling_policy::deadline)
730 return unexpected(std::make_error_code(std::errc::function_not_supported));
731# endif
732
733 int const policy_int = static_cast<int>(policy);
734 int const min_prio = sched_get_priority_min(policy_int);
735 int const max_prio = sched_get_priority_max(policy_int);
736
737 if (min_prio == -1 || max_prio == -1)
738 {
739 return unexpected(std::make_error_code(std::errc::invalid_argument));
740 }
741
742 return max_prio - min_prio;
743 }
744#endif
745};
746
750inline auto
752{
753 switch (policy)
754 {
756 return "OTHER";
758 return "FIFO";
760 return "RR";
762 return "BATCH";
764 return "IDLE";
765#if defined(SCHED_DEADLINE) && !defined(_WIN32)
766 case native_scheduling_policy::deadline:
767 return "DEADLINE";
768#endif
769 default:
770 return "UNKNOWN";
771 }
772}
773
774// ---------------------------------------------------------------------------
775// detail:: free functions for thread configuration (priority, policy,
776// affinity)
777//
778// Overloaded by handle type so that wrapper classes can delegate native
779// control operations through a common boundary.
780// ---------------------------------------------------------------------------
781
782#ifdef _WIN32
783# include "windows.hpp"
784#else
785# include "posix.hpp"
786#endif
787
788template <typename NativeHandle>
789inline auto
791{
792 auto previous = read_affinity(handle);
793 if (!previous)
794 return unexpected(previous.error());
795
796 auto applied = apply_affinity(handle, affinity);
797 if (!applied)
798 return applied;
799
800 auto effective = read_affinity(handle);
801 if (effective && effective->get_cpus() == affinity.get_cpus())
802 return {};
803
804 auto restored = apply_affinity(handle, previous.value());
805 if (!restored)
806 return unexpected(std::make_error_code(std::errc::state_not_recoverable));
807 if (!effective)
808 return unexpected(effective.error());
809 return unexpected(std::make_error_code(std::errc::invalid_argument));
810}
811
812template <typename NativeHandle>
813inline auto
816{
817 auto const scheduling = resolve_scheduling_config(config);
818 if (!scheduling.valid)
819 return unexpected(std::make_error_code(std::errc::invalid_argument));
820
821#ifdef _WIN32
822 if (scheduling.model == native_priority_model::windows_thread)
823 return apply_windows_thread_priority(handle, scheduling.priority.value());
824#endif
825
826 if (scheduling.model == native_priority_model::posix_nice)
827 {
828#ifdef _WIN32
829 (void)tid;
830 return apply_nice_value(handle, scheduling.priority.value());
831#else
832 if (tid <= 0)
833 return unexpected(std::make_error_code(std::errc::operation_not_supported));
834 auto policy_result
836 if (!policy_result)
837 return policy_result;
838 return apply_nice_value(tid, scheduling.priority.value());
839#endif
840 }
841
842 return apply_scheduling_policy(handle, scheduling.policy, scheduling.priority);
843}
844
845template <typename NativeHandle>
846inline auto
848{
849#ifdef _WIN32
850 (void)tid;
851 return read_nice_value(handle);
852#else
853 if (tid <= 0)
854 return unexpected(std::make_error_code(std::errc::operation_not_supported));
855 auto const policy = read_scheduling_policy(handle);
856 if (!policy.has_value())
857 return unexpected(std::make_error_code(std::errc::no_such_process));
858 if (policy.value() == native_scheduling_policy::idle)
859 return 19;
860 if (!uses_nice_value(policy.value()))
861 return unexpected(std::make_error_code(std::errc::operation_not_supported));
862 return read_nice_value(tid);
863#endif
864}
865
866} // namespace threadschedule::detail
Manages a set of CPU indices to which a thread may be bound.
Definition native.hpp:433
auto to_string() const -> std::string
Definition native.hpp:580
auto get_cpus() const -> std::vector< int >
Definition native.hpp:532
auto native_handle() const -> cpu_set_t const &
Definition native.hpp:573
native_thread_affinity(std::vector< int > const &cpus)
Definition native.hpp:445
auto has_cpu(int cpu) const -> bool
Definition native.hpp:516
Value-semantic wrapper for a thread scheduling priority.
Definition native.hpp:140
constexpr auto operator>(native_thread_priority const &other) const noexcept -> bool
Definition native.hpp:205
static constexpr auto realtime_lowest() noexcept -> native_thread_priority
Definition native.hpp:174
constexpr auto operator!=(native_thread_priority const &other) const noexcept -> bool
Definition native.hpp:190
constexpr auto operator==(native_thread_priority const &other) const noexcept -> bool
Definition native.hpp:185
static constexpr auto highest() noexcept -> native_thread_priority
Definition native.hpp:169
static constexpr auto lowest() noexcept -> native_thread_priority
Definition native.hpp:159
constexpr native_thread_priority(int priority=0)
Definition native.hpp:142
constexpr auto operator>=(native_thread_priority const &other) const noexcept -> bool
Definition native.hpp:210
constexpr auto is_valid() const noexcept -> bool
Definition native.hpp:153
constexpr auto operator<(native_thread_priority const &other) const noexcept -> bool
Definition native.hpp:195
static constexpr auto normal() noexcept -> native_thread_priority
Definition native.hpp:164
constexpr auto operator<=(native_thread_priority const &other) const noexcept -> bool
Definition native.hpp:200
static constexpr auto realtime_highest() noexcept -> native_thread_priority
Definition native.hpp:179
constexpr auto value() const noexcept -> int
Definition native.hpp:148
auto to_string() const -> std::string
Definition native.hpp:216
Static utility class for constructing OS-native scheduling parameters.
Definition native.hpp:651
static auto create_for_policy(native_scheduling_policy policy, native_thread_priority priority) -> expected< sched_param, std::error_code >
Definition native.hpp:685
static auto get_priority_range(native_scheduling_policy policy) -> expected< int, std::error_code >
Definition native.hpp:726
Strongly-typed nice value in the POSIX range $[-20, 19]$.
constexpr auto native(native_scheduling_policy policy, native_thread_priority priority) noexcept -> native_scheduling_config
Definition native.hpp:320
constexpr auto interactive() noexcept -> native_scheduling_config
Definition native.hpp:285
constexpr auto low_latency() noexcept -> native_scheduling_config
Definition native.hpp:292
constexpr auto normal() noexcept -> native_scheduling_config
Definition native.hpp:278
constexpr auto native_windows_priority(int priority) noexcept -> native_scheduling_config
Definition native.hpp:326
constexpr auto realtime_rr(int priority=80) noexcept -> native_scheduling_config
Definition native.hpp:306
constexpr auto background() noexcept -> native_scheduling_config
Definition native.hpp:271
Aggregates multiple thread_registry_backend instances into a single queryable view.
auto read_effective_nice(NativeHandle handle, native_thread_id tid) -> expected< int, std::error_code >
Definition native.hpp:847
native_scheduling_policy
Enumeration of available thread scheduling policies.
Definition native.hpp:85
@ other
Standard round-robin time-sharing.
@ idle
For very low priority background tasks.
auto apply_affinity_checked(NativeHandle handle, native_thread_affinity const &affinity) -> expected< void, std::error_code >
Definition native.hpp:790
constexpr auto uses_nice_value(native_scheduling_policy policy) noexcept -> bool
Definition native.hpp:347
constexpr auto is_realtime_policy(native_scheduling_policy policy) noexcept -> bool
Definition native.hpp:341
constexpr auto resolve_scheduling_config(native_scheduling_config const &config) noexcept -> resolved_scheduling
Definition native.hpp:353
auto apply_scheduling_config(NativeHandle handle, native_thread_id tid, native_scheduling_config const &config) -> expected< void, std::error_code >
Definition native.hpp:814
auto to_string(native_scheduling_policy policy) -> std::string
String conversion utilities.
Definition native.hpp:751
@ realtime_fifo
Request POSIX FIFO realtime scheduling.
POSIX and Linux native thread-control implementation fragment.
auto read_scheduling_policy(pthread_t handle) -> std::optional< native_scheduling_policy >
Definition posix.hpp:106
auto read_affinity(pthread_t handle) -> expected< native_thread_affinity, std::error_code >
Definition posix.hpp:78
auto apply_scheduling_policy(pthread_t handle, native_scheduling_policy policy, native_thread_priority priority) -> expected< void, std::error_code >
Definition posix.hpp:38
auto apply_nice_value(pid_t tid, int nice_value) -> expected< void, std::error_code >
Definition posix.hpp:125
auto read_nice_value(pid_t tid) -> expected< int, std::error_code >
Definition posix.hpp:206
auto apply_affinity(pthread_t handle, native_thread_affinity const &affinity) -> expected< void, std::error_code >
Definition posix.hpp:50
std::optional< native_scheduling_config > scheduling
Definition native.hpp:607
std::optional< native_thread_affinity > affinity
Definition native.hpp:608
std::optional< std::string > name
Definition native.hpp:606
Windows and MinGW native thread-control implementation fragment.
auto apply_windows_thread_priority(HANDLE handle, int priority) -> expected< void, std::error_code >
Definition windows.hpp:24