ThreadSchedule 3.0.0
Modern C++ thread management library
Loading...
Searching...
No Matches
chaos_controller.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "../../thread.hpp"
4#include "../../thread_config.hpp"
5#include "../../thread_registry.hpp"
6#include "../cpu_topology.hpp"
7
8#include <algorithm>
9#include <atomic>
10#include <chrono>
11#include <condition_variable>
12#include <cstdint>
13#include <exception>
14#include <functional>
15#include <future>
16#include <mutex>
17#include <optional>
18#include <random>
19#include <thread>
20#include <utility>
21
23{
24
26{
27 std::chrono::milliseconds interval{ 250 };
28 int nice_jitter{ 0 };
29 bool shuffle_affinity{ true };
30};
31
33{
34public:
35 template <typename Predicate>
36 chaos_controller(chaos_config config, Predicate predicate) : config_(config)
37 {
38 if (config_.interval <= std::chrono::milliseconds::zero())
39 throw std::invalid_argument("chaos interval must be positive");
40 if (config_.nice_jitter < 0)
41 throw std::invalid_argument("chaos nice jitter must not be negative");
42
43 std::promise<thread_id> started;
44 auto ready = started.get_future();
45 worker_ = thread(
46 [this, predicate = std::move(predicate), started = std::move(started)]() mutable
47 {
48 running_.store(true, std::memory_order_release);
49 started.set_value(
51 try
52 {
53 run_loop(predicate);
54 }
55 catch (...)
56 {
57 std::lock_guard<std::mutex> lock(failure_mutex_);
58 failure_ = std::current_exception();
59 }
60 running_.store(false, std::memory_order_release);
61 });
62 worker_id_ = ready.get();
63 (void)worker_.set_name("ts_chaos_ctl");
64 }
65
67 {
68 {
69 std::lock_guard<std::mutex> lock(wait_mutex_);
70 stop_.store(true, std::memory_order_release);
71 }
72 wakeup_.notify_one();
73 if (worker_.joinable())
74 (void)worker_.join();
75 }
76
78 auto operator=(chaos_controller const&) -> chaos_controller& = delete;
81
82 [[nodiscard]] auto
83 thread_info() const -> std::optional<registered_thread>
84 {
85 if (!worker_.joinable() || !worker_id_ || !running_.load(std::memory_order_acquire))
86 return std::nullopt;
87 auto name = worker_.get_name();
88 return registered_thread{ *worker_id_, worker_.get_id(), name.value_or(std::string{}), "chaos", true };
89 }
90
91 auto
93 {
94 if (!worker_.joinable() || !running_.load(std::memory_order_acquire))
95 return unexpected(std::make_error_code(std::errc::no_such_process));
96 return worker_.configure(config);
97 }
98
100 [[nodiscard]] auto
101 failure() const -> std::exception_ptr
102 {
103 std::lock_guard<std::mutex> lock(failure_mutex_);
104 return failure_;
105 }
106
107private:
108 [[nodiscard]] static auto
109 make_random_engine() noexcept -> std::mt19937
110 {
111 auto seed = static_cast<std::uint32_t>(std::chrono::steady_clock::now().time_since_epoch().count());
112 seed ^= static_cast<std::uint32_t>(std::hash<std::thread::id>{}(std::this_thread::get_id()));
113 try
114 {
115 std::random_device device;
116 seed ^= device();
117 }
118 catch (...)
119 {
120 }
121 return std::mt19937(seed);
122 }
123
124 template <typename Predicate>
125 void
126 run_loop(Predicate& predicate)
127 {
128 auto random = make_random_engine();
129 while (!stop_.load(std::memory_order_acquire))
130 {
131 auto entries = global_registry().snapshot();
132 if (entries)
133 perturb(*entries, predicate, random);
134
135 std::unique_lock<std::mutex> lock(wait_mutex_);
136 wakeup_.wait_for(lock, config_.interval, [this] { return stop_.load(std::memory_order_acquire); });
137 }
138 }
139
140 template <typename Predicate>
141 void
142 perturb(std::vector<registered_thread> const& entries, Predicate& predicate, std::mt19937& random)
143 {
144 auto const topology = config_.shuffle_affinity ? read_topology() : cpu_topology{};
145 std::uniform_int_distribution<int> jitter(-config_.nice_jitter, config_.nice_jitter);
146 std::size_t selected_index = 0;
147 for (auto const& entry : entries)
148 {
149 if (!entry.alive || !std::invoke(predicate, entry))
150 continue;
151
152 if (config_.shuffle_affinity)
153 {
154 auto const node = topology.numa_nodes == 0 ? 0 : selected_index % topology.numa_nodes;
155 thread_config affinity;
156 affinity.set_affinity(
157 affinity_for_node(topology, static_cast<int>(node), static_cast<int>(selected_index)));
158 (void)global_registry().configure(entry.id, affinity);
159 }
160
161 if (config_.nice_jitter != 0)
162 {
163 auto current = global_registry().get_nice(entry.id);
164 if (current)
165 {
166 auto const value
167 = std::clamp(current->value() + jitter(random), nice_value::minimum, nice_value::maximum);
168 (void)global_registry().set_nice(entry.id, nice_value{ value });
169 }
170 }
171 ++selected_index;
172 }
173 }
174
175 chaos_config config_;
176 std::atomic<bool> stop_{ false };
177 std::atomic<bool> running_{ false };
178 mutable std::mutex failure_mutex_;
179 std::exception_ptr failure_;
180 std::mutex wait_mutex_;
181 std::condition_variable wakeup_;
182 thread worker_;
183 std::optional<thread_id> worker_id_;
184};
185
186} // namespace threadschedule::advanced
auto operator=(chaos_controller const &) -> chaos_controller &=delete
chaos_controller(chaos_controller &&)=delete
auto thread_info() const -> std::optional< registered_thread >
chaos_controller(chaos_controller const &)=delete
chaos_controller(chaos_config config, Predicate predicate)
auto failure() const -> std::exception_ptr
Return an exception that stopped the controller, if any.
auto configure_thread(thread_config const &config) -> result< void >
auto operator=(chaos_controller &&) -> chaos_controller &=delete
static constexpr int minimum
Lowest accepted nice value (highest priority).
static constexpr int maximum
Highest accepted nice value (lowest priority).
Portable thread configuration bundle.
Owning thread wrapper with result-based lifecycle/configuration API.
Definition thread.hpp:29
auto set_name(std::string const &name) -> result< void >
Set thread name.
Definition thread.hpp:183
auto configure(thread_config const &config) -> result< void >
Apply a full portable thread configuration to the running thread.
Definition thread.hpp:148
auto get_name() const -> result< std::string >
Query thread name if supported by platform/backend.
Definition thread.hpp:190
auto get_id() const noexcept -> std::thread::id
Return std::thread id of the owned thread.
Definition thread.hpp:134
auto join() -> result< void >
Join the thread.
Definition thread.hpp:99
auto joinable() const noexcept -> bool
Return whether the thread object owns a running thread.
Definition thread.hpp:127
auto affinity_for_node(cpu_topology const &topo, int node_index, int thread_index, int threads_per_node=1) -> thread_affinity
Build a thread_affinity for the given NUMA node using a pre-read topology.
auto read_topology() -> cpu_topology
Discover basic topology. Linux: reads /sys for NUMA nodes. Windows: single node, processor-group-awar...
auto current_native_thread_id() noexcept -> native_thread_id
auto global_registry() -> thread_registry &
static constexpr auto make(std::uint64_t value) -> thread_id
Definition thread_id.hpp:84
Snapshot entry returned by thread_registry::snapshot.