ThreadSchedule 3.0.0
Modern C++ thread management library
Loading...
Searching...
No Matches
thread_registry.hpp
Go to the documentation of this file.
1#pragma once
2
10#include "thread_config.hpp"
11#include "thread_id.hpp"
12
13#include <algorithm>
14#include <cstdint>
15#include <limits>
16#include <memory>
17#include <stdexcept>
18#include <string>
19#include <thread>
20#include <utility>
21#include <vector>
22
23namespace threadschedule
24{
25namespace advanced
26{
27class composite_thread_registry;
28}
29
34{
38 std::thread::id std_id;
40 std::string name;
42 std::string component;
44 bool alive{ false };
45};
46
47class thread_registry;
48class auto_register_current_thread;
49class global_registry_binding;
50
51[[nodiscard]] auto global_registry() -> thread_registry&;
52
61{
62public:
64 thread_registry() : owned_(std::make_shared<detail::thread_registry_backend>()) {}
65
66 thread_registry(thread_registry&&) noexcept = default;
67 auto
68 operator=(thread_registry&& other) -> thread_registry&
69 {
70 if (this != &other)
71 {
72 bool const keep_global_proxy = global_;
73 auto* const current = owned_.get();
74 bool const is_external = current != nullptr && &detail::runtime_registry() == current;
75 retired_.reserve(retired_.size() + (owned_ != nullptr ? 1u : 0u) + other.retired_.size());
76 bindings_.reserve(bindings_.size() + other.bindings_.size());
77 // A registration guard can outlive its entry (for example after an
78 // explicit unregister) and still retain the backend address for its
79 // destructor. Keep every replaced backend alive, even when it is
80 // currently empty.
81 if (owned_ != nullptr)
82 retired_.push_back(std::move(owned_));
83 for (auto& backend : other.retired_)
84 retired_.push_back(std::move(backend));
85 other.retired_.clear();
86 for (auto& binding : other.bindings_)
87 bindings_.push_back(std::move(binding));
88 other.bindings_.clear();
89 owned_ = std::move(other.owned_);
90 retarget_bindings();
91 if (keep_global_proxy)
92 {
93 // global_registry() is a permanent facade over registry(). A
94 // public move-assignment must not turn that singleton into an
95 // unrelated owning registry.
96 global_ = true;
97 if (other.global_)
99 else if (auto state = detail::runtime_external_registry_state())
100 state->replace(owned_);
101 else
103 std::make_shared<detail::external_registry_binding_state>(owned_));
104 }
105 else
106 {
107 if (is_external)
108 {
109 if (other.global_)
111 else if (auto state = detail::runtime_external_registry_state())
112 state->replace(owned_);
113 }
114 global_ = other.global_;
115 }
116 }
117 return *this;
118 }
120 auto operator=(thread_registry const&) -> thread_registry& = delete;
121
123 static auto
128
134 auto
135 register_current_thread(std::string name = {}, std::string component = {}) -> result<void>
136 {
137 if (!has_native())
138 return unexpected(std::make_error_code(std::errc::operation_canceled));
139 return detail::try_result(
140 [&]() -> result<void>
141 {
143 native().register_current_thread(control, std::move(name), std::move(component));
144 return {};
145 });
146 }
147
149 auto
151 {
152 if (!has_native())
153 return unexpected(std::make_error_code(std::errc::operation_canceled));
154 return detail::try_result(
155 [&]() -> result<void>
156 {
157 native().unregister_current_thread();
158 return {};
159 });
160 }
161
163 [[nodiscard]] auto
164 count() const -> std::size_t
165 {
166 return has_native() ? native().count() : 0;
167 }
168
170 [[nodiscard]] auto
171 empty() const -> bool
172 {
173 return !has_native() || native().empty();
174 }
175
180 [[nodiscard]] auto
182 {
183 if (!has_native())
184 return std::vector<registered_thread>{};
185 return detail::try_result(
186 [&]() -> result<std::vector<registered_thread>>
187 {
188 auto entries = native().query().entries();
189 std::vector<registered_thread> result_entries;
190 result_entries.reserve(entries.size());
191 for (auto const& entry : entries)
192 {
193 result_entries.push_back({ detail::thread_id_access::make(static_cast<std::uint64_t>(entry.tid)),
194 entry.std_id, entry.name, entry.component, entry.alive });
195 }
196 return result_entries;
197 });
198 }
199
205 auto
207 {
208 if (!has_native())
209 return unexpected(std::make_error_code(std::errc::operation_canceled));
210 auto const native_id = checked_native_id(id);
211 if (!native_id)
212 return unexpected(native_id.error());
213 return detail::try_result([&]() -> result<void>
214 { return native().configure(native_id.value(), detail::to_native(config)); });
215 }
216
218 auto
220 {
221 return set_nice(id, nice_value{ static_cast<int>(level) });
222 }
223
225 auto
227 {
228 if (!has_native())
229 return unexpected(std::make_error_code(std::errc::operation_canceled));
230 auto const native_id = checked_native_id(id);
231 if (!native_id)
232 return unexpected(native_id.error());
233 return detail::try_result(
234 [&]() -> result<void>
235 { return native().configure(native_id.value(), detail::native_schedule::posix_nice(value.value())); });
236 }
237
239 [[nodiscard]] auto
241 {
242 if (!has_native())
243 return unexpected(std::make_error_code(std::errc::operation_canceled));
244 auto const native_id = checked_native_id(id);
245 if (!native_id)
246 return unexpected(native_id.error());
247 return detail::try_result(
249 {
250 auto value = native().get_nice_value(native_id.value());
251 if (!value)
252 return unexpected(value.error());
253 return detail::to_priority_level(value.value());
254 });
255 }
256
258 [[nodiscard]] auto
260 {
261 if (!has_native())
262 return unexpected(std::make_error_code(std::errc::operation_canceled));
263 auto const native_id = checked_native_id(id);
264 if (!native_id)
265 return unexpected(native_id.error());
266 return detail::try_result(
267 [&]() -> result<nice_value>
268 {
269 auto value = native().get_nice_value(native_id.value());
270 return detail::portable_thread_control::get_nice(std::move(value));
271 });
272 }
273
274private:
275 [[nodiscard]] static auto
276 checked_native_id(thread_id id) noexcept -> result<detail::native_thread_id>
277 {
278 auto const value = detail::thread_id_access::value(id);
279 auto const maximum = static_cast<std::uint64_t>((std::numeric_limits<detail::native_thread_id>::max)());
280 if (value > maximum)
281 return unexpected(std::make_error_code(std::errc::invalid_argument));
282 return static_cast<detail::native_thread_id>(value);
283 }
284
285 struct global_tag
286 {
287 };
288
289 explicit thread_registry(global_tag /*unused*/) noexcept : global_(true) {}
290
291 [[nodiscard]] auto
292 has_native() const noexcept -> bool
293 {
294 return global_ || owned_ != nullptr;
295 }
296
297 [[nodiscard]] auto
298 native() -> detail::thread_registry_backend&
299 {
300 return global_ ? detail::runtime_registry() : *owned_;
301 }
302
303 [[nodiscard]] auto
304 native() const -> detail::thread_registry_backend const&
305 {
306 return global_ ? detail::runtime_registry() : *owned_;
307 }
308
309 std::shared_ptr<detail::thread_registry_backend> owned_;
310 std::vector<std::shared_ptr<detail::thread_registry_backend>> retired_;
311 std::vector<std::weak_ptr<detail::external_registry_binding_state>> bindings_;
312 bool global_{ false };
313
314 void
315 retarget_bindings()
316 {
317 bindings_.erase(
318 std::remove_if(bindings_.begin(), bindings_.end(), [](auto const& binding) { return binding.expired(); }),
319 bindings_.end());
320 for (auto const& binding : bindings_)
321 if (auto state = binding.lock())
322 state->replace(owned_);
323 }
324
325 void
326 track_binding(std::shared_ptr<detail::external_registry_binding_state> const& state)
327 {
328 bindings_.erase(
329 std::remove_if(bindings_.begin(), bindings_.end(), [](auto const& binding) { return binding.expired(); }),
330 bindings_.end());
331 bindings_.push_back(state);
332 }
333
334 friend auto global_registry() -> thread_registry&;
338};
339
340[[nodiscard]] inline auto
342{
343 static thread_registry value(thread_registry::global_tag{});
344 return value;
345}
346
359{
360public:
367 {
368 if (registry.global_ || registry.owned_ == nullptr)
369 throw std::invalid_argument("global registry facade cannot be bound as an external registry");
370 owner_ = registry.owned_;
371 state_ = std::make_shared<detail::external_registry_binding_state>(owner_);
372 registry.track_binding(state_);
373 state_->previous = detail::runtime_exchange_external_registry_state(state_);
374 }
375
377 {
378 reset();
379 }
380
383
385 : owner_(std::move(other.owner_)), state_(std::move(other.state_))
386 {
387 }
388
389 auto
391 {
392 if (this != &other)
393 {
394 reset();
395 owner_ = std::move(other.owner_);
396 state_ = std::move(other.state_);
397 }
398 return *this;
399 }
400
401private:
402 void
403 reset() noexcept
404 {
405 if (state_)
406 {
407 state_->active = false;
409 {
410 auto previous = state_->previous;
411 while (previous && !previous->active)
412 previous = previous->previous;
414 }
415 }
416 owner_.reset();
417 state_.reset();
418 }
419
420 std::shared_ptr<detail::thread_registry_backend> owner_;
421 std::shared_ptr<detail::external_registry_binding_state> state_;
422};
423
426{
427public:
428 explicit auto_register_current_thread(std::string name = {}, std::string component = {})
429 : auto_register_current_thread(global_registry(), std::move(name), std::move(component))
430 {
431 }
432
433 explicit auto_register_current_thread(thread_registry& registry, std::string name = {}, std::string component = {})
434 {
435 if (!registry.has_native())
436 throw std::system_error(std::make_error_code(std::errc::operation_canceled),
437 "auto_register_current_thread: moved-from registry");
438 if (registry.global_)
439 {
441 registry_owner_ = state->owner;
442 }
443 else
444 {
445 registry_owner_ = registry.owned_;
446 }
447 registry_ = registry_owner_ ? registry_owner_.get() : &registry.native();
449 native_id_ = control->tid();
450 (void)control->set_name(name);
451 active_ = registry_->register_guard(control, name, component);
452 control_ = std::move(control);
453 }
454
456 {
457 reset();
458 }
459
462
464 : active_(other.active_), registry_(other.registry_), native_id_(other.native_id_),
465 control_(std::move(other.control_)), registry_owner_(std::move(other.registry_owner_))
466 {
467 other.active_ = false;
468 other.registry_ = nullptr;
469 other.native_id_ = {};
470 }
471
472 auto
474 {
475 if (this != &other)
476 {
477 reset();
478 active_ = other.active_;
479 registry_ = other.registry_;
480 native_id_ = other.native_id_;
481 control_ = std::move(other.control_);
482 registry_owner_ = std::move(other.registry_owner_);
483 other.active_ = false;
484 other.registry_ = nullptr;
485 other.native_id_ = {};
486 }
487 return *this;
488 }
489
490private:
491 void
492 reset() noexcept
493 {
494 if (active_ && registry_ != nullptr)
495 registry_->unregister_thread(native_id_, control_.get());
496 active_ = false;
497 registry_ = nullptr;
498 registry_owner_.reset();
499 }
500
501 bool active_{ false };
502 detail::thread_registry_backend* registry_{ nullptr };
503 detail::native_thread_id native_id_{};
504 std::shared_ptr<detail::thread_control_block> control_;
505 std::shared_ptr<detail::thread_registry_backend> registry_owner_;
506};
507
508} // namespace threadschedule
Non-owning facade that merges snapshots from multiple registries.
RAII registration of the calling thread in a portable registry.
auto operator=(auto_register_current_thread const &) -> auto_register_current_thread &=delete
auto operator=(auto_register_current_thread &&other) noexcept -> auto_register_current_thread &
auto_register_current_thread(std::string name={}, std::string component={})
auto_register_current_thread(thread_registry &registry, std::string name={}, std::string component={})
auto_register_current_thread(auto_register_current_thread &&other) noexcept
auto_register_current_thread(auto_register_current_thread const &)=delete
static auto create_for_current_thread() -> std::shared_ptr< thread_control_block >
Temporarily bind an owning registry as the global external registry.
global_registry_binding(global_registry_binding &&other) noexcept
auto operator=(global_registry_binding const &) -> global_registry_binding &=delete
auto operator=(global_registry_binding &&other) noexcept -> global_registry_binding &
global_registry_binding(thread_registry &registry)
Install external binding.
global_registry_binding(global_registry_binding const &)=delete
Strongly-typed nice value in the POSIX range $[-20, 19]$.
Portable thread configuration bundle.
Registry for named/configurable threads.
thread_registry(thread_registry const &)=delete
static auto create() -> result< thread_registry >
Create an owning registry without throwing.
friend auto global_registry() -> thread_registry &
auto register_current_thread(std::string name={}, std::string component={}) -> result< void >
Register the calling thread.
auto count() const -> std::size_t
Return number of tracked entries.
thread_registry(thread_registry &&) noexcept=default
auto get_priority(thread_id id) const -> result< priority_level >
Query portable priority preset for a registered thread.
auto set_nice(thread_id id, nice_value value) -> result< void >
Set explicit nice value for a registered thread.
auto snapshot() const -> result< std::vector< registered_thread > >
Take a snapshot of all registry entries.
auto operator=(thread_registry const &) -> thread_registry &=delete
thread_registry()
Construct an owning registry instance.
auto empty() const -> bool
Return whether registry has no tracked entries.
auto set_priority(thread_id id, priority_level level) -> result< void >
Set portable priority preset for a registered thread.
auto unregister_current_thread() -> result< void >
Unregister the calling thread.
auto configure(thread_id id, thread_config const &config) -> result< void >
Apply configuration to a registered thread.
auto get_nice(thread_id id) const -> result< nice_value >
Query nice value for a registered thread.
constexpr auto posix_nice(int nice_value) noexcept -> native_scheduling_config
Definition native.hpp:313
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)
constexpr auto to_native(shutdown_policy policy) noexcept -> shutdown_policy_backend
Definition shutdown.hpp:10
constexpr auto to_priority_level(int nice_value) noexcept -> priority_level
Definition control.hpp:32
void runtime_set_external_registry(thread_registry_backend *reg)
auto runtime_registry() -> thread_registry_backend &
auto try_result(Function &&function) -> decltype(std::forward< Function >(function)())
auto global_registry() -> thread_registry &
priority_level
Portable non-realtime priority levels.
static constexpr auto value(thread_id id) noexcept -> std::uint64_t
Definition thread_id.hpp:91
static constexpr auto make(std::uint64_t value) -> thread_id
Definition thread_id.hpp:84
Snapshot entry returned by thread_registry::snapshot.
std::string name
User-visible thread name if known.
std::thread::id std_id
Native std::thread identifier.
std::string component
Optional logical component/group label.
bool alive
Whether thread is currently alive according to the registry.
thread_id id
Stable logical thread id inside the registry.
Portable thread startup and runtime configuration.
Strongly-typed registry thread identifier.