Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 7 additions & 17 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,27 +56,16 @@ jobs:
sudo apt-get update
sudo apt-get install -y ninja-build gcc g++ ccache cmake doxygen graphviz python3-all

- name: Cache Python dependencies
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: pip-${{ runner.os }}-${{ hashFiles('.github/workflows/docs.yml') }}
restore-keys: |
pip-${{ runner.os }}-

- name: Install Python dependencies
run: pip install gcovr==7.0

- name: Create cache directories
run: mkdir -p ~/.ccache ~/.cache/CPM

- name: Setup ccache
uses: actions/cache@v4
with:
path: ~/.ccache
key: ccache-${{ runner.os }}-${{ hashFiles('**/*.cpp', '**/*.hpp', '**/*.h', '**/*.c') }}
key: cppnet-ccache-${{ runner.os }}-${{ hashFiles('**/*.cpp', '**/*.hpp', '**/*.h', '**/*.c') }}
restore-keys: |
ccache-${{ runner.os }}-
cppnet-ccache-${{ runner.os }}-

- name: Cache CMake build directory
uses: actions/cache@v4
Expand All @@ -85,22 +74,23 @@ jobs:
build/debug/_deps
build/debug/CMakeCache.txt
build/debug/CMakeFiles
key: cmake-tests-${{ runner.os }}-${{ hashFiles('**/CMakeLists.txt', 'CMakePresets.json') }}
key: cppnet-cmake-${{ runner.os }}-${{ hashFiles('**/CMakeLists.txt', 'CMakePresets.json') }}
restore-keys: |
cmake-tests-${{ runner.os }}-
cppnet-cmake-${{ runner.os }}-

- name: Cache CPM sources.
uses: actions/cache@v4
with:
path: ~/.cache/CPM
key: cpm-${{ runner.os }}-${{ hashFiles('**/CMakeLists.txt') }}
key: cppnet-cpm-${{ runner.os }}-${{ hashFiles('**/CMakeLists.txt') }}
restore-keys: |
cpm-${{ runner.os }}-
cppnet-cpm-${{ runner.os }}-

- name: Configure CMake
run: |
cmake --preset debug \
-DCPM_SOURCE_CACHE='~/.cache/CPM' \
-DCPPNET_BUILD_TESTING='OFF' \
-DCPPNET_BUILD_DOCS='ON'

- name: Build documentation
Expand Down
16 changes: 8 additions & 8 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ jobs:
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: pip-${{ runner.os }}-${{ hashFiles('.github/workflows/tests.yml') }}
key: cppnet-pip-${{ runner.os }}-${{ hashFiles('.github/workflows/tests.yml') }}
restore-keys: |
pip-${{ runner.os }}-
cppnet-pip-${{ runner.os }}-

- name: Install Python dependencies
run: pip install gcovr
Expand All @@ -40,9 +40,9 @@ jobs:
uses: actions/cache@v4
with:
path: ~/.ccache
key: ccache-${{ runner.os }}-${{ hashFiles('**/*.cpp', '**/*.hpp', '**/*.h', '**/*.c') }}
key: cppnet-ccache-${{ runner.os }}-${{ hashFiles('**/*.cpp', '**/*.hpp', '**/*.h', '**/*.c') }}
restore-keys: |
ccache-${{ runner.os }}-
cppnet-ccache-${{ runner.os }}-

- name: Cache CMake build directory
uses: actions/cache@v4
Expand All @@ -51,17 +51,17 @@ jobs:
build/debug/_deps
build/debug/CMakeCache.txt
build/debug/CMakeFiles
key: cmake-tests-${{ runner.os }}-${{ hashFiles('**/CMakeLists.txt', 'CMakePresets.json') }}
key: cppnet-cmake-${{ runner.os }}-${{ hashFiles('**/CMakeLists.txt', 'CMakePresets.json') }}
restore-keys: |
cmake-tests-${{ runner.os }}-
cppnet-cmake-${{ runner.os }}-

- name: Cache CPM sources.
uses: actions/cache@v4
with:
path: ~/.cache/CPM
key: cpm-${{ runner.os }}-${{ hashFiles('**/CMakeLists.txt') }}
key: cppnet-cpm-${{ runner.os }}-${{ hashFiles('**/CMakeLists.txt') }}
restore-keys: |
cpm-${{ runner.os }}-
cppnet-cpm-${{ runner.os }}-

- name: Configure ccache
run: |
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.28)
project(
CppNet
VERSION 0.6.1
VERSION 0.7.6
LANGUAGES CXX)

# net requires at least C++20
Expand Down
3 changes: 3 additions & 0 deletions include/net/cppnet.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@
#define CPPNET_HPP
/** @brief This is the root namespace of cppnet. */
namespace net {} // namespace net
#include "service/async_context.hpp" // IWYU pragma: export
#include "service/async_tcp_service.hpp" // IWYU pragma: export
#include "service/async_udp_service.hpp" // IWYU pragma: export
#include "service/context_thread.hpp" // IWYU pragma: export
#include "timers/interrupt.hpp" // IWYU pragma: export
#include "timers/timers.hpp" // IWYU pragma: export
#endif // CPPNET_HPP
118 changes: 118 additions & 0 deletions include/net/service/async_context.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/* Copyright (C) 2025 Kevin Exton (kevin.exton@pm.me)
*
* cppnet is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* cppnet is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with cppnet. If not, see <https://www.gnu.org/licenses/>.
*/

/**
* @file async_context.hpp
* @brief This file declares an asynchronous execution context.
*/
#pragma once
#ifndef CPPNET_ASYNC_CONTEXT_HPP
#define CPPNET_ASYNC_CONTEXT_HPP
#include "net/detail/immovable.hpp"
#include "net/timers/timers.hpp"

#include <exec/async_scope.hpp>
#include <io/io.hpp>

#include <atomic>
#include <cstdint>
/** @brief This namespace is for network services. */
namespace net::service {

/** @brief An asynchronous execution context. */
struct async_context : detail::immovable {
/** @brief Asynchronous scope type. */
using async_scope = exec::async_scope;
/** @brief The io multiplexer type. */
using multiplexer_type = io::execution::poll_multiplexer;
/** @brief The io triggers type. */
using triggers = io::execution::basic_triggers<multiplexer_type>;
/** @brief The socket dialog type. */
using socket_dialog = triggers::socket_dialog;
/** @brief The socket type. */
using socket_type = io::socket::native_socket_type;
/** @brief The signal mask type. */
using signal_mask = std::uint64_t;
/** @brief Interrupt source type. */
using interrupt_source = timers::socketpair_interrupt_source_t;
/** @brief The timers type. */
using timers_type = timers::timers<interrupt_source>;
/** @brief The clock type. */
using clock = std::chrono::steady_clock;
/** @brief The duration type. */
using duration = std::chrono::milliseconds;

/** @brief An enum of all valid async context signals. */
enum signals : std::uint8_t { terminate = 0, user1, END };
/** @brief An enum of valid context states. */
enum context_states : std::uint8_t { PENDING = 0, STARTED, STOPPED };

/** @brief The event loop timers. */
timers_type timers;
/** @brief The asynchronous scope. */
async_scope scope;
/** @brief The poll triggers. */
triggers poller;
/** @brief The active signal mask. */
std::atomic<signal_mask> sigmask;
/** @brief A counter that tracks the context state. */
std::atomic<context_states> state{PENDING};

/**
* @brief Sets the signal mask, then interrupts the service.
* @param signum The signal to send. Must be in range of
* enum signals.
*/
inline auto signal(int signum) -> void;

/** @brief Calls the timers interrupt. */
inline auto interrupt() const noexcept -> void;

/**
* @brief An interrupt service routine for the poller.
* @details When invoked, `isr()` installs an event
* handler on socket events received on `socket`. The routine will be
* continuously re-installed in a loop until it returns false.
* @tparam Fn A callable to run upon receiving an interrupt.
* @param socket The listening socket for interrupts. Its lifetime is
* tied to the lifetime of `routine`.
* @param routine The routine to run upon receiving a poll interrupt on
* `socket`.
* @code
* isr(poller.emplace(sockets[0]), [&]() noexcept {
* auto sigmask_ = sigmask.exchange(0);
* for (int signum = 0; auto mask = (sigmask_ >> signum); ++signum)
* {
* if (mask & (1 << 0))
* service.signal_handler(signum);
* }
* return !(sigmask_ & (1 << terminate));
* });
* @endcode
*/
template <typename Fn>
requires std::is_invocable_r_v<bool, Fn>
auto isr(const socket_dialog &socket, Fn routine) -> void;

/** @brief Runs the event loop. */
inline auto run() -> void;
};

} // namespace net::service

#include "impl/async_context_impl.hpp" // IWYU pragma: export

#endif // CPPNET_ASYNC_CONTEXT_HPP
91 changes: 3 additions & 88 deletions include/net/service/context_thread.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,63 +20,12 @@
#pragma once
#ifndef CPPNET_CONTEXT_THREAD_HPP
#define CPPNET_CONTEXT_THREAD_HPP
#include "net/detail/concepts.hpp"
#include "net/detail/immovable.hpp"
#include "net/timers/timers.hpp"
#include "async_context.hpp"

#include <exec/async_scope.hpp>
#include <io/io.hpp>

#include <atomic>
#include <cstdint>
#include <mutex>
#include <thread>
#include <type_traits>
/** @brief This namespace is for network services. */
namespace net::service {

/** @brief Data members for an asynchronous service context. */
struct async_context : detail::immovable {
/** @brief Asynchronous scope type. */
using async_scope = exec::async_scope;
/** @brief The io multiplexer type. */
using multiplexer_type = io::execution::poll_multiplexer;
/** @brief The io triggers type. */
using triggers = io::execution::basic_triggers<multiplexer_type>;
/** @brief The signal mask type. */
using signal_mask = std::uint64_t;
/** @brief Interrupt source type. */
using interrupt_source = timers::socketpair_interrupt_source_t;
/** @brief The timers type. */
using timers_type = timers::timers<interrupt_source>;

/** @brief An enum of all valid async context signals. */
enum signals : std::uint8_t { terminate = 0, user1, END };
/** @brief An enum of valid context states. */
enum context_states : std::uint8_t { PENDING = 0, STARTED, STOPPED };

/** @brief The asynchronous scope. */
async_scope scope;
/** @brief The poll triggers. */
triggers poller;
/** @brief A counter that tracks the context state. */
std::atomic<context_states> state{PENDING};
/** @brief The active signal mask. */
std::atomic<signal_mask> sigmask;
/** @brief The event loop timers. */
timers_type timers;

/**
* @brief Sets the signal mask, then interrupts the service.
* @param signum The signal to send. Must be in range of
* enum signals.
*/
inline auto signal(int signum) -> void;

/** @brief Calls the timers interrupt. */
inline auto interrupt() const noexcept -> void;
};

/**
* @brief A threaded asynchronous service.
*
Expand All @@ -86,33 +35,6 @@ struct async_context : detail::immovable {
* @tparam Service The service to run.
*/
template <ServiceLike Service> class context_thread : public async_context {
/** @brief The socket dialog type. */
using socket_dialog = triggers::socket_dialog;
/** @brief The socket type. */
using socket_type = io::socket::native_socket_type;
/** @brief The clock type. */
using clock = std::chrono::steady_clock;
/** @brief The duration type. */
using duration = std::chrono::milliseconds;
/**
* @brief An interrupt service routine.
*
* This interrupts the running event loop in a thread so
* that signals can be handled.
*
* @param scope The asynchronous scope.
* @param socket The listening socket for interrupts.
* @param handle A function that passes signals to the service
* signal handler.
*/
template <typename Fn>
requires std::is_invocable_r_v<bool, Fn>
static auto isr(async_scope &scope, const socket_dialog &socket,
Fn handle) -> void;

/** @brief Called when the async_service is stopped. */
auto stop() noexcept -> void;

public:
/** @brief Default constructor. */
context_thread() = default;
Expand Down Expand Up @@ -145,19 +67,12 @@ template <ServiceLike Service> class context_thread : public async_context {
/** @brief Flag that guards against starting a thread twice. */
bool started_{false};

/**
* @brief Runs the event loop.
* @tparam StopToken The stop token type.
* @param service The service to run on the event loop.
* @param token The stop token to use with the service.
*/
template <typename StopToken>
auto run(Service &service, const StopToken &token) -> void;
/** @brief Called when the async_service is stopped. */
auto stop() noexcept -> void;
};

} // namespace net::service

#include "impl/async_context_impl.hpp" // IWYU pragma: export
#include "impl/context_thread_impl.hpp" // IWYU pragma: export

#endif // CPPNET_CONTEXT_THREAD_HPP
Loading