Skip to content

Commit 614a878

Browse files
authored
refactor(core): extract lifecycle_controller for thread state management (#507)
Extract thread lifecycle management logic (state transitions, stop requests, condition variables) into a dedicated lifecycle_controller class. This reduces code duplication in thread_base and provides a reusable component for thread state management. Changes: - Add lifecycle_controller class with state, stop request, and CV management - Refactor thread_base to use lifecycle_controller via composition - Add comprehensive unit tests for lifecycle_controller - Update CMakeLists.txt to include new source files Closes #504
1 parent e02b3a7 commit 614a878

6 files changed

Lines changed: 856 additions & 182 deletions

File tree

core/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ set(CORE_HEADERS
1818
${CMAKE_CURRENT_SOURCE_DIR}/../include/kcenon/thread/core/job.h
1919
${CMAKE_CURRENT_SOURCE_DIR}/../include/kcenon/thread/core/job_queue.h
2020
${CMAKE_CURRENT_SOURCE_DIR}/../include/kcenon/thread/core/job_types.h
21+
${CMAKE_CURRENT_SOURCE_DIR}/../include/kcenon/thread/core/lifecycle_controller.h
2122
${CMAKE_CURRENT_SOURCE_DIR}/../include/kcenon/thread/core/pool_traits.h
2223
${CMAKE_CURRENT_SOURCE_DIR}/../include/kcenon/thread/core/service_registry.h
2324
${CMAKE_CURRENT_SOURCE_DIR}/../include/kcenon/thread/core/sync_primitives.h
@@ -85,6 +86,7 @@ set(CORE_SOURCES
8586
${CMAKE_CURRENT_SOURCE_DIR}/../src/core/hazard_pointer.cpp
8687
${CMAKE_CURRENT_SOURCE_DIR}/../src/core/job.cpp
8788
${CMAKE_CURRENT_SOURCE_DIR}/../src/core/job_queue.cpp
89+
${CMAKE_CURRENT_SOURCE_DIR}/../src/core/lifecycle_controller.cpp
8890
${CMAKE_CURRENT_SOURCE_DIR}/../src/core/thread_base.cpp
8991
${CMAKE_CURRENT_SOURCE_DIR}/../src/core/thread_logger_init.cpp
9092
# Lock-free implementation
Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
/*****************************************************************************
2+
BSD 3-Clause License
3+
4+
Copyright (c) 2024, 🍀☀🌕🌥 🌊
5+
All rights reserved.
6+
7+
Redistribution and use in source and binary forms, with or without
8+
modification, are permitted provided that the following conditions are met:
9+
10+
1. Redistributions of source code must retain the above copyright notice, this
11+
list of conditions and the following disclaimer.
12+
13+
2. Redistributions in binary form must reproduce the above copyright notice,
14+
this list of conditions and the following disclaimer in the documentation
15+
and/or other materials provided with the distribution.
16+
17+
3. Neither the name of the copyright holder nor the names of its
18+
contributors may be used to endorse or promote products derived from
19+
this software without specific prior written permission.
20+
21+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+
*****************************************************************************/
32+
33+
#pragma once
34+
35+
#include "thread_conditions.h"
36+
37+
#include <mutex>
38+
#include <atomic>
39+
#include <chrono>
40+
#include <optional>
41+
#include <condition_variable>
42+
43+
#ifdef USE_STD_JTHREAD
44+
#include <stop_token>
45+
#endif
46+
47+
namespace kcenon::thread
48+
{
49+
/**
50+
* @class lifecycle_controller
51+
* @brief Centralized thread lifecycle state and synchronization management.
52+
*
53+
* @ingroup core_threading
54+
*
55+
* The @c lifecycle_controller class consolidates duplicated thread lifecycle
56+
* management patterns (start, stop, state transitions, condition variables)
57+
* into a single reusable component. Thread classes can use composition with
58+
* this controller instead of implementing these patterns themselves.
59+
*
60+
* ### Key Features
61+
* - Thread state management (Created, Waiting, Working, Stopping, Stopped)
62+
* - Condition variable signaling for wake-ups
63+
* - Stop request handling (supports both std::jthread and legacy std::thread)
64+
* - Thread-safe state queries and transitions
65+
*
66+
* ### Thread Safety
67+
* All public methods are thread-safe. The class uses internal synchronization
68+
* to protect state transitions and condition variable operations.
69+
*
70+
* ### Example Usage
71+
* @code
72+
* class my_thread {
73+
* private:
74+
* lifecycle_controller lifecycle_;
75+
*
76+
* public:
77+
* void start() {
78+
* lifecycle_.initialize_for_start();
79+
* // spawn thread...
80+
* }
81+
*
82+
* void stop() {
83+
* lifecycle_.request_stop();
84+
* lifecycle_.notify_all();
85+
* // join thread...
86+
* lifecycle_.set_stopped();
87+
* }
88+
*
89+
* void worker_loop() {
90+
* while (!lifecycle_.is_stop_requested() || has_work()) {
91+
* lifecycle_.set_state(thread_conditions::Waiting);
92+
* lifecycle_.wait_for(timeout, [this] { return has_work(); });
93+
* lifecycle_.set_state(thread_conditions::Working);
94+
* do_work();
95+
* }
96+
* }
97+
* };
98+
* @endcode
99+
*/
100+
class lifecycle_controller
101+
{
102+
public:
103+
lifecycle_controller(const lifecycle_controller&) = delete;
104+
lifecycle_controller& operator=(const lifecycle_controller&) = delete;
105+
lifecycle_controller(lifecycle_controller&&) = delete;
106+
lifecycle_controller& operator=(lifecycle_controller&&) = delete;
107+
108+
/**
109+
* @brief Constructs a new lifecycle_controller in Created state.
110+
*/
111+
lifecycle_controller();
112+
113+
/**
114+
* @brief Destructor.
115+
*/
116+
~lifecycle_controller() = default;
117+
118+
// =========================================================================
119+
// State Management
120+
// =========================================================================
121+
122+
/**
123+
* @brief Gets the current thread condition/state.
124+
* @return The current thread_conditions value.
125+
*
126+
* Thread Safety:
127+
* - Safe to call from any thread
128+
* - Uses atomic load with acquire memory ordering
129+
*/
130+
[[nodiscard]] auto get_state() const noexcept -> thread_conditions;
131+
132+
/**
133+
* @brief Sets the thread condition/state.
134+
* @param state The new thread_conditions value.
135+
*
136+
* Thread Safety:
137+
* - Safe to call from any thread
138+
* - Uses atomic store with release memory ordering
139+
*/
140+
auto set_state(thread_conditions state) noexcept -> void;
141+
142+
/**
143+
* @brief Checks if the thread is currently running.
144+
* @return true if state is Working or Waiting, false otherwise.
145+
*/
146+
[[nodiscard]] auto is_running() const noexcept -> bool;
147+
148+
/**
149+
* @brief Marks the thread as stopped.
150+
*
151+
* Convenience method equivalent to set_state(thread_conditions::Stopped).
152+
*/
153+
auto set_stopped() noexcept -> void;
154+
155+
// =========================================================================
156+
// Stop Request Management
157+
// =========================================================================
158+
159+
/**
160+
* @brief Initializes the controller for a new thread start.
161+
*
162+
* Resets the stop request flag and prepares for a new thread lifecycle.
163+
* In C++20 jthread mode, creates a new stop_source.
164+
*
165+
* @note Must be called before spawning the worker thread.
166+
*/
167+
auto initialize_for_start() -> void;
168+
169+
/**
170+
* @brief Requests the thread to stop.
171+
*
172+
* In C++20 mode, calls request_stop() on the stop_source.
173+
* In legacy mode, sets the atomic stop_requested_ flag to true.
174+
*
175+
* Thread Safety:
176+
* - Safe to call from any thread
177+
* - The request is visible to the worker thread immediately
178+
*/
179+
auto request_stop() noexcept -> void;
180+
181+
/**
182+
* @brief Checks if a stop has been requested.
183+
* @return true if stop has been requested, false otherwise.
184+
*
185+
* Thread Safety:
186+
* - Safe to call from any thread
187+
*/
188+
[[nodiscard]] auto is_stop_requested() const noexcept -> bool;
189+
190+
/**
191+
* @brief Checks if the controller has an active stop source (C++20 only).
192+
* @return true if a stop_source is active, false otherwise.
193+
*
194+
* In legacy mode, checks if stop has NOT been requested (indicating active state).
195+
*/
196+
[[nodiscard]] auto has_active_source() const noexcept -> bool;
197+
198+
/**
199+
* @brief Resets the stop control mechanism after thread completion.
200+
*
201+
* In C++20 mode, resets the stop_source.
202+
* Should be called after thread join to clean up resources.
203+
*/
204+
auto reset_stop_source() noexcept -> void;
205+
206+
// =========================================================================
207+
// Condition Variable Operations
208+
// =========================================================================
209+
210+
/**
211+
* @brief Acquires a unique lock on the condition variable mutex.
212+
* @return A unique_lock holding the mutex.
213+
*
214+
* Use this to prepare for wait operations.
215+
*/
216+
[[nodiscard]] auto acquire_lock() -> std::unique_lock<std::mutex>;
217+
218+
/**
219+
* @brief Waits on the condition variable with a predicate.
220+
* @tparam Predicate A callable returning bool.
221+
* @param lock The unique_lock (must be holding cv_mutex_).
222+
* @param pred The predicate to check.
223+
*
224+
* Waits until pred() returns true OR stop is requested.
225+
*/
226+
template<typename Predicate>
227+
auto wait(std::unique_lock<std::mutex>& lock, Predicate pred) -> void
228+
{
229+
#ifdef USE_STD_JTHREAD
230+
if (stop_source_.has_value())
231+
{
232+
auto stop_token = stop_source_.value().get_token();
233+
condition_.wait(lock, [this, &stop_token, &pred]() {
234+
return stop_token.stop_requested() || pred();
235+
});
236+
}
237+
else
238+
{
239+
condition_.wait(lock, pred);
240+
}
241+
#else
242+
condition_.wait(lock, [this, &pred]() {
243+
return stop_requested_.load(std::memory_order_acquire) || pred();
244+
});
245+
#endif
246+
}
247+
248+
/**
249+
* @brief Waits on the condition variable with a timeout and predicate.
250+
* @tparam Rep Duration rep type.
251+
* @tparam Period Duration period type.
252+
* @tparam Predicate A callable returning bool.
253+
* @param lock The unique_lock (must be holding cv_mutex_).
254+
* @param timeout The maximum duration to wait.
255+
* @param pred The predicate to check.
256+
* @return true if pred() is satisfied, false if timed out.
257+
*/
258+
template<typename Rep, typename Period, typename Predicate>
259+
auto wait_for(std::unique_lock<std::mutex>& lock,
260+
const std::chrono::duration<Rep, Period>& timeout,
261+
Predicate pred) -> bool
262+
{
263+
#ifdef USE_STD_JTHREAD
264+
if (stop_source_.has_value())
265+
{
266+
auto stop_token = stop_source_.value().get_token();
267+
return condition_.wait_for(lock, timeout, [this, &stop_token, &pred]() {
268+
return stop_token.stop_requested() || pred();
269+
});
270+
}
271+
else
272+
{
273+
return condition_.wait_for(lock, timeout, pred);
274+
}
275+
#else
276+
return condition_.wait_for(lock, timeout, [this, &pred]() {
277+
return stop_requested_.load(std::memory_order_acquire) || pred();
278+
});
279+
#endif
280+
}
281+
282+
/**
283+
* @brief Notifies one waiting thread.
284+
*
285+
* Thread Safety:
286+
* - Safe to call from any thread
287+
*/
288+
auto notify_one() -> void;
289+
290+
/**
291+
* @brief Notifies all waiting threads.
292+
*
293+
* Thread Safety:
294+
* - Safe to call from any thread
295+
*/
296+
auto notify_all() -> void;
297+
298+
private:
299+
/// Mutex for condition variable operations.
300+
std::mutex cv_mutex_;
301+
302+
/// Condition variable for thread signaling.
303+
std::condition_variable condition_;
304+
305+
/// Current thread state.
306+
std::atomic<thread_conditions> state_{thread_conditions::Created};
307+
308+
#ifdef USE_STD_JTHREAD
309+
/// Stop source for cooperative cancellation (C++20).
310+
std::optional<std::stop_source> stop_source_;
311+
#else
312+
/// Atomic flag for stop request (legacy mode).
313+
std::atomic<bool> stop_requested_{false};
314+
#endif
315+
};
316+
317+
} // namespace kcenon::thread

0 commit comments

Comments
 (0)