Skip to content
Open
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
10 changes: 10 additions & 0 deletions CMakePresets.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"version": 6,
"cmakeMinimumRequired": {
"major": 3,
"minor": 25,
"patch": 0
},
"configurePresets": [],
"buildPresets": []
}
2 changes: 1 addition & 1 deletion include/boost/corosio/acceptor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include <concepts>
#include <coroutine>
#include <cstddef>
#include <memory>
#include <stop_token>
#include <type_traits>

Expand Down Expand Up @@ -275,7 +276,6 @@ class BOOST_COROSIO_DECL acceptor : public io_object
};

private:

inline acceptor_impl& get() const noexcept
{
return *static_cast<acceptor_impl*>(impl_);
Expand Down
13 changes: 13 additions & 0 deletions include/boost/corosio/io_stream.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,19 @@ class BOOST_COROSIO_DECL io_stream : public io_object
std::size_t*) = 0;
};

/** Returns the underlying implementation.
This accessor is provided for testing and advanced use cases
that need direct access to the implementation object.
@return Pointer to the io_stream_impl, or nullptr if not set.
*/
io_stream_impl*
get_impl() const noexcept
{
return static_cast<io_stream_impl*>(impl_);
}

protected:
explicit
io_stream(
Expand Down
11 changes: 4 additions & 7 deletions include/boost/corosio/read.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,24 +76,21 @@ namespace corosio {
error or EOF occurs), whereas `read_some()` may return
after reading any amount of data.
*/
template<capy::MutableBufferSequence MutableBufferSequence>
template<capy::MutableBufferSequence MB>
capy::task<io_result<std::size_t>>
read(io_stream& ios, MutableBufferSequence const& buffers)
read(io_stream& ios, MB const& bs)
{
consuming_buffers<MutableBufferSequence> consuming(buffers);
std::size_t const total_size = capy::buffer_size(buffers);
consuming_buffers<MB> consuming(bs);
std::size_t const total_size = capy::buffer_size(bs);
Comment on lines +79 to +84
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

Update the @param name to match bs.

The parameter was renamed to bs (Line 81), but the doc block still uses buffers, which can desync Doxygen-style docs.

📚 Suggested doc fix
-    `@param` buffers The buffer sequence to read data into.
+    `@param` bs The buffer sequence to read data into.
🤖 Prompt for AI Agents
In `@include/boost/corosio/read.hpp` around lines 79 - 84, The docblock for the
template function read(io_stream& ios, MB const& bs) uses an outdated `@param`
name "buffers"; update the `@param` entry to use "bs" to match the parameter in
the function signature (template<capy::MutableBufferSequence MB>
capy::task<io_result<std::size_t>> read(io_stream& ios, MB const& bs)) so
Doxygen stays in sync.

std::size_t total_read = 0;

while (total_read < total_size)
{
auto [ec, n] = co_await ios.read_some(consuming);

if (ec)
co_return {ec, total_read};

if (n == 0)
co_return {make_error_code(capy::error::eof), total_read};

consuming.consume(n);
total_read += n;
}
Expand Down
1 change: 1 addition & 0 deletions include/boost/corosio/socket.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include <concepts>
#include <coroutine>
#include <cstddef>
#include <memory>
#include <stop_token>
#include <type_traits>

Expand Down
42 changes: 42 additions & 0 deletions include/boost/corosio/test/socket_pair.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//
// Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/cppalliance/corosio
//

#ifndef BOOST_COROSIO_TEST_SOCKET_PAIR_HPP
#define BOOST_COROSIO_TEST_SOCKET_PAIR_HPP

#include <boost/corosio/detail/config.hpp>
#include <boost/corosio/socket.hpp>

#include <utility>

namespace boost {
namespace corosio {

class io_context;

namespace test {

/** Create a connected pair of sockets.
Creates two sockets connected via loopback TCP sockets.
Data written to one socket can be read from the other.
@param ioc The io_context for the sockets.
@return A pair of connected sockets.
*/
BOOST_COROSIO_DECL
std::pair<socket, socket>
make_socket_pair(io_context& ioc);

} // namespace test
} // namespace corosio
} // namespace boost

#endif
15 changes: 1 addition & 14 deletions include/boost/corosio/tls/openssl_stream.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,7 @@ namespace corosio {
class BOOST_COROSIO_DECL openssl_stream : public tls_stream
{
public:
/** Construct an OpenSSL stream with default context.
The underlying stream must remain valid for the lifetime of
this openssl_stream object.
@param stream Reference to the underlying stream to wrap.
*/
explicit
openssl_stream( io_stream& stream );

/** Construct an OpenSSL stream with TLS context.
/** Construct an OpenSSL stream.
The underlying stream must remain valid for the lifetime of
this openssl_stream object. The context's configuration is
Expand All @@ -67,9 +57,6 @@ class BOOST_COROSIO_DECL openssl_stream : public tls_stream
@param ctx The TLS context containing configuration.
*/
openssl_stream( io_stream& stream, tls::context ctx );

private:
void construct( tls::context ctx );
};

} // namespace corosio
Expand Down
15 changes: 1 addition & 14 deletions include/boost/corosio/tls/wolfssl_stream.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,7 @@ class BOOST_COROSIO_DECL
wolfssl_stream : public tls_stream
{
public:
/** Construct a WolfSSL stream with default context.

The underlying stream must remain valid for the lifetime of
this wolfssl_stream object.

@param stream Reference to the underlying stream to wrap.
*/
explicit
wolfssl_stream(io_stream& stream);

/** Construct a WolfSSL stream with TLS context.
/** Construct a WolfSSL stream.

The underlying stream must remain valid for the lifetime of
this wolfssl_stream object. The context's configuration is
Expand All @@ -69,9 +59,6 @@ class BOOST_COROSIO_DECL
@param ctx The TLS context containing configuration.
*/
wolfssl_stream(io_stream& stream, tls::context ctx);

private:
void construct(tls::context ctx);
};

} // namespace corosio
Expand Down
20 changes: 15 additions & 5 deletions src/corosio/src/acceptor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,18 @@ listen(endpoint ep, int backlog)
close();

auto& svc = ctx_->use_service<acceptor_service>();
auto& impl = svc.create_acceptor_impl();
impl_ = &impl;
auto& wrapper = svc.create_acceptor_impl();
impl_ = &wrapper;

system::error_code ec = svc.open_acceptor(impl, ep, backlog);
#if defined(BOOST_COROSIO_BACKEND_IOCP)
system::error_code ec = svc.open_acceptor(
*wrapper.get_internal(), ep, backlog);
#elif defined(BOOST_COROSIO_BACKEND_EPOLL)
system::error_code ec = svc.open_acceptor(wrapper, ep, backlog);
#endif
if (ec)
{
impl.release();
wrapper.release();
impl_ = nullptr;
detail::throw_system_error(ec, "acceptor::listen");
}
Expand All @@ -75,7 +80,8 @@ close()
if (!impl_)
return;

impl_->release();
auto* wrapper = static_cast<acceptor_impl_type*>(impl_);
wrapper->release();
impl_ = nullptr;
}

Expand All @@ -84,7 +90,11 @@ acceptor::
cancel()
{
assert(impl_ != nullptr);
#if defined(BOOST_COROSIO_BACKEND_IOCP)
static_cast<acceptor_impl_type*>(impl_)->get_internal()->cancel();
#elif defined(BOOST_COROSIO_BACKEND_EPOLL)
static_cast<acceptor_impl_type*>(impl_)->cancel();
#endif
}

} // namespace corosio
Expand Down
33 changes: 21 additions & 12 deletions src/corosio/src/detail/epoll/op.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <boost/capy/error.hpp>
#include <boost/system/error_code.hpp>

#include "src/detail/make_err.hpp"
#include "src/detail/scheduler_op.hpp"

#include <unistd.h>
Expand Down Expand Up @@ -62,7 +63,7 @@ struct epoll_op : scheduler_op

int fd = -1;
std::uint32_t events = 0;
int error = 0;
int errn = 0;
std::size_t bytes_transferred = 0;

std::atomic<bool> cancelled{false};
Expand All @@ -77,7 +78,7 @@ struct epoll_op : scheduler_op
{
fd = -1;
events = 0;
error = 0;
errn = 0;
bytes_transferred = 0;
cancelled.store(false, std::memory_order_relaxed);
}
Expand All @@ -89,13 +90,13 @@ struct epoll_op : scheduler_op
if (ec_out)
{
if (cancelled.load(std::memory_order_acquire))
*ec_out = make_error_code(system::errc::operation_canceled);
else if (error != 0)
*ec_out = system::error_code(error, system::system_category());
*ec_out = capy::error::canceled;
else if (errn != 0)
*ec_out = make_err(errn);
else if (is_read_operation() && bytes_transferred == 0)
{
// EOF: 0 bytes transferred with no error indicates end of stream
*ec_out = make_error_code(capy::error::eof);
*ec_out = capy::error::eof;
}
Comment on lines 90 to 100
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:

find . -type f -name "op.hpp" | head -20

Repository: cppalliance/corosio

Length of output: 100


🏁 Script executed:

wc -l src/corosio/src/detail/epoll/op.hpp

Repository: cppalliance/corosio

Length of output: 102


🏁 Script executed:

sed -n '80,110p' src/corosio/src/detail/epoll/op.hpp

Repository: cppalliance/corosio

Length of output: 883


🏁 Script executed:

# Also check the broader context of the perform_io and reset methods
sed -n '1,150p' src/corosio/src/detail/epoll/op.hpp

Repository: cppalliance/corosio

Length of output: 3683


🏁 Script executed:

# Search for IOCP implementation to compare EOF handling
find . -type f -name "*.hpp" -o -name "*.h" | xargs rg -l "iocp|IOCP" | head -10

Repository: cppalliance/corosio

Length of output: 514


🏁 Script executed:

sed -n '1,200p' src/corosio/src/detail/iocp/overlapped_op.hpp

Repository: cppalliance/corosio

Length of output: 4334


🏁 Script executed:

# Search for EOF handling in IOCP
rg -A 5 -B 5 "eof|EOF" src/corosio/src/detail/iocp/overlapped_op.hpp

Repository: cppalliance/corosio

Length of output: 813


🏁 Script executed:

# Search for empty_buffer or zero-length read handling in IOCP
rg -A 5 -B 5 "empty|buffer|bytes_transferred == 0" src/corosio/src/detail/iocp/overlapped_op.hpp

Repository: cppalliance/corosio

Length of output: 1319


🏁 Script executed:

# Find read operation implementations in epoll to see if zero-length reads can happen
find . -path "*epoll*" -name "*.hpp" -o -name "*.cpp" | xargs rg -l "is_read_operation|perform_io" | head -10

Repository: cppalliance/corosio

Length of output: 145


🏁 Script executed:

# Check the epoll scheduler to see if empty-buffer reads are issued
sed -n '1,100p' src/corosio/src/detail/epoll/scheduler.cpp

Repository: cppalliance/corosio

Length of output: 2379


🏁 Script executed:

# Search for socket read operations in epoll
rg -A 10 "perform_io" src/corosio/src/detail/epoll/scheduler.cpp | head -50

Repository: cppalliance/corosio

Length of output: 313


🏁 Script executed:

# Find all files in epoll directory to understand read operation implementation
find . -path "*epoll*" -type f \( -name "*.hpp" -o -name "*.cpp" \)

Repository: cppalliance/corosio

Length of output: 285


🏁 Script executed:

# Check sockets.hpp for read operation implementations
cat -n src/corosio/src/detail/epoll/sockets.hpp | head -200

Repository: cppalliance/corosio

Length of output: 6816


🏁 Script executed:

# Search for readv usage and iovec handling
rg -B 5 -A 10 "readv|iovec_count" src/corosio/src/detail/epoll/sockets.hpp

Repository: cppalliance/corosio

Length of output: 1585


🏁 Script executed:

# Look for the complete read operation class definition
rg -B 10 -A 20 "is_read_operation\|perform_io" src/corosio/src/detail/epoll/sockets.hpp | head -100

Repository: cppalliance/corosio

Length of output: 45


🏁 Script executed:

# Get the complete read operation class definitions for epoll
rg -B 5 -A 30 "struct epoll_read_op|struct epoll_write_op" src/corosio/src/detail/epoll/sockets.hpp

Repository: cppalliance/corosio

Length of output: 45


🏁 Script executed:

# Check how the read_some method completes to understand the flow better
rg -B 10 -A 30 "void read_some" src/corosio/src/detail/epoll/sockets.hpp

Repository: cppalliance/corosio

Length of output: 1014


🏁 Script executed:

# Find where epoll_read_op is defined - search in all files
find . -path "*epoll*" -type f \( -name "*.hpp" -o -name "*.cpp" \) -exec grep -l "struct epoll_read_op\|class epoll_read_op" {} \;

Repository: cppalliance/corosio

Length of output: 100


🏁 Script executed:

# Look for all operation classes across epoll files
rg "struct epoll.*_op" src/corosio/src/detail/epoll/

Repository: cppalliance/corosio

Length of output: 467


🏁 Script executed:

# Get the full sockets.hpp file since it's not huge
wc -l src/corosio/src/detail/epoll/sockets.hpp

Repository: cppalliance/corosio

Length of output: 107


🏁 Script executed:

# Read the entire sockets.hpp file
cat src/corosio/src/detail/epoll/sockets.hpp

Repository: cppalliance/corosio

Length of output: 14926


Add empty_buffer flag to suppress EOF for intentional empty-buffer reads on epoll.

The epoll backend reports EOF (0 bytes transferred with no error) for any zero-byte read. IOCP already suppresses this for intentional empty-buffer reads via an empty_buffer flag. Since read_some can issue zero-length reads (handled at line 322–328 in sockets.hpp), epoll will mis-report EOF and diverge cross-platform. Track empty_buffer in epoll_op to match IOCP semantics:

  • Add bool empty_buffer = false; member to epoll_op
  • Reset it in reset()
  • Set empty_buffer = (iovec_count == 0) in perform_io() for read operations
  • Guard EOF detection: else if (is_read_operation() && bytes_transferred == 0 && !empty_buffer)
🤖 Prompt for AI Agents
In `@src/corosio/src/detail/epoll/op.hpp` around lines 90 - 100, The epoll backend
is treating any 0-byte read as EOF; add an empty_buffer flag to epoll_op and use
it to suppress EOF for intentional zero-length reads: add a member bool
empty_buffer = false to epoll_op, reset it to false in epoll_op::reset(), set
empty_buffer = (iovec_count == 0) inside epoll_op::perform_io() for read
operations, and change the EOF guard in the completion path to check
!empty_buffer (i.e. else if (is_read_operation() && bytes_transferred == 0 &&
!empty_buffer)). Ensure all references are to epoll_op, reset(), perform_io(),
is_read_operation(), and bytes_transferred so behavior matches the IOCP
semantics.

}

Expand Down Expand Up @@ -128,7 +129,7 @@ struct epoll_op : scheduler_op

void complete(int err, std::size_t bytes) noexcept
{
error = err;
errn = err;
bytes_transferred = bytes;
}

Expand Down Expand Up @@ -170,12 +171,20 @@ struct epoll_read_op : epoll_op
iovec iovecs[max_buffers];
int iovec_count = 0;

bool is_read_operation() const noexcept override { return true; }
// True when 0 bytes is due to empty buffer, not EOF
bool empty_buffer_read = false;

// EOF only applies when we actually tried to read something
bool is_read_operation() const noexcept override
{
return !empty_buffer_read;
}

void reset() noexcept
{
epoll_op::reset();
iovec_count = 0;
empty_buffer_read = false;
}

void perform_io() noexcept override
Expand Down Expand Up @@ -259,14 +268,14 @@ struct epoll_accept_op : epoll_op
{
stop_cb.reset();

bool success = (error == 0 && !cancelled.load(std::memory_order_acquire));
bool success = (errn == 0 && !cancelled.load(std::memory_order_acquire));

if (ec_out)
{
if (cancelled.load(std::memory_order_acquire))
*ec_out = make_error_code(system::errc::operation_canceled);
else if (error != 0)
*ec_out = system::error_code(error, system::system_category());
*ec_out = capy::error::canceled;
else if (errn != 0)
*ec_out = make_err(errn);
}

if (success && accepted_fd >= 0 && peer_impl)
Expand Down
Loading
Loading