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/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.
7 */
8
9 #include <atomic>
10 #include <functional>
11 #include <stop_token>
12 #include <string>
13 #include <string_view>
14 #include <thread>
15
16 namespace DetourModKit
17 {
18 /**
19 * @class StoppableWorker
20 * @brief RAII-owned named background worker built on std::jthread.
21 * @details The body receives a std::stop_token and must poll it cooperatively. On destruction the worker requests
22 * stop and joins the thread, unless it detects that the current thread is executing under the Windows
23 * loader lock -- in which case the thread is detached to avoid deadlock. Callers that need to preempt the
24 * loader-lock case should call request_stop() and join() from a thread outside DllMain prior to teardown.
25 *
26 * Non-copyable and non-movable: the name, stop state, and thread handle form a single invariant. Copying
27 * would duplicate the thread handle; moving would race with a running body.
28 */
29 class StoppableWorker
30 {
31 public:
32 /**
33 * @brief Starts a new worker thread running the supplied body.
34 * @param name Descriptive name for logging. Copied into the worker.
35 * @param body Invocable receiving a stop_token. Must return promptly when stop_requested() becomes true.
36 */
37 StoppableWorker(std::string_view name, std::function<void(std::stop_token)> body);
38
39 ~StoppableWorker() noexcept;
40
41 StoppableWorker(const StoppableWorker &) = delete;
42 StoppableWorker &operator=(const StoppableWorker &) = delete;
43 StoppableWorker(StoppableWorker &&) = delete;
44 StoppableWorker &operator=(StoppableWorker &&) = delete;
45
46 /**
47 * @brief Signals the worker to stop cooperatively.
48 * @details Idempotent. Does not block.
49 */
50 void request_stop() noexcept;
51
52 /**
53 * @brief Returns true once the worker thread has started and before it has been shut down.
54 * @details Reads liveness from atomics, never the std::jthread handle, so it is race-free against a concurrent
55 * shutdown() that joins or detaches the thread.
56 */
57 [[nodiscard]] bool is_running() const noexcept;
58
59 /**
60 * @brief Returns the worker's descriptive name.
61 */
62 2 [[nodiscard]] const std::string &name() const noexcept { return m_name; }
63
64 /**
65 * @brief Requests stop and joins the worker thread.
66 * @details Safe to call multiple times. If invoked under the
67 * Windows loader lock the thread is detached instead of joined (documented trade-off -- the pinned
68 * module keeps code pages alive).
69 */
70 void shutdown() noexcept;
71
72 private:
73 std::string m_name;
74 std::jthread m_thread;
75 // Stable copy of the jthread stop source. request_stop() signals this shared stop state instead of touching
76 // m_thread while shutdown() may be joining or detaching the handle.
77 std::stop_source m_stop_source;
78 // Liveness snapshot read by is_running()/request_stop(). m_started is set once m_thread and m_stop_source are
79 // both initialized; m_joined is set when shutdown begins (or in the empty-body early return).
80 std::atomic<bool> m_started{false};
81 std::atomic<bool> m_joined{false};
82 };
83 } // namespace DetourModKit
84
85 #endif // DETOURMODKIT_WORKER_HPP
86