Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 50 additions & 4 deletions include/threadschedule/chaos.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,64 @@ namespace threadschedule
{

/**
* @brief Runtime chaos settings.
* @brief Plain value type holding runtime chaos-testing parameters.
*
* All fields have sensible defaults so a default-constructed `ChaosConfig`
* is immediately usable.
*
* @see ChaosController
*/
struct ChaosConfig
{
/** Time between successive chaos perturbations (default 250 ms). */
std::chrono::milliseconds interval{250};
int priority_jitter{0}; // +/- jitter applied around current priority

/**
* @brief +/- range applied around the current thread priority each
* interval.
*
* A value of 0 disables priority perturbation.
*/
int priority_jitter{0};

/** Whether to reassign CPU affinities each interval (default `true`). */
bool shuffle_affinity{true};
};

// RAII controller that periodically perturbs affinity/priority of registered threads matching a predicate
/**
* @brief RAII controller that periodically applies chaos operations.
* @brief RAII controller that periodically perturbs scheduling attributes
* of registered threads for chaos/fuzz testing.
*
* On construction, `ChaosController` spawns a background `std::thread`
* that wakes every `ChaosConfig::interval` and applies perturbations
* (affinity shuffling, priority jitter) to threads in the global
* `registry()` that match the user-supplied predicate.
*
* **Ownership semantics:**
* - Non-copyable, non-movable.
* - The destructor signals the worker to stop and **blocks** until it
* joins. Do not destroy from a context where blocking is unacceptable.
*
* **Thread safety:**
* The controller operates on the global `registry()`, which is internally
* synchronized, so multiple controllers or concurrent registrations are
* safe.
*
* @warning Intended for testing and validation only -- not for production
* use. Perturbations may cause spurious priority inversions and
* cache-thrashing.
*
* @par Example
* @code
* ChaosConfig cfg{.interval = 100ms, .priority_jitter = 5};
* ChaosController chaos(cfg, [](auto const& info) {
* return info.name.starts_with("worker");
* });
* // ... run tests while chaos is active ...
* // destructor joins the worker thread
* @endcode
*
* @see ChaosConfig, registry()
*/
class ChaosController
{
Expand Down
77 changes: 66 additions & 11 deletions include/threadschedule/concepts.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
#pragma once

/**
* @file concepts.hpp
* @brief C++20 concepts, type traits, and SFINAE helpers for the threading library.
*
* Provides compile-time constraints (`ThreadCallable`, `ThreadIdentifiable`,
* `Duration`, `PriorityType`, `CPUSetType`) used throughout the library to
* enforce correct template arguments. When C++20 concepts are unavailable,
* equivalent `constexpr bool` variables are defined as fallbacks.
*
* Also defines the `is_thread_like<T>` trait hierarchy for generic thread
* handle dispatch.
*/

#include <chrono>
#include <functional>
#include <set>
Expand All @@ -10,36 +23,55 @@
namespace threadschedule
{

// Custom duration trait for compatibility across all C++ versions
/**
* @brief SFINAE trait that detects `std::chrono::duration` types.
*
* Yields `std::true_type` when @p T exposes nested `rep` and `period`
* type aliases (the signature of any `std::chrono::duration` instantiation).
* The primary template is `std::false_type`; the partial specialization
* using `std::void_t` matches duration-like types.
*
* @tparam T The type to test.
*/
template <typename T, typename = void>
struct is_duration_impl : std::false_type
{
};

/** @copydoc is_duration_impl */
template <typename T>
struct is_duration_impl<T, std::void_t<typename T::rep, typename T::period>> : std::true_type
{
};

// C++23 concepts (with fallbacks for older compilers)
// C++20 concepts (with constexpr-bool fallbacks for older compilers)
#if __cpp_concepts >= 201907L

/**
* @brief Concept for callable objects that can be executed by threads
* @brief Constrains @p F to be invocable with @p Args.
*
* Use in template parameter lists to restrict thread-entry functions or
* callbacks to types that are actually callable with the given arguments.
*/
template <typename F, typename... Args>
concept ThreadCallable = std::is_invocable_v<F, Args...>;

/**
* @brief Concept for types that can be used as thread identifiers
* @brief Constrains @p T to types that expose a thread identity via
* `get_id()` returning something convertible to `std::thread::id`.
*
* Satisfied by `std::thread`, `std::jthread`, and `ThreadWrapper`.
*/
template <typename T>
concept ThreadIdentifiable = requires(T t) {
{ t.get_id() } -> std::convertible_to<std::thread::id>;
};

/**
* @brief Concept for duration types used in thread operations
* @brief Constrains @p T to `std::chrono::duration`-like types (those
* exposing `rep` and `period` nested types).
*
* Use for timeout / interval parameters in scheduling APIs.
*/
template <typename T>
concept Duration = requires {
Expand All @@ -48,13 +80,15 @@ concept Duration = requires {
};

/**
* @brief Concept for types that can represent thread priorities
* @brief Constrains @p T to integral types suitable for representing
* thread priorities.
*/
template <typename T>
concept PriorityType = std::is_integral_v<T>;

/**
* @brief Concept for CPU set types
* @brief Constrains @p T to container-like types that can represent a set
* of CPU indices (must provide `size()`, `begin()`, `end()`).
*/
template <typename T>
concept CPUSetType = requires(T t) {
Expand All @@ -65,46 +99,67 @@ concept CPUSetType = requires(T t) {

#else

// Fallback using SFINAE for older compilers
/**
* @brief Pre-C++20 fallback for ThreadCallable (constexpr bool).
* @see ThreadCallable concept above.
*/
template <typename F, typename... Args>
constexpr bool ThreadCallable = std::is_invocable_v<F, Args...>;

/** @brief Pre-C++20 fallback for ThreadIdentifiable (constexpr bool). */
template <typename T>
constexpr bool ThreadIdentifiable = std::is_same_v<decltype(std::declval<T>().get_id()), std::thread::id>;

/** @brief Pre-C++20 fallback for Duration (constexpr bool). */
template <typename T>
constexpr bool Duration = is_duration_impl<T>::value;

/** @brief Pre-C++20 fallback for PriorityType (constexpr bool). */
template <typename T>
constexpr bool PriorityType = std::is_integral_v<T>;

// For CPU set types, we'll use a simple trait
/** @brief Pre-C++20 fallback for CPUSetType (constexpr bool). */
template <typename T>
constexpr bool CPUSetType = std::is_same_v<T, std::vector<int>> || std::is_same_v<T, std::set<int>>;

#endif

/**
* @brief Type trait for thread-like objects
* @brief Type trait that identifies thread-like types.
*
* The primary template yields `std::false_type`. Explicit specializations
* are provided for `std::thread` and (when C++20 is available)
* `std::jthread`. Additional specializations for library types such as
* `ThreadWrapper` are defined in `profiles.hpp`.
*
* Used by `apply_profile()` and other generic scheduling functions to
* accept any thread-like handle uniformly.
*
* @tparam T The type to test.
*
* @par Helper variable
* `is_thread_like_v<T>` is a convenience `inline constexpr bool`.
*/
template <typename T>
struct is_thread_like : std::false_type
{
};

/** @brief `std::thread` is a thread-like type. */
template <>
struct is_thread_like<std::thread> : std::true_type
{
};

// Only include jthread if C++20 is available
#if __cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)
/** @brief `std::jthread` is a thread-like type (C++20). */
template <>
struct is_thread_like<std::jthread> : std::true_type
{
};
#endif

/** @brief Convenience variable template for is_thread_like. */
template <typename T>
inline constexpr bool is_thread_like_v = is_thread_like<T>::value;

Expand Down
Loading
Loading