diff --git a/.github/workflows/windows-msvc.yml b/.github/workflows/windows-msvc.yml index 90fb040..a139c19 100644 --- a/.github/workflows/windows-msvc.yml +++ b/.github/workflows/windows-msvc.yml @@ -11,15 +11,24 @@ on: jobs: build: - name: Build & Test (${{ matrix.tls.name }}) + name: Build & Test (${{ matrix.arch.name }}, ${{ matrix.build_type }}, ${{ matrix.tls.name }}) runs-on: windows-latest timeout-minutes: 10 strategy: fail-fast: false matrix: - build_type: [Debug] + build_type: [Debug, Release] std: [23] + arch: + - name: x64 + cmake_arch: x64 + vcpkg_triplet: x64-windows + build_dir_suffix: x64 + - name: x86 + cmake_arch: Win32 + vcpkg_triplet: x86-windows + build_dir_suffix: x86 tls: - name: No TLS cmake_arg: "" @@ -30,9 +39,6 @@ jobs: build_dir: build-wolfssl use_vcpkg: true - env: - VCPKG_DEFAULT_TRIPLET: x64-windows - steps: - uses: actions/checkout@v4 @@ -44,19 +50,22 @@ jobs: - name: Install wolfSSL (vcpkg) if: matrix.tls.use_vcpkg + shell: pwsh run: | - & "$env:VCPKG_ROOT\vcpkg.exe" install "wolfssl[asio]" --triplet "$env:VCPKG_DEFAULT_TRIPLET" + & "$env:VCPKG_ROOT\vcpkg.exe" install "wolfssl[asio]" --triplet "${{ matrix.arch.vcpkg_triplet }}" - name: Configure CMake shell: pwsh run: | + $buildDir = "${{ github.workspace }}/${{ matrix.tls.build_dir }}-${{ matrix.arch.build_dir_suffix }}-${{ matrix.build_type }}" $vcpkgCmakeArgs = @() if ("${{ matrix.tls.use_vcpkg }}" -eq "true") { $vcpkgCmakeArgs += "-DCMAKE_TOOLCHAIN_FILE=$env:VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" - $vcpkgCmakeArgs += "-DVCPKG_TARGET_TRIPLET=$env:VCPKG_DEFAULT_TRIPLET" + $vcpkgCmakeArgs += "-DVCPKG_TARGET_TRIPLET=${{ matrix.arch.vcpkg_triplet }}" } - cmake -B "${{ github.workspace }}/${{ matrix.tls.build_dir }}" ` + cmake -B $buildDir ` + -A "${{ matrix.arch.cmake_arch }}" ` -DCMAKE_CXX_STANDARD=${{ matrix.std }} ` -DCMAKE_CXX_STANDARD_REQUIRED=ON ` -DCMAKE_CXX_EXTENSIONS=OFF ` @@ -65,8 +74,14 @@ jobs: $vcpkgCmakeArgs - name: Build - run: cmake --build "${{ github.workspace }}/${{ matrix.tls.build_dir }}" --config ${{ matrix.build_type }} --parallel + shell: pwsh + run: | + $buildDir = "${{ github.workspace }}/${{ matrix.tls.build_dir }}-${{ matrix.arch.build_dir_suffix }}-${{ matrix.build_type }}" + cmake --build $buildDir --config ${{ matrix.build_type }} --parallel - name: Test - working-directory: ${{ github.workspace }}/${{ matrix.tls.build_dir }} - run: ctest --output-on-failure -C ${{ matrix.build_type }} + shell: pwsh + working-directory: ${{ github.workspace }} + run: | + $buildDir = "${{ github.workspace }}/${{ matrix.tls.build_dir }}-${{ matrix.arch.build_dir_suffix }}-${{ matrix.build_type }}" + ctest --test-dir $buildDir --output-on-failure -C ${{ matrix.build_type }} diff --git a/cmake/AeroUseAsio.cmake b/cmake/AeroUseAsio.cmake index 5ad7411..a04c590 100644 --- a/cmake/AeroUseAsio.cmake +++ b/cmake/AeroUseAsio.cmake @@ -4,10 +4,6 @@ include(FetchContent) set(AERO_ASIO_DEFINITIONS ASIO_STANDALONE - ASIO_NO_DEPRECATED - ASIO_NO_TS_EXECUTORS - ASIO_DISABLE_BUFFER_DEBUGGING - ASIO_NO_TYPEID ) function(_aero_select_first_existing_asio_target out_var) diff --git a/include/aero/detail/aligned_allocator.hpp b/include/aero/detail/aligned_allocator.hpp new file mode 100644 index 0000000..3138a1f --- /dev/null +++ b/include/aero/detail/aligned_allocator.hpp @@ -0,0 +1,48 @@ +#ifndef AERO_DETAIL_ALIGNED_ALLOCATOR_HPP +#define AERO_DETAIL_ALIGNED_ALLOCATOR_HPP + +#include +#include + +namespace aero::detail { + + constexpr inline std::size_t default_allocator_alignment = 16; + + // See https://github.com/chriskohlhoff/asio/pull/1724 + template + struct alignas(Alignment < alignof(T) ? alignof(T) : Alignment) aligned_allocator { + using value_type = T; + using is_always_equal = std::true_type; + + aligned_allocator() noexcept = default; + + template + explicit aligned_allocator(const aligned_allocator&) noexcept {} + + template + struct rebind { + using other = aligned_allocator; + }; + + [[nodiscard]] T* allocate(std::size_t element_count) { + return asio::recycling_allocator{}.allocate(element_count); + } + + void deallocate(T* pointer, std::size_t element_count) noexcept { + asio::recycling_allocator{}.deallocate(pointer, element_count); + } + + template + bool operator==(const aligned_allocator&) const noexcept { + return true; + } + + template + bool operator!=(const aligned_allocator&) const noexcept { + return false; + } + }; + +} // namespace aero::detail + +#endif diff --git a/include/aero/net/detail/basic_transport.hpp b/include/aero/net/detail/basic_transport.hpp index 67ef798..c4f9b9b 100644 --- a/include/aero/net/detail/basic_transport.hpp +++ b/include/aero/net/detail/basic_transport.hpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -25,6 +26,7 @@ #include #include +#include "aero/detail/aligned_allocator.hpp" #include "aero/net/concepts/transport.hpp" namespace aero::net::detail { @@ -70,7 +72,9 @@ namespace aero::net::detail { template auto async_read_some(CompletionToken&& token) { - return asio::async_initiate( + auto bound_token = asio::bind_allocator(aero::detail::aligned_allocator<>{}, std::forward(token)); + + return asio::async_initiate( asio::co_composed( [this](auto) -> void { auto [read_ec, bytes_read] = co_await stream_.async_read_some(get_mutable_buffer(), asio::as_tuple(asio::deferred)); @@ -80,7 +84,7 @@ namespace aero::net::detail { co_return {std::error_code{}, get_buffer_view(0, bytes_read)}; }, strand_), - token); + bound_token); } template @@ -90,7 +94,9 @@ namespace aero::net::detail { template auto async_read_exactly(std::size_t bytes_count, CompletionToken&& token) { - return asio::async_initiate( + auto bound_token = asio::bind_allocator(aero::detail::aligned_allocator<>{}, std::forward(token)); + + return asio::async_initiate( asio::co_composed( [this, bytes_count](auto) -> void { auto [read_ec, bytes_read] = co_await asio::async_read(stream_, @@ -103,12 +109,14 @@ namespace aero::net::detail { co_return {std::error_code{}, get_buffer_view(0, bytes_read)}; }, strand_), - token); + bound_token); } template auto async_write(const_buffer buffer, CompletionToken&& token) { - return asio::async_initiate( + auto bound_token = asio::bind_allocator(aero::detail::aligned_allocator<>{}, std::forward(token)); + + return asio::async_initiate( asio::co_composed( [this](auto, const_buffer buffer) -> void { asio::const_buffer asio_buffer(buffer.data(), buffer.size()); @@ -128,7 +136,7 @@ namespace aero::net::detail { co_return {write_request->result.ec, write_request->result.bytes_written}; }, strand_), - token, + bound_token, buffer); } diff --git a/include/aero/net/tcp_transport.hpp b/include/aero/net/tcp_transport.hpp index 877e713..108ed61 100644 --- a/include/aero/net/tcp_transport.hpp +++ b/include/aero/net/tcp_transport.hpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -18,6 +19,7 @@ #include #include +#include "aero/detail/aligned_allocator.hpp" #include "aero/net/concepts/transport.hpp" #include "aero/net/detail/basic_transport.hpp" #include "aero/net/error.hpp" @@ -40,7 +42,9 @@ namespace aero::net { template auto async_connect(std::string host, port_type port, CompletionToken&& token) { - return asio::async_initiate( + auto bound_token = asio::bind_allocator(aero::detail::aligned_allocator<>{}, std::forward(token)); + + return asio::async_initiate( asio::co_composed( [this](auto, std::string host, port_type port) -> void { using net::error::connect_error; @@ -79,14 +83,16 @@ namespace aero::net { co_return std::error_code{}; }, get_strand()), - token, + bound_token, std::move(host), port); } template auto async_shutdown(CompletionToken&& token) { - return asio::async_initiate( + auto bound_token = asio::bind_allocator(aero::detail::aligned_allocator<>{}, std::forward(token)); + + return asio::async_initiate( asio::co_composed( [this](auto) -> void { resolver_.cancel(); @@ -108,7 +114,7 @@ namespace aero::net { co_return shutdown_ec ? shutdown_ec : close_ec; }, get_strand()), - token); + bound_token); } template diff --git a/include/aero/net/tls_transport.hpp b/include/aero/net/tls_transport.hpp index ba056bd..b8b2db4 100644 --- a/include/aero/net/tls_transport.hpp +++ b/include/aero/net/tls_transport.hpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -19,6 +20,7 @@ #include #include +#include "aero/detail/aligned_allocator.hpp" #include "aero/net/concepts/transport.hpp" #include "aero/net/detail/basic_transport.hpp" #include "aero/net/error.hpp" @@ -71,7 +73,9 @@ namespace aero::net { template auto async_connect(std::string host, port_type port, CompletionToken&& token) { - return asio::async_initiate( + auto bound_token = asio::bind_allocator(aero::detail::aligned_allocator<>{}, std::forward(token)); + + return asio::async_initiate( asio::co_composed( [this](auto, std::string host, port_type port) -> void { using net::error::connect_error; @@ -121,14 +125,16 @@ namespace aero::net { co_return co_await this->async_handshake(asio::as_tuple(asio::deferred)); }, get_strand()), - token, + bound_token, std::move(host), port); } template auto async_shutdown(CompletionToken&& token) { - return asio::async_initiate( + auto bound_token = asio::bind_allocator(aero::detail::aligned_allocator<>{}, std::forward(token)); + + return asio::async_initiate( asio::co_composed( [this](auto) -> void { resolver_.cancel(); @@ -150,7 +156,7 @@ namespace aero::net { co_return shutdown_ec ? shutdown_ec : close_ec; }, get_strand()), - token); + bound_token); } template @@ -200,7 +206,9 @@ namespace aero::net { template auto async_handshake(CompletionToken&& token) { - return asio::async_initiate( + auto bound_token = asio::bind_allocator(aero::detail::aligned_allocator<>{}, std::forward(token)); + + return asio::async_initiate( asio::co_composed( [this](auto) -> void { using tls::detail::x509_verify_error; @@ -231,7 +239,7 @@ namespace aero::net { co_return handshake_ec; }, get_strand()), - token); + bound_token); } net::detail::basic_transport basic_transport_; diff --git a/include/aero/websocket/basic_client.hpp b/include/aero/websocket/basic_client.hpp index 13a82f8..efec9e0 100644 --- a/include/aero/websocket/basic_client.hpp +++ b/include/aero/websocket/basic_client.hpp @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -29,6 +30,7 @@ #include #include "aero/deadline.hpp" +#include "aero/detail/aligned_allocator.hpp" #include "aero/detail/final_action.hpp" #include "aero/error.hpp" #include "aero/http/headers.hpp" @@ -133,7 +135,9 @@ namespace aero::websocket { template auto async_connect(websocket::uri uri, http::headers headers, CompletionToken&& token) { - return asio::async_initiate( + auto bound_token = asio::bind_allocator(aero::detail::aligned_allocator<>{}, std::forward(token)); + + return asio::async_initiate( asio::co_composed( [this](auto, websocket::uri uri, http::headers headers) -> void { reset_connection_state(state::connecting); @@ -191,7 +195,7 @@ namespace aero::websocket { co_return {std::error_code{}, std::move(*parsed_headers)}; }, transport_.get_strand()), - token, + bound_token, std::move(uri), std::move(headers)); } @@ -199,7 +203,9 @@ namespace aero::websocket { template auto async_connect(std::expected parsed_uri, http::headers headers, CompletionToken&& token) { - return asio::async_initiate( + auto bound_token = asio::bind_allocator(aero::detail::aligned_allocator<>{}, std::forward(token)); + + return asio::async_initiate( asio::co_composed( [this](auto, std::expected parsed_uri, http::headers headers) -> void { if (!parsed_uri.has_value()) { @@ -209,7 +215,7 @@ namespace aero::websocket { co_return co_await this->async_connect(std::move(*parsed_uri), std::move(headers), return_as_deferred_tuple()); }, transport_.get_strand()), - token, + bound_token, std::move(parsed_uri), std::move(headers)); } @@ -237,7 +243,9 @@ namespace aero::websocket { // Caller must ensure that given buffer remains valid until the operation is completed template auto async_send_text(std::string_view text, CompletionToken&& token) { - return asio::async_initiate( + auto bound_token = asio::bind_allocator(aero::detail::aligned_allocator<>{}, std::forward(token)); + + return asio::async_initiate( asio::co_composed( [this](auto, std::string_view text) -> void { if (!is_current_state(state::open) || is_close_received()) { @@ -252,14 +260,16 @@ namespace aero::websocket { co_return co_await async_write_bytes(*frame, return_as_deferred_tuple()); }, transport_.get_strand()), - token, + bound_token, text); } // Caller must ensure that given buffer remains valid until the operation is completed template auto async_send_binary(std::span data, CompletionToken&& token) { - return asio::async_initiate( + auto bound_token = asio::bind_allocator(aero::detail::aligned_allocator<>{}, std::forward(token)); + + return asio::async_initiate( asio::co_composed( [this](auto, std::span data) -> void { if (!is_current_state(state::open) || is_close_received()) { @@ -274,7 +284,7 @@ namespace aero::websocket { co_return co_await async_write_bytes(*frame, return_as_deferred_tuple()); }, transport_.get_strand()), - token, + bound_token, data); } @@ -291,7 +301,9 @@ namespace aero::websocket { template auto async_ping(std::span data, CompletionToken&& token) { - return asio::async_initiate( + auto bound_token = asio::bind_allocator(aero::detail::aligned_allocator<>{}, std::forward(token)); + + return asio::async_initiate( asio::co_composed( [this](auto, std::span data) -> void { if (!is_current_state(state::open) || is_close_received()) { @@ -306,13 +318,15 @@ namespace aero::websocket { co_return co_await async_write_bytes(*frame, return_as_deferred_tuple()); }, transport_.get_strand()), - token, + bound_token, data); } template auto async_pong(std::span data, CompletionToken&& token) { - return asio::async_initiate( + auto bound_token = asio::bind_allocator(aero::detail::aligned_allocator<>{}, std::forward(token)); + + return asio::async_initiate( asio::co_composed( [this](auto, std::span data) -> void { if (!is_current_state(state::open, state::closing) || is_close_received()) { @@ -327,7 +341,7 @@ namespace aero::websocket { co_return co_await async_write_bytes(*frame, return_as_deferred_tuple()); }, transport_.get_strand()), - token, + bound_token, data); } @@ -344,7 +358,9 @@ namespace aero::websocket { template auto async_close(websocket::close_code code, std::string_view reason, CompletionToken&& token) { - return asio::async_initiate( + auto bound_token = asio::bind_allocator(aero::detail::aligned_allocator<>{}, std::forward(token)); + + return asio::async_initiate( asio::co_composed( [this](auto, websocket::close_code close_code, std::string_view close_reason) -> void { if (is_close_code_server_only(close_code)) { @@ -430,7 +446,7 @@ namespace aero::websocket { } }, transport_.get_strand()), - token, + bound_token, code, reason); } @@ -443,12 +459,14 @@ namespace aero::websocket { // Tear down transport without performing close handshake template auto async_force_close(CompletionToken&& token) { - return async_finalize_session(std::error_code{}, token); + return async_finalize_session(std::error_code{}, std::forward(token)); } template auto async_read(CompletionToken&& token) { - return asio::async_initiate( + auto bound_token = asio::bind_allocator(aero::detail::aligned_allocator<>{}, std::forward(token)); + + return asio::async_initiate( asio::co_composed( [this](auto) -> void { // Prevent concurrent async_read operations (one read at a time) @@ -535,7 +553,7 @@ namespace aero::websocket { } }, transport_.get_strand()), - token); + bound_token); } std::expected connect(websocket::uri uri, http::headers headers) { @@ -726,7 +744,9 @@ namespace aero::websocket { template auto async_write_bytes(std::span frame, CompletionToken&& token) { - return asio::async_initiate( + auto bound_token = asio::bind_allocator(aero::detail::aligned_allocator<>{}, std::forward(token)); + + return asio::async_initiate( asio::co_composed( [this](auto, std::span frame) -> void { auto [write_ec, bytes_written] = co_await transport_.async_write(frame, return_as_deferred_tuple()); @@ -749,13 +769,15 @@ namespace aero::websocket { co_return co_await async_finalize_session(write_ec, return_as_deferred_tuple()); }, transport_.get_strand()), - token, + bound_token, frame); } template auto async_send_close(websocket::close_code code, std::optional reason, CompletionToken&& token) { - return asio::async_initiate( + auto bound_token = asio::bind_allocator(aero::detail::aligned_allocator<>{}, std::forward(token)); + + return asio::async_initiate( asio::co_composed( [this](auto, close_code code, std::optional reason) -> void { if (is_close_sent()) { @@ -776,7 +798,7 @@ namespace aero::websocket { co_return std::error_code{}; }, transport_.get_strand()), - token, + bound_token, code, std::move(reason)); } @@ -787,7 +809,9 @@ namespace aero::websocket { // code, drains a little, then force-shutdowns the transport and resets all internal state template auto async_fail_connection(std::error_code fatal_ec, CompletionToken&& token) { - return asio::async_initiate( + auto bound_token = asio::bind_allocator(aero::detail::aligned_allocator<>{}, std::forward(token)); + + return asio::async_initiate( asio::co_composed( [this](auto, std::error_code fatal_ec) -> void { using namespace std::chrono_literals; @@ -816,7 +840,7 @@ namespace aero::websocket { co_return {}; }, transport_.get_strand()), - token, + bound_token, fatal_ec); } @@ -826,7 +850,9 @@ namespace aero::websocket { // internal state. This does not initiate a protocol failure, only finalizes/cleans up template auto async_finalize_session(std::error_code final_ec, CompletionToken&& token) { - return asio::async_initiate( + auto bound_token = asio::bind_allocator(aero::detail::aligned_allocator<>{}, std::forward(token)); + + return asio::async_initiate( asio::co_composed( [this](auto state, std::error_code final_ec) -> void { // Disable cancellation via the coroutine associated cancellation slot for cleanup path. @@ -852,13 +878,15 @@ namespace aero::websocket { co_return result_ec; }, transport_.get_strand()), - token, + bound_token, final_ec); } template auto async_respond_to_control_message(const websocket::message& message, CompletionToken&& token) { - return asio::async_initiate( + auto bound_token = asio::bind_allocator(aero::detail::aligned_allocator<>{}, std::forward(token)); + + return asio::async_initiate( asio::co_composed( [this](auto, const websocket::message& message) -> void { if (message.is_ping()) { @@ -875,7 +903,7 @@ namespace aero::websocket { co_return std::error_code{}; }, transport_.get_strand()), - token, + bound_token, message); } diff --git a/tests/websocket/client_completion_tokens.cpp b/tests/websocket/client_completion_tokens.cpp index 2a6459b..0610824 100644 --- a/tests/websocket/client_completion_tokens.cpp +++ b/tests/websocket/client_completion_tokens.cpp @@ -25,7 +25,7 @@ namespace { using error_awaitable = asio::awaitable>; using message_awaitable = asio::awaitable>; - auto as_tuple_awaitable = asio::as_tuple(asio::use_awaitable); + constexpr auto as_tuple_awaitable = asio::as_tuple(asio::use_awaitable); static_assert( std::same_as().async_connect(std::string_view{}, as_tuple_awaitable)), headers_awaitable>);