ThreadSchedule 3.0.0
Modern C++ thread management library
Loading...
Searching...
No Matches
thread_registry_backend.hpp
Go to the documentation of this file.
1#pragma once
2
7#include "../../expected.hpp"
8#include "../../export.hpp"
9#include "../callable/copyable_function.hpp"
12
13#include <memory>
14#include <optional>
15#include <shared_mutex>
16#include <string>
17#include <system_error>
18#include <thread>
19#include <type_traits>
20#include <unordered_map>
21#include <utility>
22#include <vector>
23
24namespace threadschedule
25{
26class auto_register_current_thread;
27}
28
30{
31
79class thread_registry_backend : public detail::query_facade_mixin<thread_registry_backend>
80{
81public:
84 {
85 std::unique_lock<std::shared_mutex> lock(mutex_);
86 for (auto& entry : threads_)
87 if (entry.second.control)
88 entry.second.control->deactivate();
89 }
92
93 void
94 register_current_thread(std::string name = std::string(), std::string component = std::string())
95 {
98 info.std_id = std::this_thread::get_id();
99 info.name = std::move(name);
100 info.component = std::move(component);
101 info.alive = true;
102 (void)try_register(std::move(info));
103 }
104
105 void
106 register_current_thread(std::shared_ptr<thread_control_block> const& control_block, std::string name = std::string(),
107 std::string component = std::string())
108 {
109 if (!control_block)
110 return;
112 info.tid = control_block->tid();
113 info.std_id = control_block->std_id();
114 info.name = std::move(name);
115 info.component = std::move(component);
116 info.alive = true;
117 info.control = control_block;
118 (void)try_register(std::move(info));
119 }
120
121 void
123 {
124 unregister_thread(thread_info::get_thread_id());
125 }
126
127private:
128 void
129 unregister_thread(native_thread_id tid, thread_control_block const* expected_control = nullptr) noexcept
130 {
131 try
132 {
133 std::unique_lock<std::shared_mutex> lock(mutex_);
134 auto it = threads_.find(tid);
135 if (it == threads_.end() || (expected_control != nullptr && it->second.control.get() != expected_control))
136 return;
137
138 auto info = std::move(it->second);
139 info.alive = false;
140 if (info.control)
141 info.control->deactivate();
142 threads_.erase(it);
143
145 if (on_unregister_)
146 {
147 try
148 {
149 cb = on_unregister_;
150 }
151 catch (...)
152 {
153 }
154 }
155 lock.unlock();
156 if (cb)
157 {
158 try
159 {
160 cb(info);
161 }
162 catch (...)
163 {
164 }
165 }
166 }
167 catch (...)
168 {
169 }
170 }
171
172public:
173 // Lookup
174 [[nodiscard]] auto
175 get(native_thread_id tid) const -> std::optional<registered_thread_info_backend>
176 {
177 std::shared_lock<std::shared_mutex> lock(mutex_);
178 auto it = threads_.find(tid);
179 if (it == threads_.end())
180 return std::nullopt;
181 return it->second;
182 }
183
221 {
222 public:
223 explicit query_view(std::vector<registered_thread_info_backend> entries) : entries_(std::move(entries)) {}
224
225 template <typename Predicate>
226 auto
228 {
229 std::vector<registered_thread_info_backend> filtered;
230 filtered.reserve(entries_.size());
231 for (auto const& entry : entries_)
232 {
233 if (pred(entry))
234 filtered.push_back(entry);
235 }
236 return query_view(std::move(filtered));
237 }
238
239 template <typename Fn>
240 void
241 for_each(Fn&& fn) const
242 {
243 for (auto const& entry : entries_)
244 {
245 fn(entry);
246 }
247 }
248
249 [[nodiscard]] auto
250 count() const -> size_t
251 {
252 return entries_.size();
253 }
254
255 [[nodiscard]] auto
256 empty() const -> bool
257 {
258 return entries_.empty();
259 }
260
261 [[nodiscard]] auto
263 {
264 return entries_;
265 }
266
267 // Transform entries to a vector of another type
268 template <typename Fn>
269 [[nodiscard]] auto
270 map(Fn&& fn) const -> std::vector<std::invoke_result_t<Fn, registered_thread_info_backend const&>>
271 {
272 std::vector<std::invoke_result_t<Fn, registered_thread_info_backend const&>> result;
273 result.reserve(entries_.size());
274 for (auto const& entry : entries_)
275 {
276 result.push_back(fn(entry));
277 }
278 return result;
279 }
280
281 // Find first entry matching predicate
282 template <typename Predicate>
283 [[nodiscard]] auto
284 find_if(Predicate&& pred) const -> std::optional<registered_thread_info_backend>
285 {
286 for (auto const& entry : entries_)
287 {
288 if (pred(entry))
289 return entry;
290 }
291 return std::nullopt;
292 }
293
294 template <typename Predicate>
295 [[nodiscard]] auto
296 any(Predicate&& pred) const -> bool
297 {
298 for (auto const& entry : entries_)
299 {
300 if (pred(entry))
301 return true;
302 }
303 return false;
304 }
305
306 template <typename Predicate>
307 [[nodiscard]] auto
308 all(Predicate&& pred) const -> bool
309 {
310 for (auto const& entry : entries_)
311 {
312 if (!pred(entry))
313 return false;
314 }
315 return true;
316 }
317
318 template <typename Predicate>
319 [[nodiscard]] auto
320 none(Predicate&& pred) const -> bool
321 {
322 return !any(std::forward<Predicate>(pred));
323 }
324
325 [[nodiscard]] auto
326 take(size_t n) const -> query_view
327 {
328 auto result = entries_;
329 if (result.size() > n)
330 result.resize(n);
331 return query_view(std::move(result));
332 }
333
334 [[nodiscard]] auto
335 skip(size_t n) const -> query_view
336 {
337 std::vector<registered_thread_info_backend> result;
338 if (n < entries_.size())
339 {
340 result.assign(entries_.begin() + n, entries_.end());
341 }
342 return query_view(std::move(result));
343 }
344
345 private:
346 std::vector<registered_thread_info_backend> entries_;
347 };
348
349 // Create a query view over all registered threads
350 [[nodiscard]] auto
352 {
353 std::vector<registered_thread_info_backend> snapshot;
354 std::shared_lock<std::shared_mutex> lock(mutex_);
355 snapshot.reserve(threads_.size());
356 for (auto const& kv : threads_)
357 {
358 snapshot.push_back(kv.second);
359 }
360 return query_view(std::move(snapshot));
361 }
362
363 [[nodiscard]] auto
365 {
366 auto blk = lock_block(tid);
367 if (!blk)
368 return unexpected(std::make_error_code(std::errc::no_such_process));
369 return blk->set_affinity(affinity);
370 }
371
372 [[nodiscard]] auto
374 {
375 auto blk = lock_block(tid);
376 if (!blk)
377 return unexpected(std::make_error_code(std::errc::no_such_process));
378 return blk->set_priority(priority);
379 }
380
381 [[nodiscard]] auto
383 {
384 auto blk = lock_block(tid);
385 if (!blk)
386 return unexpected(std::make_error_code(std::errc::no_such_process));
387 return blk->set_nice_value(nice_value);
388 }
389
390 [[nodiscard]] auto
392 {
393 auto blk = lock_block(tid);
394 if (!blk)
395 return unexpected(std::make_error_code(std::errc::no_such_process));
396 return blk->get_nice_value();
397 }
398
399 [[nodiscard]] auto
402 {
403 auto blk = lock_block(tid);
404 if (!blk)
405 return unexpected(std::make_error_code(std::errc::no_such_process));
406 return blk->set_scheduling_policy(policy, priority);
407 }
408
409 [[nodiscard]] auto
411 {
412 auto blk = lock_block(tid);
413 if (!blk)
414 return unexpected(std::make_error_code(std::errc::no_such_process));
415 return blk->configure(config);
416 }
417
418 [[nodiscard]] auto
420 {
421 if (config.name)
422 {
423 auto named = set_name(tid, *config.name);
424 if (!named)
425 return unexpected(named.error());
426 }
427 if (config.scheduling)
428 {
429 auto scheduled = configure(tid, *config.scheduling);
430 if (!scheduled)
431 return unexpected(scheduled.error());
432 }
433 if (config.affinity.has_value())
434 return set_affinity(tid, *config.affinity);
435 return {};
436 }
437
438 [[nodiscard]] auto
439 set_name(native_thread_id tid, std::string const& name) const -> expected<void, std::error_code>
440 {
441 auto blk = lock_block(tid);
442 if (!blk)
443 return unexpected(std::make_error_code(std::errc::no_such_process));
444 return blk->set_name(name);
445 }
446
447 void
448 update_registered_name(native_thread_id tid, std::string const& name)
449 {
450 std::unique_lock<std::shared_mutex> lock(mutex_);
451 auto const found = threads_.find(tid);
452 if (found != threads_.end())
453 found->second.name = name;
454 }
455
456 // Register/unregister hooks (system integration)
457 void
459 {
460 std::unique_lock<std::shared_mutex> lock(mutex_);
461 on_register_ = std::move(cb);
462 }
463
464 template <typename Callback, std::enable_if_t<!std::is_same_v<std::decay_t<Callback>, registry_callback>, int> = 0>
465 void
467 {
468 static_assert(std::is_invocable_r_v<void, Callback&, registered_thread_info_backend const&>,
469 "Register callback must be invocable with "
470 "registered_thread_info_backend "
471 "const&");
472 std::unique_lock<std::shared_mutex> lock(mutex_);
473 on_register_
474 = detail::make_copyable_function<void(registered_thread_info_backend const&)>(std::forward<Callback>(cb));
475 }
476
477 void
479 {
480 std::unique_lock<std::shared_mutex> lock(mutex_);
481 on_unregister_ = std::move(cb);
482 }
483
484 template <typename Callback, std::enable_if_t<!std::is_same_v<std::decay_t<Callback>, registry_callback>, int> = 0>
485 void
487 {
488 static_assert(std::is_invocable_r_v<void, Callback&, registered_thread_info_backend const&>,
489 "Unregister callback must be invocable with "
490 "registered_thread_info_backend "
491 "const&");
492 std::unique_lock<std::shared_mutex> lock(mutex_);
493 on_unregister_
494 = detail::make_copyable_function<void(registered_thread_info_backend const&)>(std::forward<Callback>(cb));
495 }
496
497private:
498 [[nodiscard]] auto
499 try_register(registered_thread_info_backend info) -> bool
500 {
501 std::unique_lock<std::shared_mutex> lock(mutex_);
502 auto it = threads_.find(info.tid);
503 if (it != threads_.end())
504 return false;
505 auto stored = info;
506 threads_.emplace(info.tid, std::move(info));
507 if (on_register_)
508 {
510 try
511 {
512 cb = on_register_;
513 }
514 catch (...)
515 {
516 }
517 lock.unlock();
518 if (cb)
519 {
520 try
521 {
522 cb(stored);
523 }
524 catch (...)
525 {
526 }
527 }
528 }
529 return true;
530 }
531
532 [[nodiscard]] auto
533 register_guard(std::shared_ptr<thread_control_block> const& control_block, std::string const& name,
534 std::string const& component) -> bool
535 {
536 if (!control_block)
537 return false;
538 registered_thread_info_backend info;
539 info.tid = control_block->tid();
540 info.std_id = control_block->std_id();
541 info.name = name;
542 info.component = component;
543 info.alive = true;
544 info.control = control_block;
545 return try_register(std::move(info));
546 }
547
548 [[nodiscard]] auto
549 lock_block(native_thread_id tid) const -> std::shared_ptr<thread_control_block>
550 {
551 std::shared_lock<std::shared_mutex> lock(mutex_);
552 auto it = threads_.find(tid);
553 if (it == threads_.end())
554 return nullptr;
555 return it->second.control;
556 }
557 mutable std::shared_mutex mutex_;
558 std::unordered_map<native_thread_id, registered_thread_info_backend> threads_;
559
560 registry_callback on_register_;
561 registry_callback on_unregister_;
562
564 friend class ::threadschedule::auto_register_current_thread;
565};
566
588{
590 explicit external_registry_binding_state(std::shared_ptr<thread_registry_backend> value)
591 : registry(value.get()), owner(std::move(value))
592 {
593 }
594
595 void
596 replace(std::shared_ptr<thread_registry_backend> value)
597 {
598 owner = std::move(value);
599 registry = owner.get();
600 }
601
603 std::shared_ptr<thread_registry_backend> owner;
604 std::shared_ptr<external_registry_binding_state> previous;
605 bool active{ true };
606};
607
608#if defined(THREADSCHEDULE_RUNTIME)
609THREADSCHEDULE_API auto runtime_registry() -> thread_registry_backend&;
610THREADSCHEDULE_API void runtime_set_external_registry(thread_registry_backend* reg);
611THREADSCHEDULE_API auto runtime_exchange_external_registry(thread_registry_backend* reg) -> thread_registry_backend*;
612THREADSCHEDULE_API void runtime_set_external_registry_state(std::shared_ptr<external_registry_binding_state> state);
613THREADSCHEDULE_API auto runtime_exchange_external_registry_state(std::shared_ptr<external_registry_binding_state> state)
614 -> std::shared_ptr<external_registry_binding_state>;
615THREADSCHEDULE_API auto runtime_external_registry_state() -> std::shared_ptr<external_registry_binding_state>;
616#else
618struct runtime_registry_storage
619{
620 std::shared_ptr<external_registry_binding_state> external;
621};
622
623inline auto
624registry_storage() -> runtime_registry_storage&
625{
626 static runtime_registry_storage storage;
627 return storage;
628}
631inline auto
633{
634 auto const& storage = registry_storage();
635 if (storage.external && storage.external->registry != nullptr)
636 return *storage.external->registry;
637 static thread_registry_backend local;
638 return local;
639}
640
641inline void
643{
644 auto& storage = registry_storage();
645 storage.external = reg == nullptr ? nullptr : std::make_shared<external_registry_binding_state>(reg);
646}
647
648inline auto
650{
651 auto& storage = registry_storage();
652 auto* const previous = storage.external ? storage.external->registry : nullptr;
654 return previous;
655}
656
657inline void
658runtime_set_external_registry_state(std::shared_ptr<external_registry_binding_state> state)
659{
660 registry_storage().external = std::move(state);
661}
662
663inline auto
664runtime_exchange_external_registry_state(std::shared_ptr<external_registry_binding_state> state)
665 -> std::shared_ptr<external_registry_binding_state>
666{
667 auto& storage = registry_storage();
668 auto previous = std::move(storage.external);
669 storage.external = std::move(state);
670 return previous;
671}
672
673inline auto
674runtime_external_registry_state() -> std::shared_ptr<external_registry_binding_state>
675{
676 return registry_storage().external;
677}
678#endif
679
680} // namespace threadschedule::detail
Manages a set of CPU indices to which a thread may be bound.
Definition native.hpp:433
Value-semantic wrapper for a thread scheduling priority.
Definition native.hpp:140
CRTP mixin that provides functional-style query facade methods.
auto map(Fn &&fn) const -> std::vector< std::invoke_result_t< Fn, registered_thread_info_backend const & > >
Internal movable RAII guard for worker registration.
Per-thread control handle for OS-level scheduling operations.
static auto get_thread_id() -> native_thread_id
Lazy, functional-style query/filter view over a snapshot of registered threads.
query_view(std::vector< registered_thread_info_backend > entries)
auto map(Fn &&fn) const -> std::vector< std::invoke_result_t< Fn, registered_thread_info_backend const & > >
auto find_if(Predicate &&pred) const -> std::optional< registered_thread_info_backend >
auto entries() const -> std::vector< registered_thread_info_backend > const &
Central registry of threads indexed by OS-level thread ID (native_thread_id).
auto get(native_thread_id tid) const -> std::optional< registered_thread_info_backend >
auto get_nice_value(native_thread_id tid) const -> expected< int, std::error_code >
auto set_nice_value(native_thread_id tid, int nice_value) const -> expected< void, std::error_code >
void register_current_thread(std::shared_ptr< thread_control_block > const &control_block, std::string name=std::string(), std::string component=std::string())
auto configure(native_thread_id tid, native_thread_config const &config) const -> expected< void, std::error_code >
auto set_priority(native_thread_id tid, native_thread_priority priority) const -> expected< void, std::error_code >
auto operator=(thread_registry_backend const &) -> thread_registry_backend &=delete
auto configure(native_thread_id tid, native_scheduling_config const &config) const -> expected< void, std::error_code >
auto set_affinity(native_thread_id tid, native_thread_affinity const &affinity) const -> expected< void, std::error_code >
auto set_scheduling_policy(native_thread_id tid, native_scheduling_policy policy, native_thread_priority priority) const -> expected< void, std::error_code >
void update_registered_name(native_thread_id tid, std::string const &name)
auto set_name(native_thread_id tid, std::string const &name) const -> expected< void, std::error_code >
thread_registry_backend(thread_registry_backend const &)=delete
void register_current_thread(std::string name=std::string(), std::string component=std::string())
Strongly-typed nice value in the POSIX range $[-20, 19]$.
#define THREADSCHEDULE_API
Symbol visibility/import-export marker for runtime builds.
Definition export.hpp:22
Aggregates multiple thread_registry_backend instances into a single queryable view.
native_scheduling_policy
Enumeration of available thread scheduling policies.
Definition native.hpp:85
detail::copyable_function< void(registered_thread_info_backend const &)> registry_callback
auto runtime_exchange_external_registry_state(std::shared_ptr< external_registry_binding_state > state) -> std::shared_ptr< external_registry_binding_state >
auto runtime_external_registry_state() -> std::shared_ptr< external_registry_binding_state >
void runtime_set_external_registry_state(std::shared_ptr< external_registry_binding_state > state)
void runtime_set_external_registry(thread_registry_backend *reg)
auto runtime_exchange_external_registry(thread_registry_backend *reg) -> thread_registry_backend *
auto runtime_registry() -> thread_registry_backend &
expected< T, std::error_code > result
Standard result type used by public APIs.
Definition result.hpp:22
Snapshot query facade shared by registry implementations.
std::shared_ptr< external_registry_binding_state > previous
external_registry_binding_state(std::shared_ptr< thread_registry_backend > value)
external_registry_binding_state(thread_registry_backend *value) noexcept
void replace(std::shared_ptr< thread_registry_backend > value)
Snapshot of metadata for a single registered thread.
Native thread metadata and lifecycle-safe control blocks.