Skip to content

Commit d2e257f

Browse files
committed
feat: context_thread wait() methods.
- Added a wait_until() method to basic_context_thread as a convenience method for blocking the current thread until a service has reached a certain state. - Added a wait() method to basic_context_thread as a convenience method for blocking the current thread until a service has stopped.
1 parent a749289 commit d2e257f

File tree

7 files changed

+242
-9
lines changed

7 files changed

+242
-9
lines changed

include/net/service/async_context.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ struct async_context : detail::immovable {
5656
/** @brief An enum of all valid async context signals. */
5757
enum signals : std::uint8_t { terminate = 0, user1, END };
5858
/** @brief An enum of valid context states. */
59-
enum context_states : std::uint8_t { PENDING = 0, STARTED, STOPPED };
59+
enum context_state : std::uint8_t { PENDING = 0, STARTED, STOPPED };
6060

6161
/** @brief The event loop timers. */
6262
timers_type timers;
@@ -67,7 +67,7 @@ struct async_context : detail::immovable {
6767
/** @brief The active signal mask. */
6868
std::atomic<signal_mask> sigmask;
6969
/** @brief A counter that tracks the context state. */
70-
std::atomic<context_states> state{PENDING};
70+
std::atomic<context_state> state{PENDING};
7171

7272
/**
7373
* @brief Sets the signal mask, then interrupts the service.

include/net/service/context_thread.hpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,20 @@ class basic_context_thread : public async_context {
7777
*/
7878
template <typename... Args> auto start(Args &&...args) -> void;
7979

80+
/**
81+
* @brief Waits for a condition.
82+
* @details Blocks the current thread until the service state condition
83+
* is satisfied.
84+
* @param cond The state that the current thread should wait for.
85+
*/
86+
auto wait_until(context_state cond) const noexcept -> void;
87+
88+
/**
89+
* @brief Waits for the service to stop.
90+
* @details Blocks the current thread until the service stops.
91+
*/
92+
auto wait() const noexcept -> void;
93+
8094
/** @brief The destructor signals the thread before joining it. */
8195
~basic_context_thread();
8296

include/net/service/impl/context_thread_impl.hpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,24 @@ auto basic_context_thread<Service>::start(Args &&...args) -> void
9191
throw std::system_error(error, "service failed to start");
9292
}
9393

94+
template <ServiceLike Service>
95+
auto basic_context_thread<Service>::wait_until(
96+
context_state cond) const noexcept -> void
97+
{
98+
auto curr_state = state.load();
99+
while (curr_state < cond)
100+
{
101+
state.wait(curr_state);
102+
curr_state = state.load();
103+
}
104+
}
105+
106+
template <ServiceLike Service>
107+
auto basic_context_thread<Service>::wait() const noexcept -> void
108+
{
109+
wait_until(STOPPED);
110+
}
111+
94112
template <ServiceLike Service>
95113
basic_context_thread<Service>::~basic_context_thread()
96114
{

tests/test_async_context.cpp

Lines changed: 204 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ struct test_service {
6161

6262
TEST_F(AsyncContextTest, AsyncServiceTest)
6363
{
64-
using enum async_context::context_states;
64+
using enum async_context::context_state;
6565

6666
auto service = basic_context_thread<test_service>();
6767
service.start();
@@ -74,7 +74,7 @@ TEST_F(AsyncContextTest, AsyncServiceTest)
7474

7575
TEST_F(AsyncContextTest, StartTwiceTest)
7676
{
77-
using enum async_context::context_states;
77+
using enum async_context::context_state;
7878

7979
auto service = basic_context_thread<test_service>{};
8080

@@ -89,7 +89,7 @@ TEST_F(AsyncContextTest, StartTwiceTest)
8989

9090
TEST_F(AsyncContextTest, TestUser1Signal)
9191
{
92-
using enum async_context::context_states;
92+
using enum async_context::context_state;
9393

9494
auto service = basic_context_thread<test_service>();
9595

@@ -103,4 +103,205 @@ TEST_F(AsyncContextTest, TestUser1Signal)
103103
}
104104
EXPECT_EQ(test_signal, service.user1);
105105
}
106+
107+
TEST_F(AsyncContextTest, WaitUntilStarted)
108+
{
109+
using enum async_context::context_state;
110+
111+
auto service = basic_context_thread<test_service>();
112+
ASSERT_EQ(service.state, PENDING);
113+
114+
auto wait_thread = std::thread([&] { service.wait_until(STARTED); });
115+
116+
service.start();
117+
wait_thread.join();
118+
119+
ASSERT_EQ(service.state, STARTED);
120+
}
121+
122+
TEST_F(AsyncContextTest, WaitUntilStopped)
123+
{
124+
using enum async_context::context_state;
125+
126+
auto service = basic_context_thread<test_service>();
127+
service.start();
128+
ASSERT_EQ(service.state, STARTED);
129+
130+
auto wait_thread = std::thread([&] { service.wait_until(STOPPED); });
131+
132+
service.signal(service.terminate);
133+
wait_thread.join();
134+
135+
ASSERT_EQ(service.state, STOPPED);
136+
}
137+
138+
TEST_F(AsyncContextTest, WaitUntilAlreadySatisfied)
139+
{
140+
using enum async_context::context_state;
141+
142+
auto service = basic_context_thread<test_service>();
143+
service.start();
144+
ASSERT_EQ(service.state, STARTED);
145+
146+
service.wait_until(PENDING);
147+
EXPECT_EQ(service.state, STARTED);
148+
149+
service.wait_until(STARTED);
150+
EXPECT_EQ(service.state, STARTED);
151+
}
152+
153+
TEST_F(AsyncContextTest, WaitUntilMultipleWaiters)
154+
{
155+
using enum async_context::context_state;
156+
157+
auto service = basic_context_thread<test_service>();
158+
ASSERT_EQ(service.state, PENDING);
159+
160+
std::atomic<int> waiters_unblocked{0};
161+
162+
auto wait_thread1 = std::thread([&] {
163+
service.wait_until(STARTED);
164+
waiters_unblocked.fetch_add(1);
165+
});
166+
167+
auto wait_thread2 = std::thread([&] {
168+
service.wait_until(STARTED);
169+
waiters_unblocked.fetch_add(1);
170+
});
171+
172+
auto wait_thread3 = std::thread([&] {
173+
service.wait_until(STARTED);
174+
waiters_unblocked.fetch_add(1);
175+
});
176+
177+
std::this_thread::sleep_for(std::chrono::milliseconds(10));
178+
EXPECT_EQ(waiters_unblocked.load(), 0);
179+
180+
service.start();
181+
182+
wait_thread1.join();
183+
wait_thread2.join();
184+
wait_thread3.join();
185+
186+
EXPECT_EQ(waiters_unblocked.load(), 3);
187+
ASSERT_EQ(service.state, STARTED);
188+
}
189+
190+
TEST_F(AsyncContextTest, WaitUntilPendingToStopped)
191+
{
192+
using enum async_context::context_state;
193+
194+
auto service = basic_context_thread<test_service>();
195+
ASSERT_EQ(service.state, PENDING);
196+
197+
auto wait_thread = std::thread([&] { service.wait_until(STOPPED); });
198+
199+
service.start();
200+
std::this_thread::sleep_for(std::chrono::milliseconds(10));
201+
202+
service.signal(service.terminate);
203+
wait_thread.join();
204+
205+
ASSERT_EQ(service.state, STOPPED);
206+
}
207+
208+
TEST_F(AsyncContextTest, WaitFromPendingToStopped)
209+
{
210+
using enum async_context::context_state;
211+
212+
auto service = basic_context_thread<test_service>();
213+
ASSERT_EQ(service.state, PENDING);
214+
215+
auto wait_thread = std::thread([&] { service.wait(); });
216+
217+
service.start();
218+
std::this_thread::sleep_for(std::chrono::milliseconds(10));
219+
220+
service.signal(service.terminate);
221+
wait_thread.join();
222+
223+
ASSERT_EQ(service.state, STOPPED);
224+
}
225+
226+
TEST_F(AsyncContextTest, WaitFromStartedToStopped)
227+
{
228+
using enum async_context::context_state;
229+
230+
auto service = basic_context_thread<test_service>();
231+
service.start();
232+
ASSERT_EQ(service.state, STARTED);
233+
234+
auto wait_thread = std::thread([&] { service.wait(); });
235+
236+
service.signal(service.terminate);
237+
wait_thread.join();
238+
239+
ASSERT_EQ(service.state, STOPPED);
240+
}
241+
242+
TEST_F(AsyncContextTest, WaitWhenAlreadyStopped)
243+
{
244+
using enum async_context::context_state;
245+
246+
auto service = basic_context_thread<test_service>();
247+
service.start();
248+
ASSERT_EQ(service.state, STARTED);
249+
250+
service.signal(service.terminate);
251+
service.state.wait(STARTED);
252+
ASSERT_EQ(service.state, STOPPED);
253+
254+
service.wait();
255+
256+
EXPECT_EQ(service.state, STOPPED);
257+
}
258+
259+
TEST_F(AsyncContextTest, WaitMultipleWaiters)
260+
{
261+
using enum async_context::context_state;
262+
263+
auto service = basic_context_thread<test_service>();
264+
service.start();
265+
ASSERT_EQ(service.state, STARTED);
266+
267+
std::atomic<int> waiters_unblocked{0};
268+
269+
auto wait_thread1 = std::thread([&] {
270+
service.wait();
271+
waiters_unblocked.fetch_add(1);
272+
});
273+
274+
auto wait_thread2 = std::thread([&] {
275+
service.wait();
276+
waiters_unblocked.fetch_add(1);
277+
});
278+
279+
auto wait_thread3 = std::thread([&] {
280+
service.wait();
281+
waiters_unblocked.fetch_add(1);
282+
});
283+
284+
std::this_thread::sleep_for(std::chrono::milliseconds(10));
285+
EXPECT_EQ(waiters_unblocked.load(), 0);
286+
287+
service.signal(service.terminate);
288+
289+
wait_thread1.join();
290+
wait_thread2.join();
291+
wait_thread3.join();
292+
293+
EXPECT_EQ(waiters_unblocked.load(), 3);
294+
ASSERT_EQ(service.state, STOPPED);
295+
}
296+
297+
TEST_F(AsyncContextTest, WaitNoexceptGuarantee)
298+
{
299+
auto service = basic_context_thread<test_service>();
300+
service.start();
301+
302+
EXPECT_TRUE(noexcept(service.wait()));
303+
304+
service.signal(service.terminate);
305+
service.wait();
306+
}
106307
// NOLINTEND

tests/test_async_tcp_service.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ TEST_F(AsyncTcpServiceTest, AsyncServerTest)
109109
using namespace io;
110110
using namespace io::socket;
111111
using enum async_context::signals;
112-
using enum async_context::context_states;
112+
using enum async_context::context_state;
113113

114114
server_v4->start(addr_v4);
115115
server_v6->start(addr_v6);
@@ -155,7 +155,7 @@ TEST_F(AsyncTcpServiceTest, ServerDrainTest)
155155
using namespace std::chrono;
156156
using namespace net::timers;
157157
using enum async_context::signals;
158-
using enum async_context::context_states;
158+
using enum async_context::context_state;
159159

160160
server_v4->start(addr_v4);
161161
ASSERT_EQ(server_v4->state, STARTED);

tests/test_async_udp_service.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ TEST_F(AsyncUDPServiceTest, AsyncServerTest)
9696
using namespace io;
9797
using namespace io::socket;
9898
using enum async_context::signals;
99-
using enum async_context::context_states;
99+
using enum async_context::context_state;
100100

101101
server_v4->start(addr_v4);
102102
server_v6->start(addr_v6);

tests/test_mock_socketpair.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ struct test_service {
5151

5252
TEST_F(AsyncServiceTest, StartTest)
5353
{
54-
using enum async_context::context_states;
54+
using enum async_context::context_state;
5555

5656
auto service = basic_context_thread<test_service>();
5757

0 commit comments

Comments
 (0)