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
12 changes: 6 additions & 6 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -98,18 +98,18 @@ add_subdirectory(bench)

#-------------------------------------------------
#
# Tests
# Examples (before tests so Boost::asio is available)
#
#-------------------------------------------------
if (BOOST_CAPY_BUILD_TESTS)
add_subdirectory(test)
if (BOOST_CAPY_BUILD_EXAMPLES)
add_subdirectory(example)
endif ()

#-------------------------------------------------
#
# Examples
# Tests
#
#-------------------------------------------------
if (BOOST_CAPY_BUILD_EXAMPLES)
add_subdirectory(example)
if (BOOST_CAPY_BUILD_TESTS)
add_subdirectory(test)
Comment on lines +101 to +114
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

fd -e txt CMakeLists.txt --exec grep -l -i "asio\|Boost::asio" {}

Repository: cppalliance/capy

Length of output: 158


🏁 Script executed:

# Also search more broadly for find_package and FetchContent related to Boost
rg -i "(find_package|FetchContent).*[Bb]oost|[Bb]oost.*asio" --type cmake

Repository: cppalliance/capy

Length of output: 1360


🏁 Script executed:

# Check the content of test/CMakeLists.txt and example/CMakeLists.txt specifically
echo "=== Checking test/CMakeLists.txt ===" && \
cat test/CMakeLists.txt 2>/dev/null | head -50 && \
echo "=== Checking example/CMakeLists.txt ===" && \
cat example/CMakeLists.txt 2>/dev/null | head -50

Repository: cppalliance/capy

Length of output: 1306


🏁 Script executed:

cat CMakeLists.txt | head -120 | tail -30

Repository: cppalliance/capy

Length of output: 840


🏁 Script executed:

# Check if there's any find_package for Boost at root level before examples
rg "find_package|FetchContent" CMakeLists.txt -B2 -A2 | head -60

Repository: cppalliance/capy

Length of output: 42


Implicit Boost::asio availability dependency is not enforced

The comment states tests depend on examples being built first to make Boost::asio available, but both are independently guarded. If BOOST_CAPY_BUILD_EXAMPLES=OFF and BOOST_CAPY_BUILD_TESTS=ON (a common CI configuration), tests will be configured without Boost::asio, causing a silent failure—tests compile but the conditional link against Boost::asio (in test/unit/CMakeLists.txt) is skipped since the target is never created.

Consider either:

  1. Adding an explicit CMake guard, or
  2. Moving the Boost::asio discovery into the test CMakeLists.txt (or the root) rather than relying on examples as a side-effect.
🛡️ Suggested guard
+if (BOOST_CAPY_BUILD_TESTS AND NOT BOOST_CAPY_BUILD_EXAMPLES)
+    message(FATAL_ERROR
+        "BOOST_CAPY_BUILD_TESTS requires BOOST_CAPY_BUILD_EXAMPLES "
+        "(or a standalone Boost::asio discovery in test/CMakeLists.txt)")
+endif()
+
 `#-------------------------------------------------`
 #
 # Examples (before tests so Boost::asio is available)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CMakeLists.txt` around lines 101 - 114, The tests assume Boost::asio is
provided by building the example, but BOOST_CAPY_BUILD_EXAMPLES and
BOOST_CAPY_BUILD_TESTS are independent; fix by enforcing the dependency or
relocating the discovery: either add an explicit guard in the root
CMakeLists.txt that errors or forces BOOST_CAPY_BUILD_EXAMPLES=ON when
BOOST_CAPY_BUILD_TESTS is ON (referencing the variables
BOOST_CAPY_BUILD_EXAMPLES and BOOST_CAPY_BUILD_TESTS), or move the Boost::asio
discovery (find_package(Boost COMPONENTS asio) / find_package(Boost REQUIRED
COMPONENTS asio)) into the test CMakeLists (e.g., test/unit/CMakeLists.txt) or
the root so tests directly find and link Boost::asio without relying on the
example target; implement one of these options and update
test/unit/CMakeLists.txt to link to Boost::asio unconditionally when tests are
enabled.

endif ()
2 changes: 1 addition & 1 deletion doc/modules/ROOT/pages/4.coroutines/4d.io-awaitable.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ This signature receives:
* `env` — The execution environment containing:
** `env->executor` — The caller's executor for dispatching completions
** `env->stop_token` — A stop token for cooperative cancellation
** `env->allocator` — An optional allocator for frame allocation
** `env->frame_allocator` — An optional frame allocator

The return type enables symmetric transfer.

Expand Down
25 changes: 15 additions & 10 deletions example/allocation/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
# Official repository: https://github.com/cppalliance/capy
#

include(FetchContent)
FetchContent_Declare(mimalloc
GIT_REPOSITORY https://github.com/microsoft/mimalloc
GIT_TAG v2.2.7
GIT_SHALLOW TRUE)
set(MI_BUILD_TESTS OFF CACHE BOOL "Disable mimalloc tests" FORCE)
FetchContent_MakeAvailable(mimalloc)
if(BUILD_SHARED_LIBS)
include(FetchContent)
FetchContent_Declare(mimalloc
GIT_REPOSITORY https://github.com/microsoft/mimalloc
GIT_TAG v2.2.7
GIT_SHALLOW TRUE)
set(MI_BUILD_TESTS OFF CACHE BOOL "Disable mimalloc tests" FORCE)
FetchContent_MakeAvailable(mimalloc)
endif()
Comment on lines +10 to +18
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for BUILD_SHARED_LIBS usage in CMakeLists.txt files
rg -n "BUILD_SHARED_LIBS" --include="CMakeLists.txt" -A2 -B2

Repository: cppalliance/capy

Length of output: 501


🏁 Script executed:

#!/bin/bash
# Read the full example/allocation/CMakeLists.txt file to understand context
cat example/allocation/CMakeLists.txt

Repository: cppalliance/capy

Length of output: 1186


🏁 Script executed:

#!/bin/bash
# Search for mimalloc-related configuration or comments
rg -n "mimalloc" --include="CMakeLists.txt" -B2 -A2

Repository: cppalliance/capy

Length of output: 501


🏁 Script executed:

#!/bin/bash
# Search for BUILD_SHARED_LIBS patterns in CMakeLists.txt files with corrected syntax
fd CMakeLists.txt -x rg "BUILD_SHARED_LIBS" {} -B2 -A2

Repository: cppalliance/capy

Length of output: 633


🏁 Script executed:

#!/bin/bash
# Check the root CMakeLists.txt for any related configuration
cat CMakeLists.txt

Repository: cppalliance/capy

Length of output: 4087


🏁 Script executed:

#!/bin/bash
# Search for any comments or documentation about allocator strategy
rg "mimalloc" -B3 -A3

Repository: cppalliance/capy

Length of output: 5915


Add clarifying comment explaining the BUILD_SHARED_LIBS condition for mimalloc

The mimalloc setup (lines 10–18) and linking of mimalloc-static (lines 31–35) are both gated on BUILD_SHARED_LIBS=ON. This is counterintuitive: BUILD_SHARED_LIBS controls whether Boost::capy is built as a SHARED or STATIC library, and linking a static allocator specifically when using shared libraries—while omitting the mimalloc arm of the benchmark in the static build—needs explanation.

Add a comment clarifying the design intent: e.g., whether this avoids ODR/allocator-instance issues when Boost::capy is shared, or whether the condition should be inverted to NOT BUILD_SHARED_LIBS.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@example/allocation/CMakeLists.txt` around lines 10 - 18, Add a short
clarifying comment above the conditional that fetches and links mimalloc
explaining why the mimalloc arm is gated on BUILD_SHARED_LIBS (e.g., to avoid
ODR/multiple allocator-instance issues when Boost::capy is built SHARED, or
because the static build should not include the mimalloc benchmark), and state
whether this is intentional or should instead be inverted to NOT
BUILD_SHARED_LIBS; reference the BUILD_SHARED_LIBS symbol and the mimalloc /
mimalloc-static linkage to make the intent clear relative to Boost::capy and the
benchmark behavior.


file(GLOB_RECURSE PFILES CONFIGURE_DEPENDS *.cpp *.hpp
CMakeLists.txt
Expand All @@ -26,6 +28,9 @@ add_executable(capy_example_allocation ${PFILES})
set_property(TARGET capy_example_allocation
PROPERTY FOLDER "examples")

target_link_libraries(capy_example_allocation
Boost::capy
mimalloc-static)
target_link_libraries(capy_example_allocation Boost::capy)

if(BUILD_SHARED_LIBS)
target_link_libraries(capy_example_allocation mimalloc-static)
target_compile_definitions(capy_example_allocation PRIVATE BOOST_CAPY_HAS_MIMALLOC=1)
endif()
39 changes: 26 additions & 13 deletions example/allocation/allocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,17 @@

#include <boost/capy.hpp>
#include <boost/capy/test/run_blocking.hpp>
#if BOOST_CAPY_HAS_MIMALLOC
#include <mimalloc.h>
#endif
#include <atomic>
#include <chrono>
#include <cmath>
#include <cstddef>
#include <iomanip>
#include <iostream>
#include <memory_resource>
#include <sstream>

// Prevent HALO from eliding coroutine frame allocations
#if defined(_MSC_VER)
Expand All @@ -39,6 +42,7 @@ using namespace boost::capy;

std::atomic<std::size_t> counter{0};

#if BOOST_CAPY_HAS_MIMALLOC
// Adapts mimalloc to std::pmr::memory_resource
class mi_memory_resource
: public std::pmr::memory_resource
Expand Down Expand Up @@ -71,6 +75,7 @@ class mi_memory_resource
return this == &other;
}
};
#endif

// These coroutines simulate a "composed operation"
// consisting of layered APIs. For example a user's
Expand Down Expand Up @@ -124,6 +129,7 @@ int main()
}
auto t1 = std::chrono::steady_clock::now();

#if BOOST_CAPY_HAS_MIMALLOC
// With mimalloc
counter.store(0);
mi_memory_resource mi_mr;
Expand All @@ -137,6 +143,7 @@ int main()
ctx.run();
}
auto t3 = std::chrono::steady_clock::now();
#endif

// With std::allocator (no recycling)
counter.store(0);
Expand All @@ -152,31 +159,37 @@ int main()

auto ms_recycling =
std::chrono::duration<double, std::milli>(t1 - t0).count();
auto ms_mimalloc =
std::chrono::duration<double, std::milli>(t3 - t2).count();
auto ms_standard =
std::chrono::duration<double, std::milli>(t5 - t4).count();

auto pct_rc_std = std::round(
(ms_standard / ms_recycling - 1.0) * 1000.0) / 10.0;
auto pct_mi_std = std::round(
(ms_standard / ms_mimalloc - 1.0) * 1000.0) / 10.0;
auto pct_rc_mi = std::round(
(ms_mimalloc / ms_recycling - 1.0) * 1000.0) / 10.0;

std::cout
<< iterations << " iterations, "
std::ostringstream os;
os << std::fixed << std::setprecision(1);
os << iterations << " iterations, "
<< "4-deep coroutine chain\n\n"
<< std::fixed << std::setprecision(1)
<< " Recycling allocator: "
<< ms_recycling << " ms (+"
<< pct_rc_std << "% vs std, +"
<< pct_rc_mi << "% vs mimalloc)\n"
<< pct_rc_std << "% vs std";
#if BOOST_CAPY_HAS_MIMALLOC
auto ms_mimalloc =
std::chrono::duration<double, std::milli>(t3 - t2).count();
auto pct_mi_std = std::round(
(ms_standard / ms_mimalloc - 1.0) * 1000.0) / 10.0;
auto pct_rc_mi = std::round(
(ms_mimalloc / ms_recycling - 1.0) * 1000.0) / 10.0;
os << ", +" << pct_rc_mi << "% vs mimalloc)\n"
<< " mimalloc: "
<< ms_mimalloc << " ms (+"
<< pct_mi_std << "% vs std)\n"
<< " std::allocator: "
<< pct_mi_std << "% vs std)\n";
#else
os << ")\n";
#endif
os << " std::allocator: "
<< ms_standard << " ms\n";

std::cout << os.str();

return 0;
}
5 changes: 3 additions & 2 deletions example/asio/api/capy_streams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <boost/asio/post.hpp>

#include <boost/capy/buffers.hpp>
#include <boost/capy/buffers/asio.hpp>
#include <boost/capy/buffers/buffer_array.hpp>
#include <boost/capy/concept/stream.hpp>
#include <coroutine>
Expand Down Expand Up @@ -105,7 +106,7 @@ class asio_socket
cancel_ = std::make_shared<cancel_state>(env->stop_token);

self_->socket_.async_read_some(
capy::mutable_buffer_array<8>(buffers_),
capy::to_asio(capy::mutable_buffer_array<8>(buffers_)),
net::bind_cancellation_slot(
cancel_->signal.slot(),
[this, h, ex = env->executor](
Expand Down Expand Up @@ -167,7 +168,7 @@ class asio_socket
cancel_ = std::make_shared<cancel_state>(env->stop_token);

self_->socket_.async_write_some(
capy::const_buffer_array<8>(buffers_),
capy::to_asio(capy::const_buffer_array<8>(buffers_)),
net::bind_cancellation_slot(
cancel_->signal.slot(),
[this, h, ex = env->executor](
Expand Down
7 changes: 5 additions & 2 deletions example/asio/api/uni_stream.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

#include "capy_streams.hpp"

#include <boost/capy/buffers/asio.hpp>
#include <boost/capy/io/any_stream.hpp>
#include <boost/capy/ex/run_async.hpp>
#include <boost/capy/task.hpp>
Expand Down Expand Up @@ -121,7 +122,8 @@ class uni_stream
MutableBufferSequence bufs,
ReadHandler h) -> capy::task<>
{
auto [ec, n] = co_await stream->read_some(bufs);
auto [ec, n] = co_await stream->read_some(
capy::from_asio(bufs));
std::move(h)(ec, n);
}(&self_->stream_, buffers, std::forward<ReadHandler>(handler)));
}
Expand Down Expand Up @@ -152,7 +154,8 @@ class uni_stream
ConstBufferSequence bufs,
WriteHandler h) -> capy::task<>
{
auto [ec, n] = co_await stream->write_some(bufs);
auto [ec, n] = co_await stream->write_some(
capy::from_asio(bufs));
std::move(h)(ec, n);
}(&self_->stream_, buffers, std::forward<WriteHandler>(handler)));
}
Expand Down
75 changes: 12 additions & 63 deletions include/boost/capy/buffers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,38 +32,6 @@ namespace capy {
class const_buffer;
class mutable_buffer;

namespace detail {

// satisfies Asio's buffer constructors, CANNOT be removed!
template<class T, std::size_t Extent = (std::size_t)(-1)>
class basic_buffer
{
constexpr auto data() const noexcept ->
std::conditional_t<std::is_const_v<T>, void const*, void*>
{
return p_;
}

constexpr std::size_t size() const noexcept
{
return n_;
}

friend class capy::const_buffer;
friend class capy::mutable_buffer;
friend class asio::const_buffer;
friend class asio::mutable_buffer;
basic_buffer() = default;
constexpr basic_buffer(T* p, std::size_t n) noexcept : p_(p), n_(n) {}
constexpr basic_buffer<T, (std::size_t)(-1)> subspan(
std::size_t, std::size_t = (std::size_t)(-1)) const noexcept;

T* p_ = nullptr;
std::size_t n_ = 0;
};

} // detail

//------------------------------------------------

/// Tag type for customizing `buffer_size` via `tag_invoke`.
Expand Down Expand Up @@ -98,8 +66,10 @@ enum class slice_how
@see const_buffer, MutableBufferSequence
*/
class mutable_buffer
: public detail::basic_buffer<unsigned char>
{
unsigned char* p_ = nullptr;
std::size_t n_ = 0;

public:
/// Construct an empty buffer.
mutable_buffer() = default;
Expand All @@ -115,19 +85,8 @@ class mutable_buffer
/// Construct from pointer and size.
constexpr mutable_buffer(
void* data, std::size_t size) noexcept
: basic_buffer<unsigned char>(
static_cast<unsigned char*>(data), size)
{
}

/// Construct from Asio mutable_buffer.
template<class MutableBuffer>
requires std::same_as<MutableBuffer, asio::mutable_buffer>
constexpr mutable_buffer(
MutableBuffer const& b) noexcept
: basic_buffer<unsigned char>(
static_cast<unsigned char*>(
b.data()), b.size())
: p_(static_cast<unsigned char*>(data))
, n_(size)
{
}

Expand Down Expand Up @@ -199,8 +158,10 @@ class mutable_buffer
@see mutable_buffer, ConstBufferSequence
*/
class const_buffer
: public detail::basic_buffer<unsigned char const>
{
unsigned char const* p_ = nullptr;
std::size_t n_ = 0;

public:
/// Construct an empty buffer.
const_buffer() = default;
Expand All @@ -215,28 +176,16 @@ class const_buffer
/// Construct from pointer and size.
constexpr const_buffer(
void const* data, std::size_t size) noexcept
: basic_buffer<unsigned char const>(
static_cast<unsigned char const*>(data), size)
: p_(static_cast<unsigned char const*>(data))
, n_(size)
{
}

/// Construct from mutable_buffer.
constexpr const_buffer(
mutable_buffer const& b) noexcept
: basic_buffer<unsigned char const>(
static_cast<unsigned char const*>(b.data()), b.size())
{
}

/// Construct from Asio buffer types.
template<class ConstBuffer>
requires (std::same_as<ConstBuffer, asio::const_buffer> ||
std::same_as<ConstBuffer, asio::mutable_buffer>)
constexpr const_buffer(
ConstBuffer const& b) noexcept
: basic_buffer<unsigned char const>(
static_cast<unsigned char const*>(
b.data()), b.size())
: p_(static_cast<unsigned char const*>(b.data()))
, n_(b.size())
{
}

Expand Down
Loading
Loading