GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 100.0% 1 / 0 / 1
Functions: 100.0% 1 / 0 / 1
Branches: -% 0 / 0 / 0

include/DetourModKit/detail/worker.hpp
Line Branch Exec Source
1 #ifndef DETOURMODKIT_WORKER_HPP
2 #define DETOURMODKIT_WORKER_HPP
3
4 /**
5 * @file worker.hpp
6 * @brief RAII wrapper around std::jthread with a named stop signal and explicit lifecycle state.
7 * @note Lives in detail/ for compile visibility but declares the public utility at the DetourModKit root.
8 */
9
10 #include <atomic>
11 #include <cstdint>
12 #include <functional>
13 #include <memory>
14 #include <stop_token>
15 #include <string>
16 #include <string_view>
17 #include <thread>
18
19 namespace DetourModKit
20 {
21 /**
22 * @class StoppableWorker
23 * @brief RAII-owned named background worker built on std::jthread.
24 * @details The body receives a std::stop_token and must poll it cooperatively. Destruction requests stop and joins
25 * when blocking teardown is authorized. Under the Windows loader lock the thread is detached without
26 * invoking stop callbacks and its module reference is leaked; self-shutdown hands the thread and
27 * reference to an off-thread reaper. The type is non-copyable and non-movable because its name, stop
28 * state, lifecycle, and thread handle form one invariant.
29 */
30 class StoppableWorker
31 {
32 public:
33 /**
34 * @brief Starts a worker thread running @p body.
35 * @param name Descriptive name for logging. Copied into the worker.
36 * @param body Invocable receiving a stop_token. Must return promptly once stop_requested().
37 * @throws std::system_error if the counted module reference cannot be taken (the keepalive must exist
38 * before the thread runs library code) or the thread cannot be created.
39 * @throws std::bad_alloc if owned setup state cannot be allocated.
40 * @note Construction is all-or-nothing: on throw no thread survives and the module reference is released.
41 */
42 StoppableWorker(std::string_view name, std::function<void(std::stop_token)> body);
43
44 ~StoppableWorker() noexcept;
45
46 StoppableWorker(const StoppableWorker &) = delete;
47 StoppableWorker &operator=(const StoppableWorker &) = delete;
48 StoppableWorker(StoppableWorker &&) = delete;
49 StoppableWorker &operator=(StoppableWorker &&) = delete;
50
51 /// Signals the worker cooperatively; registered stop callbacks run synchronously and may block.
52 void request_stop() noexcept;
53
54 /**
55 * @brief Returns true while the body is starting or executing.
56 * @details Returns false once the body returns or shutdown begins. Reads shared atomic state rather
57 * than the jthread handle, so it is race-free against concurrent shutdown.
58 */
59 [[nodiscard]] bool is_running() const noexcept;
60
61 /// Returns the worker's descriptive name.
62 2 [[nodiscard]] const std::string &name() const noexcept { return m_name; }
63
64 /**
65 * @brief Retires the worker thread, requesting stop only when blocking teardown is authorized.
66 * @details Idempotent. Joins off the loader lock and off the worker's own thread; under the loader lock it
67 * detaches without invoking stop callbacks and leaks the module reference; on the worker's own thread
68 * it hands the thread and reference to the off-thread reaper so a self-join can never raise
69 * std::system_error inside this noexcept function.
70 */
71 void shutdown() noexcept;
72
73 private:
74 enum class State : std::uint8_t
75 {
76 Starting,
77 Running,
78 Exited,
79 Stopping,
80 Stopped
81 };
82
83 std::string m_name;
84 // Heap ownership lets a failed detach retain the still-joinable jthread without running its destructor.
85 std::unique_ptr<std::jthread> m_thread;
86 // Stable copy of the jthread stop source: request_stop() signals this instead of touching m_thread
87 // while shutdown() may be joining or detaching the handle.
88 std::stop_source m_stop_source;
89 // Lifecycle phase, heap-shared with the body so the body can publish Running/Exited even if the owner
90 // is reaped or detached while the body still runs. is_running()/request_stop()/shutdown() read and
91 // write it without touching the jthread handle.
92 std::shared_ptr<std::atomic<State>> m_state;
93 // Counted reference on the module this worker's code lives in, taken before thread creation while the module is
94 // mapped. shutdown() releases it after a clean join, hands it to the reaper on self-shutdown, or leaks it on a
95 // loader-lock detach. void* keeps this installed header free of <windows.h>; it holds an HMODULE. See
96 // detail::acquire_module_ref.
97 void *m_self_ref{nullptr};
98 };
99 } // namespace DetourModKit
100
101 #endif // DETOURMODKIT_WORKER_HPP
102