ThreadSchedule 1.0.0
Modern C++ thread management library
Loading...
Searching...
No Matches
scheduler_policy.hpp
1#pragma once
2
3#include "expected.hpp"
4#include <algorithm>
5#include <cstdint>
6#include <sstream>
7#include <string>
8#include <system_error>
9#include <vector>
10
11#ifdef _WIN32
12#include <windows.h>
13#else
14#include <sched.h>
15#include <sys/resource.h>
16#endif
17
18namespace threadschedule
19{
20// expected/result are provided by expected.hpp
21
25enum class SchedulingPolicy : std::uint_fast8_t
26{
27#ifdef _WIN32
28 // Windows doesn't have the same scheduling policies as Linux
29 // We'll use generic values
30 OTHER = 0,
31 FIFO = 1,
32 RR = 2,
33 BATCH = 3,
34 IDLE = 4
35#else
36 OTHER = SCHED_OTHER,
37 FIFO = SCHED_FIFO,
38 RR = SCHED_RR,
39 BATCH = SCHED_BATCH,
40 IDLE = SCHED_IDLE,
41#ifdef SCHED_DEADLINE
42 DEADLINE = SCHED_DEADLINE
43#endif
44#endif
45};
46
50class ThreadPriority
51{
52 public:
53 constexpr explicit ThreadPriority(int priority = 0) : priority_(std::clamp(priority, min_priority, max_priority))
54 {
55 }
56
57 [[nodiscard]] constexpr auto value() const noexcept -> int
58 {
59 return priority_;
60 }
61 [[nodiscard]] constexpr auto is_valid() const noexcept -> bool
62 {
63 return priority_ >= min_priority && priority_ <= max_priority;
64 }
65
66 // Factory methods for common priorities
67 static constexpr auto lowest() -> ThreadPriority
68 {
69 return ThreadPriority(min_priority);
70 }
71 static constexpr auto normal() -> ThreadPriority
72 {
73 return ThreadPriority(0);
74 }
75 static constexpr auto highest() -> ThreadPriority
76 {
77 return ThreadPriority(max_priority);
78 }
79
80 // Comparison operators
81 [[nodiscard]] auto operator==(ThreadPriority const& other) const -> bool
82 {
83 return priority_ == other.priority_;
84 }
85 [[nodiscard]] auto operator!=(ThreadPriority const& other) const -> bool
86 {
87 return priority_ != other.priority_;
88 }
89 [[nodiscard]] auto operator<(ThreadPriority const& other) const -> bool
90 {
91 return priority_ < other.priority_;
92 }
93 [[nodiscard]] auto operator<=(ThreadPriority const& other) const -> bool
94 {
95 return priority_ <= other.priority_;
96 }
97 [[nodiscard]] auto operator>(ThreadPriority const& other) const -> bool
98 {
99 return priority_ > other.priority_;
100 }
101 [[nodiscard]] auto operator>=(ThreadPriority const& other) const -> bool
102 {
103 return priority_ >= other.priority_;
104 }
105
106 [[nodiscard]] auto to_string() const -> std::string
107 {
108 std::ostringstream oss;
109 oss << "ThreadPriority(" << priority_ << ")";
110 return oss.str();
111 }
112
113 private:
114 static constexpr int min_priority = -20;
115 static constexpr int max_priority = 19;
116 int priority_;
117};
118
122class ThreadAffinity
123{
124 public:
125 ThreadAffinity()
126 {
127#ifdef _WIN32
128 group_ = 0;
129 mask_ = 0;
130#else
131 CPU_ZERO(&cpuset_);
132#endif
133 }
134
135 explicit ThreadAffinity(std::vector<int> const& cpus) : ThreadAffinity()
136 {
137 for (int cpu : cpus)
138 {
139 add_cpu(cpu);
140 }
141 }
142
143 // Adds a CPU index. On Windows, indices >= 64 select group = cpu/64 automatically.
144 void add_cpu(int cpu)
145 {
146#ifdef _WIN32
147 if (cpu < 0)
148 return;
149 WORD g = static_cast<WORD>(cpu / 64);
150 int bit = cpu % 64;
151 if (!has_any())
152 {
153 group_ = g;
154 }
155 if (g != group_)
156 {
157 // Single-group affinity object: ignore CPUs from other groups
158 return;
159 }
160 mask_ |= (static_cast<unsigned long long>(1) << bit);
161#else
162 if (cpu >= 0 && cpu < CPU_SETSIZE)
163 {
164 CPU_SET(cpu, &cpuset_);
165 }
166#endif
167 }
168
169 void remove_cpu(int cpu)
170 {
171#ifdef _WIN32
172 if (cpu < 0)
173 return;
174 WORD g = static_cast<WORD>(cpu / 64);
175 int bit = cpu % 64;
176 if (g == group_)
177 {
178 mask_ &= ~(static_cast<unsigned long long>(1) << bit);
179 }
180#else
181 if (cpu >= 0 && cpu < CPU_SETSIZE)
182 {
183 CPU_CLR(cpu, &cpuset_);
184 }
185#endif
186 }
187
188 [[nodiscard]] auto is_set(int cpu) const -> bool
189 {
190#ifdef _WIN32
191 if (cpu < 0)
192 return false;
193 WORD g = static_cast<WORD>(cpu / 64);
194 int bit = cpu % 64;
195 return g == group_ && (mask_ & (static_cast<unsigned long long>(1) << bit)) != 0;
196#else
197 return cpu >= 0 && cpu < CPU_SETSIZE && CPU_ISSET(cpu, &cpuset_);
198#endif
199 }
200
201 [[nodiscard]] auto has_cpu(int cpu) const -> bool
202 {
203 return is_set(cpu);
204 }
205
206 void clear()
207 {
208#ifdef _WIN32
209 mask_ = 0;
210#else
211 CPU_ZERO(&cpuset_);
212#endif
213 }
214
215 [[nodiscard]] auto get_cpus() const -> std::vector<int>
216 {
217 std::vector<int> cpus;
218#ifdef _WIN32
219 for (int i = 0; i < 64; ++i)
220 {
221 if (mask_ & (static_cast<unsigned long long>(1) << i))
222 {
223 cpus.push_back(static_cast<int>(group_) * 64 + i);
224 }
225 }
226#else
227 for (int i = 0; i < CPU_SETSIZE; ++i)
228 {
229 if (CPU_ISSET(i, &cpuset_))
230 {
231 cpus.push_back(i);
232 }
233 }
234#endif
235 return cpus;
236 }
237
238#ifdef _WIN32
239 [[nodiscard]] unsigned long long get_mask() const
240 {
241 return mask_;
242 }
243 [[nodiscard]] WORD get_group() const
244 {
245 return group_;
246 }
247 [[nodiscard]] bool has_any() const
248 {
249 return mask_ != 0;
250 }
251#else
252 [[nodiscard]] auto native_handle() const -> cpu_set_t const&
253 {
254 return cpuset_;
255 }
256#endif
257
258 [[nodiscard]] auto to_string() const -> std::string
259 {
260 auto cpus = get_cpus();
261 std::ostringstream oss;
262 oss << "ThreadAffinity({";
263 for (size_t i = 0; i < cpus.size(); ++i)
264 {
265 if (i > 0)
266 oss << ", ";
267 oss << cpus[i];
268 }
269 oss << "})";
270 return oss.str();
271 }
272
273 private:
274#ifdef _WIN32
275 WORD group_;
276 unsigned long long mask_;
277#else
278 cpu_set_t cpuset_;
279#endif
280};
281
286{
287 public:
288#ifdef _WIN32
289 // Windows doesn't use sched_param, but we'll define a compatible type
290 struct sched_param_win
291 {
292 int sched_priority;
293 };
294
295 static expected<sched_param_win, std::error_code> create_for_policy(SchedulingPolicy policy,
296 ThreadPriority priority)
297 {
298 sched_param_win param{};
299 // On Windows, priority is directly used
300 param.sched_priority = priority.value();
301 return param;
302 }
303
304 static expected<int, std::error_code> get_priority_range(SchedulingPolicy policy)
305 {
306 // Windows thread priorities range from -15 to +15
307 return 30;
308 }
309#else
310 static auto create_for_policy(SchedulingPolicy policy, ThreadPriority priority)
312 {
313 sched_param param{};
314
315 int const policy_int = static_cast<int>(policy);
316 int const min_prio = sched_get_priority_min(policy_int);
317 int const max_prio = sched_get_priority_max(policy_int);
318
319 if (min_prio == -1 || max_prio == -1)
320 {
321 return unexpected(std::make_error_code(std::errc::invalid_argument));
322 }
323
324 param.sched_priority = std::clamp(priority.value(), min_prio, max_prio);
325 return param;
326 }
327
328 static auto get_priority_range(SchedulingPolicy policy) -> expected<int, std::error_code>
329 {
330 int const policy_int = static_cast<int>(policy);
331 int const min_prio = sched_get_priority_min(policy_int);
332 int const max_prio = sched_get_priority_max(policy_int);
333
334 if (min_prio == -1 || max_prio == -1)
335 {
336 return unexpected(std::make_error_code(std::errc::invalid_argument));
337 }
338
339 return max_prio - min_prio;
340 }
341#endif
342};
343
347inline auto to_string(SchedulingPolicy policy) -> std::string
348{
349 switch (policy)
350 {
351 case SchedulingPolicy::OTHER:
352 return "OTHER";
353 case SchedulingPolicy::FIFO:
354 return "FIFO";
355 case SchedulingPolicy::RR:
356 return "RR";
357 case SchedulingPolicy::BATCH:
358 return "BATCH";
359 case SchedulingPolicy::IDLE:
360 return "IDLE";
361#if defined(SCHED_DEADLINE) && !defined(_WIN32)
362 case SchedulingPolicy::DEADLINE:
363 return "DEADLINE";
364#endif
365 default:
366 return "UNKNOWN";
367 }
368}
369
370} // namespace threadschedule
Scheduler parameter utilities.
Thread priority wrapper with validation.