Skip to content

Commit d13b589

Browse files
committed
feat: Added timers to asynchronous contexts.
1 parent 283b40e commit d13b589

22 files changed

Lines changed: 1070 additions & 1503 deletions

.clang-tidy

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Checks: >
66
-cppcoreguidelines-narrowing-conversions,
77
modernize-*,
88
performance-*,
9+
-performance-unnecessary-value-param,
910
readability-*,
1011
-readability-braces-around-statements,
1112
-readability-magic-numbers,

CMakePresets.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,7 @@
4747
"name": "debug",
4848
"description": "tests",
4949
"displayName": "Debug",
50-
"configurePreset": "debug",
51-
"execution": {
52-
"jobs": 1
53-
}
50+
"configurePreset": "debug"
5451
}
5552
],
5653
"packagePresets": [

include/net/detail/concepts.hpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,22 @@ struct async_context;
3131
* @brief The root namespace for all cppnet components.
3232
*/
3333
namespace net {
34+
/** @brief ServiceLike describes types that behave like an application or
35+
* service. */
3436
template <typename S>
3537
concept ServiceLike = requires(S service, service::async_context &ctx) {
3638
{ service.signal_handler(1) } noexcept -> std::same_as<void>;
3739
{ service.start(ctx) } noexcept -> std::same_as<void>;
3840
};
41+
42+
/** @brief This namespace is for timers and interrupts. */
43+
namespace timers {
44+
/** @brief A concept for constraining interrupt sources. */
45+
template <typename Tag>
46+
concept InterruptSource = requires(const Tag tag) {
47+
{ tag.interrupt() } noexcept -> std::same_as<void>;
48+
};
49+
} // namespace timers.
50+
3951
} // namespace net
4052
#endif // CPPNET_CONCEPT_HPP

include/net/service/context_thread.hpp

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,12 @@
2222
#define CPPNET_CONTEXT_THREAD_HPP
2323
#include "net/detail/concepts.hpp"
2424
#include "net/detail/immovable.hpp"
25-
#include "net/timers/interrupt.hpp"
25+
#include "net/timers/timers.hpp"
2626

2727
#include <exec/async_scope.hpp>
2828
#include <io/io.hpp>
2929

3030
#include <atomic>
31-
#include <condition_variable>
3231
#include <cstdint>
3332
#include <mutex>
3433
#include <thread>
@@ -48,8 +47,8 @@ struct async_context : detail::immovable {
4847
using signal_mask = std::uint64_t;
4948
/** @brief Interrupt source type. */
5049
using interrupt_source = timers::socketpair_interrupt_source_t;
51-
/** @brief The interrupt type. */
52-
using interrupt_type = timers::interrupt<interrupt_source>;
50+
/** @brief The timers type. */
51+
using timers_type = timers::timers<interrupt_source>;
5352

5453
/** @brief An enum of all valid async context signals. */
5554
enum signals : std::uint8_t { terminate = 0, user1, END };
@@ -64,15 +63,18 @@ struct async_context : detail::immovable {
6463
std::atomic<context_states> state{PENDING};
6564
/** @brief The active signal mask. */
6665
std::atomic<signal_mask> sigmask;
67-
/** @brief The event loop interrupt. */
68-
interrupt_type interrupt;
66+
/** @brief The event loop timers. */
67+
timers_type timers;
6968

7069
/**
7170
* @brief Sets the signal mask, then interrupts the service.
7271
* @param signum The signal to send. Must be in range of
7372
* enum signals.
7473
*/
75-
auto signal(int signum) -> void;
74+
inline auto signal(int signum) -> void;
75+
76+
/** @brief Calls the timers interrupt. */
77+
inline auto interrupt() const noexcept -> void;
7678
};
7779

7880
/**
@@ -92,10 +94,6 @@ template <ServiceLike Service> class context_thread : public async_context {
9294
using clock = std::chrono::steady_clock;
9395
/** @brief The duration type. */
9496
using duration = std::chrono::milliseconds;
95-
96-
/** @brief Internal context loop interval.*/
97-
static constexpr int INTERVAL_MS = 2000;
98-
9997
/**
10098
* @brief An interrupt service routine.
10199
*
@@ -137,9 +135,7 @@ template <ServiceLike Service> class context_thread : public async_context {
137135
* thread.
138136
* @param args The arguments to forward to the Service constructor.
139137
*/
140-
template <typename... Args>
141-
auto start(std::mutex &mtx, std::condition_variable &cvar,
142-
Args &&...args) -> void;
138+
template <typename... Args> auto start(Args &&...args) -> void;
143139

144140
/** @brief The destructor signals the thread before joining it. */
145141
~context_thread();

include/net/service/impl/async_context_impl.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,11 @@ inline auto async_context::signal(int signum) -> void
3131
interrupt();
3232
}
3333

34+
/** @brief Calls the timers interrupt. */
35+
inline auto async_context::interrupt() const noexcept -> void
36+
{
37+
static_cast<const timers_type::interrupt_source_t &>(timers).interrupt();
38+
}
39+
3440
} // namespace net::service
3541
#endif // CPPNET_ASYNC_CONTEXT_IMPL_HPP

include/net/service/impl/context_thread_impl.hpp

Lines changed: 39 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
#pragma once
2222
#ifndef CPPNET_CONTEXT_THREAD_IMPL_HPP
2323
#define CPPNET_CONTEXT_THREAD_IMPL_HPP
24-
#include "net/detail/with_lock.hpp"
2524
#include "net/service/context_thread.hpp"
2625

2726
#include <stdexec/execution.hpp>
@@ -42,10 +41,7 @@ auto context_thread<Service>::isr(async_scope &scope,
4241
static auto msg = socket_message{.buffers = buffer};
4342

4443
if (!handle())
45-
{
46-
scope.request_stop();
4744
return;
48-
}
4945

5046
auto recvmsg =
5147
io::recvmsg(socket, msg, 0) |
@@ -57,55 +53,67 @@ auto context_thread<Service>::isr(async_scope &scope,
5753
template <ServiceLike Service>
5854
auto context_thread<Service>::stop() noexcept -> void
5955
{
60-
auto socket = interrupt.sockets[1];
61-
interrupt.sockets[1] = interrupt.INVALID_SOCKET;
62-
if (socket != interrupt.INVALID_SOCKET)
56+
auto socket = timers.sockets[1];
57+
timers.sockets[1] = timers.INVALID_SOCKET;
58+
if (socket != timers.INVALID_SOCKET)
6359
io::socket::close(socket);
6460
state = STOPPED;
6561
}
6662

6763
template <ServiceLike Service>
6864
template <typename... Args>
69-
auto context_thread<Service>::start(std::mutex &mtx,
70-
std::condition_variable &cvar,
71-
Args &&...args) -> void
65+
auto context_thread<Service>::start(Args &&...args) -> void
7266
{
73-
auto lock = std::lock_guard{mtx};
67+
auto lock = std::lock_guard{mtx_};
7468
if (started_)
7569
throw std::invalid_argument("context_thread can't be started twice.");
7670

7771
server_ = std::thread([&]() noexcept {
7872
using namespace detail;
7973
using namespace io::socket;
74+
using namespace std::chrono;
8075

8176
auto service = Service{std::forward<Args>(args)...};
82-
auto &sockets = interrupt.sockets;
77+
auto &sockets = timers.sockets;
8378
if (!socketpair(AF_UNIX, SOCK_STREAM, 0, sockets.data()))
8479
{
80+
const auto token = scope.get_stop_token();
81+
8582
isr(scope, poller.emplace(sockets[0]), [&]() noexcept {
8683
auto sigmask_ = sigmask.exchange(0);
8784
for (int signum = 0; auto mask = (sigmask_ >> signum); ++signum)
8885
{
8986
if (mask & (1 << 0))
9087
service.signal_handler(signum);
9188
}
92-
return !(sigmask_ & (1 << terminate));
93-
});
9489

95-
state = STARTED;
96-
cvar.notify_all();
90+
if (sigmask_ & (1 << terminate))
91+
{
92+
scope.request_stop();
93+
timers.add(
94+
seconds(1),
95+
[&](timers::timer_id) { service.signal_handler(terminate); },
96+
seconds(1));
97+
}
98+
99+
return !token.stop_requested();
100+
});
97101

98102
service.start(static_cast<async_context &>(*this));
103+
state = STARTED;
99104

100-
const auto token = scope.get_stop_token();
101105
if (token.stop_requested())
106+
{
107+
state = STOPPED;
102108
signal(terminate);
109+
}
103110

111+
state.notify_all();
104112
run(service, token);
105113
}
106114

107-
with_lock(std::unique_lock{mtx}, [&]() noexcept { stop(); });
108-
cvar.notify_all();
115+
stop();
116+
state.notify_all();
109117
});
110118

111119
started_ = true;
@@ -126,27 +134,21 @@ auto context_thread<Service>::run(Service &service,
126134
const StopToken &token) -> void
127135
{
128136
using namespace stdexec;
129-
using std::chrono::duration_cast;
137+
using namespace std::chrono;
130138

131-
int next = 0;
132-
auto start = clock::now();
133-
auto is_empty = false;
134-
scope.spawn(poller.on_empty() | then([&]() noexcept { is_empty = true; }));
139+
auto next = timers.resolve();
140+
int wait_ms =
141+
(next.count() < 0) ? next.count() : duration_cast<duration>(next).count();
135142

136-
while (poller.wait_for(next) || !is_empty)
137-
{
138-
const auto now = clock::now();
143+
auto is_empty = std::atomic_flag();
144+
scope.spawn(poller.on_empty() |
145+
then([&]() noexcept { is_empty.test_and_set(); }));
139146

140-
next -= duration_cast<duration>(now - start).count();
141-
if (next <= 0)
142-
{
143-
if (token.stop_requested())
144-
service.signal_handler(terminate);
145-
146-
next = INTERVAL_MS;
147-
}
148-
149-
start = now;
147+
while (poller.wait_for(wait_ms) || !is_empty.test())
148+
{
149+
next = timers.resolve();
150+
wait_ms = (next.count() < 0) ? next.count()
151+
: duration_cast<duration>(next).count();
150152
}
151153
}
152154
} // namespace net::service

include/net/timers/impl/interrupt_impl.hpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@ inline auto socketpair_interrupt_source_t::interrupt() const noexcept -> void
3030
static constexpr auto buf = std::array<char, 1>{'x'};
3131
static const auto msg = socket_message<sockaddr_in>{.buffers = buf};
3232

33-
if (sockets[1] != INVALID_SOCKET)
34-
::io::sendmsg(sockets[1], msg, 0);
33+
::io::sendmsg(sockets[1], msg, MSG_NOSIGNAL);
3534
}
3635

3736
template <InterruptSource Interrupt>

0 commit comments

Comments
 (0)