Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
3f86674
test(coverage): exercise deferred completions when the inline budget …
sgerbino Sep 3, 2026
283e06e
test(coverage): claim parked ops on cancel, close, and release
sgerbino Sep 3, 2026
366eee4
test(coverage): complete file and resolver ops inline when the pool r…
sgerbino Sep 3, 2026
25f2fb4
test(tls): reject bad credentials, ciphers, and CRLs at handshake
sgerbino Sep 3, 2026
343475a
test(signals): cover multi-registration removal and queued delivery
sgerbino Sep 3, 2026
a8aefac
test(ipv6): cover tail-validation rejection arms and to_buffer bounds
sgerbino Sep 3, 2026
0468d63
test(sockets): cover datagram edge ops and assign validation
sgerbino Sep 3, 2026
b57b750
test(server): hand a dropped launcher's worker to a waiting accept
sgerbino Sep 3, 2026
5ebb985
test(native): observe stop requests at resume time
sgerbino Sep 3, 2026
fa76f92
test(coverage): bounded run variants on reactor schedulers
sgerbino Sep 3, 2026
b8b85fb
test(io_uring): drain in-flight and reaped ops at context teardown
sgerbino Sep 3, 2026
4eb7d75
fix(io_uring): absorb SIGPIPE when teardown flushes a broken pipe write
sgerbino Sep 3, 2026
f5d1fac
refactor(reactor): drop unreachable descriptor, datagram, and queue p…
sgerbino Sep 4, 2026
71d7dfd
test(tls): drive the driver's flush latch, alert flush, and buffer edges
sgerbino Sep 3, 2026
5c4cd09
test(reactor): batch-dispatch a descriptor event and contend bounded …
sgerbino Sep 3, 2026
ab7461c
test(iocp): cancel, tear down, and validate the paths only Windows runs
sgerbino Sep 3, 2026
9d00860
test(fault): add an allocation-failure entry and drive the bad_alloc …
sgerbino Sep 3, 2026
5c968b8
test(fault): add the pthread_create entry and prove pool spawn refusal
sgerbino Sep 3, 2026
2ac31bc
test(fault): add OpenSSL entry points and drive the engine refusal arms
sgerbino Sep 4, 2026
1931732
test(fault): reach thread creation on Windows toolchains
sgerbino Sep 4, 2026
361f950
test(fault): add an on-demand submission-queue fill entry
sgerbino Sep 4, 2026
e56e7dd
test(tls): report truncation when the peer dies before close_notify
sgerbino Sep 4, 2026
1d19e1e
chore(coverage): annotate verified-unreachable arms for the report
sgerbino Sep 4, 2026
d7d058d
test(fault): reach the OpenSSL engine on Windows via IAT patching
sgerbino Sep 4, 2026
ed1bfdf
test(fault): reach the OpenSSL engine on macOS via satellite rebinding
sgerbino Sep 4, 2026
7497387
refactor(reactor): pass the always-live reactor context by reference
sgerbino Sep 4, 2026
19b9d12
fix(test): keep the acceptor re-bind off the just-released port
sgerbino Sep 4, 2026
4ed4088
test(coverage): exercise leave_group_v6 static traits directly
sgerbino Sep 5, 2026
94e5904
test(coverage): drive the file and accept-socket resume-time cancel arms
sgerbino Sep 5, 2026
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
4 changes: 2 additions & 2 deletions include/boost/corosio/native/detail/epoll/epoll_scheduler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ class BOOST_COROSIO_DECL epoll_scheduler final : public reactor_scheduler

private:
void
run_task(lock_type& lock, context_type* ctx,
run_task(lock_type& lock, context_type& ctx,
long timeout_us) override;
void interrupt_reactor() const override;
void update_timerfd() const;
Expand Down Expand Up @@ -347,7 +347,7 @@ epoll_scheduler::update_timerfd() const

inline void
epoll_scheduler::run_task(
lock_type& lock, context_type* ctx, long timeout_us)
lock_type& lock, context_type& ctx, long timeout_us)
{
int timeout_ms;
if (task_interrupted_)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ struct uring_multi_accept_op : io_uring_op
static void do_retired_cqe(
io_uring_op* /*base*/, int res, unsigned /*flags*/) noexcept
{
if (res >= 0)
::close(res);
if (res >= 0) // LCOV_EXCL_LINE adopt-over-armed race leak guard
::close(res); // LCOV_EXCL_LINE adopt-over-armed race leak guard
}

static void do_cqe(io_uring_op* base, int res, unsigned flags,
Expand All @@ -105,15 +105,15 @@ struct uring_multi_accept_op : io_uring_op
// whether to surface the fd via a waiter or park it.
}

/// Never invoked: the multishot op is owned by the acceptor and
/// never queued for handler dispatch. Provided so the vtable is
/// complete.
// LCOV_EXCL_START: never invoked; the multishot op is owned by
// the acceptor and never queued for handler dispatch. Provided so
// the vtable is complete.
static void do_handler(
void* /*owner*/, scheduler_op* /*base*/,
std::uint32_t /*bytes*/, std::uint32_t /*error*/) noexcept
{
// No-op. The acceptor's per-accept callback handles everything.
}
// LCOV_EXCL_STOP
};

/** Synthesized accept op — manufactured by the acceptor for parked fds.
Expand Down Expand Up @@ -153,11 +153,13 @@ struct uring_accept_op : io_uring_op
: io_uring_op(&do_handler, &do_cqe)
{}

// LCOV_EXCL_START: never receives a CQE; present for vtable
// completeness.
static void do_cqe(io_uring_op*, int, unsigned,
ready_queue&) noexcept
{
// Unreachable: this op never receives a CQE.
}
// LCOV_EXCL_STOP

static void do_handler(
void* owner, scheduler_op* base,
Expand Down Expand Up @@ -193,9 +195,12 @@ struct uring_accept_op : io_uring_op
self->peer_service, self->accepted_fd,
self->peer_storage, self->peer_len);

// LCOV_EXCL_START: no public accept overload reports the peer
// endpoint on this backend yet.
if (self->peer_endpoint_out)
*self->peer_endpoint_out =
sockaddr_to_endpoint(self->peer_storage);
// LCOV_EXCL_STOP

if (self->ec_out)
*self->ec_out = {};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,10 @@ struct uring_random_access_read_op : uring_file_read_op_base

Stream files pass `offset == -1` (kernel f_pos); random-access
files pass an explicit caller-supplied offset. Unlike socket
writes, no `MSG_NOSIGNAL` is needed — files don't generate
SIGPIPE on closed peers.
writes there is no `MSG_NOSIGNAL` equivalent: a write to a pipe
or FIFO whose reader has closed raises SIGPIPE when the kernel
executes it, so the teardown paths that flush queued writes hold
the signal blocked (see `scoped_sigpipe_block`).
*/
/// Shared state and submission logic for file write ops. Concrete
/// subclasses pick a `do_handler` matching their storage model.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,10 @@ class BOOST_COROSIO_DECL io_uring_random_access_file final
{
if (fd_ >= 0)
{
// The kernel may run a queued pipe write as task work at
// either kernel entry below; with the reader already gone
// that raises SIGPIPE.
scoped_sigpipe_block no_sigpipe;
sched_->cancel_and_flush(fd_);
::close(fd_);
fd_ = -1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,63 @@

#include <errno.h>
#include <poll.h>
#include <pthread.h>
#include <signal.h>
#include <sys/eventfd.h>
#include <time.h>
#include <unistd.h>

namespace boost::corosio::detail {

/** Block SIGPIPE on the calling thread for the current scope.

A queued pipe write can execute inline while this thread is in
the kernel submitting SQEs; if the pipe's reader is already gone
the kernel raises a thread-directed SIGPIPE, which kills any
process that has not ignored the signal. The write's CQE still
reports `EPIPE`, so the signal carries no information the
completion path does not already deliver. The destructor consumes
any SIGPIPE raised while blocked and restores the caller's mask.
*/
class scoped_sigpipe_block
{
sigset_t old_{};
bool restore_ = false;

public:
/// Consume any SIGPIPE raised in scope and restore the mask.
~scoped_sigpipe_block()
{
if (!restore_)
return;
if (!sigismember(&old_, SIGPIPE))
{
// Only consume what this scope could have generated; a
// caller who blocked SIGPIPE keeps their pending state.
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGPIPE);
timespec zero{};
while (::sigtimedwait(&set, nullptr, &zero) == SIGPIPE)
{
}
}
::pthread_sigmask(SIG_SETMASK, &old_, nullptr);
}

/// Construct and block SIGPIPE for the calling thread.
scoped_sigpipe_block() noexcept
{
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGPIPE);
restore_ = ::pthread_sigmask(SIG_BLOCK, &set, &old_) == 0;
}

scoped_sigpipe_block(scoped_sigpipe_block const&) = delete;
scoped_sigpipe_block& operator=(scoped_sigpipe_block const&) = delete;
};

// Forward-declared so the out-of-line inline definitions below the class
// can reference the frame stack without a circular dependency.
struct io_uring_scheduler_frame;
Expand Down Expand Up @@ -556,6 +608,9 @@ io_uring_scheduler::~io_uring_scheduler()
{
if (ring_inited_)
{
// Ring teardown can still run a doomed pipe write as task
// work; absorb the SIGPIPE it would raise.
scoped_sigpipe_block no_sigpipe;
if (wakeup_eventfd_ >= 0)
::close(wakeup_eventfd_);
::io_uring_queue_exit(&ring_);
Expand Down Expand Up @@ -1493,6 +1548,12 @@ io_uring_op::on_cancel() noexcept
inline void
io_uring_scheduler::cancel_and_flush(int fd) noexcept
{
// The flush can execute a queued write on `fd` inline; when the
// fd is a pipe whose reader has already closed — service
// shutdown closes impls one at a time, so teardown itself
// creates that state — the kernel raises SIGPIPE.
scoped_sigpipe_block no_sigpipe;

lazy_init_ring();
interrupt_reactor();
lock_type lock(ring_mutex_);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,10 @@ class BOOST_COROSIO_DECL io_uring_stream_file final
{
if (fd_ >= 0)
{
// The kernel may run a queued pipe write as task work at
// either kernel entry below; with the reader already gone
// that raises SIGPIPE.
scoped_sigpipe_block no_sigpipe;
sched_->cancel_and_flush(fd_);
::close(fd_);
fd_ = -1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2594,6 +2594,9 @@ class BOOST_COROSIO_DECL io_uring_local_datagram_socket final
return remote_endpoint_;
}

// LCOV_EXCL_START: the public bind routes through the
// service's bind_socket; nothing calls the implementation
// interface's bind on this backend.
std::error_code bind(corosio::local_endpoint ep) noexcept override
{
sockaddr_storage addr{};
Expand All @@ -2609,6 +2612,7 @@ class BOOST_COROSIO_DECL io_uring_local_datagram_socket final
local_endpoint_ = sockaddr_to_local_endpoint(local, local_len);
return {};
}
// LCOV_EXCL_STOP

private:
std::coroutine_handle<> submit_send(
Expand Down
13 changes: 9 additions & 4 deletions include/boost/corosio/native/detail/iocp/win_scheduler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,10 @@ class BOOST_COROSIO_DECL win_scheduler final
/// Return true when scheduler locking is disabled (fully-lockless tier).
bool scheduler_locking_disabled() const noexcept override
{
// LCOV_EXCL_START: consulted only by the POSIX pool-backed
// services; the IOCP services do not read it yet.
return scheduler_locking_disabled_;
// LCOV_EXCL_STOP
}

/** Signal that an overlapped I/O operation is now pending.
Expand Down Expand Up @@ -618,8 +621,8 @@ win_scheduler::do_one(unsigned long timeout_ms)
return 1;
}

default:
continue;
default: // LCOV_EXCL_LINE unreachable: closed key set
continue; // LCOV_EXCL_LINE unreachable: closed key set
}
}

Expand All @@ -646,8 +649,10 @@ win_scheduler::do_one(unsigned long timeout_ms)
}
continue;

default:
continue;
// A key outside the closed set reaches here only if a
// third party posts to the port.
default: // LCOV_EXCL_LINE unreachable: closed key set
continue; // LCOV_EXCL_LINE unreachable: closed key set
}
}

Expand Down
4 changes: 2 additions & 2 deletions include/boost/corosio/native/detail/iocp/win_signals.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -704,8 +704,8 @@ win_signals::start_wait(win_signal& impl, signal_op* op)
inline void
win_signals::deliver_signal(int signal_number)
{
if (signal_number < 0 || signal_number >= max_signal_number)
return;
if (signal_number < 0 || signal_number >= max_signal_number) // LCOV_EXCL_LINE OS never delivers out-of-range
return; // LCOV_EXCL_LINE OS never delivers out-of-range

signal_detail::signal_state* state = signal_detail::get_signal_state();
std::lock_guard<std::mutex> lock(state->mutex);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ class BOOST_COROSIO_DECL kqueue_scheduler final : public reactor_scheduler

private:
void
run_task(lock_type& lock, context_type* ctx,
run_task(lock_type& lock, context_type& ctx,
long timeout_us) override;
void interrupt_reactor() const override;
long calculate_timeout(long requested_timeout_us) const;
Expand Down Expand Up @@ -335,7 +335,7 @@ kqueue_scheduler::calculate_timeout(long requested_timeout_us) const

inline void
kqueue_scheduler::run_task(
lock_type& lock, context_type* ctx, long timeout_us)
lock_type& lock, context_type& ctx, long timeout_us)
{
long effective_timeout_us =
task_interrupted_ ? 0 : calculate_timeout(timeout_us);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,16 +117,14 @@ class reactor_basic_socket

/** Register an op with the reactor.

Handles cached edge events and deferred cancellation.
Called on the EAGAIN/EINPROGRESS path when speculative
I/O failed.
Handles cached edge events. Called on the EAGAIN/EINPROGRESS
path when speculative I/O failed.
*/
template<class Op>
void register_op(
Op& op,
reactor_op_base*& desc_slot,
bool& ready_flag,
bool& cancel_flag,
bool is_write_direction = false) noexcept;

/** Cancel a single pending operation.
Expand All @@ -135,7 +133,6 @@ class reactor_basic_socket
the mutex and posts it to the scheduler as cancelled.
Derived must implement:
op_to_desc_slot(Op&) -> reactor_op_base**
op_to_cancel_flag(Op&) -> bool*
*/
template<class Op>
void cancel_single_op(Op& op) noexcept;
Expand Down Expand Up @@ -175,7 +172,6 @@ reactor_basic_socket<Derived, ImplBase, Service, DescState, Endpoint>::register_
Op& op,
reactor_op_base*& desc_slot,
bool& ready_flag,
bool& cancel_flag,
bool is_write_direction) noexcept
{
svc_.work_started();
Expand All @@ -191,11 +187,6 @@ reactor_basic_socket<Derived, ImplBase, Service, DescState, Endpoint>::register_
op.errn = 0;
}

if (cancel_flag)
{
cancel_flag = false;
op.cancelled.store(true, std::memory_order_relaxed);
}

if (io_done || op.cancelled.load(std::memory_order_acquire))
{
Expand Down Expand Up @@ -332,12 +323,6 @@ reactor_basic_socket<Derived, ImplBase, Service, DescState, Endpoint>::
});
desc_state_.read_ready = false;
desc_state_.write_ready = false;
desc_state_.read_cancel_pending = false;
desc_state_.write_cancel_pending = false;
desc_state_.connect_cancel_pending = false;
desc_state_.wait_read_cancel_pending = false;
desc_state_.wait_write_cancel_pending = false;
desc_state_.wait_error_cancel_pending = false;

if (desc_state_.is_enqueued_.load(std::memory_order_acquire))
desc_state_.impl_ref_ = self;
Expand Down Expand Up @@ -398,12 +383,6 @@ reactor_basic_socket<Derived, ImplBase, Service, DescState, Endpoint>::
});
desc_state_.read_ready = false;
desc_state_.write_ready = false;
desc_state_.read_cancel_pending = false;
desc_state_.write_cancel_pending = false;
desc_state_.connect_cancel_pending = false;
desc_state_.wait_read_cancel_pending = false;
desc_state_.wait_write_cancel_pending = false;
desc_state_.wait_error_cancel_pending = false;

if (desc_state_.is_enqueued_.load(std::memory_order_acquire))
desc_state_.impl_ref_ = self;
Expand Down
Loading
Loading