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
18 changes: 10 additions & 8 deletions include/boost/capy/io/any_write_stream.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -400,23 +400,25 @@ any_write_stream::write_some(CB buffers)
}

bool
await_ready() const noexcept
await_ready()
{
return ba_.to_span().empty();
}
// An empty write never touches the underlying stream
if(ba_.to_span().empty())
return true;

std::coroutine_handle<>
await_suspend(std::coroutine_handle<> h, io_env const* env)
{
self_->vt_->construct_awaitable(
self_->stream_,
self_->cached_awaitable_,
ba_.to_span());
self_->awaitable_active_ = true;

if(self_->vt_->await_ready(self_->cached_awaitable_))
return h;
return self_->vt_->await_ready(
self_->cached_awaitable_);
}

std::coroutine_handle<>
await_suspend(std::coroutine_handle<> h, io_env const* env)
{
return self_->vt_->await_suspend(
self_->cached_awaitable_, h, env);
}
Expand Down
43 changes: 43 additions & 0 deletions test/unit/io/any_read_stream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,29 @@ struct pending_read_stream
{ return pending_read_awaitable{counter_}; }
};

// Reports readiness without consulting the io_env, like a stream
// whose data is already in user-space memory.
struct ready_read_awaitable
{
int* suspended_;
bool await_ready() const noexcept { return true; }
std::coroutine_handle<> await_suspend(std::coroutine_handle<>, io_env const*)
{
++(*suspended_);
return std::noop_coroutine();
}
io_result<std::size_t> await_resume()
{ return {std::error_code(), 7}; }
};

struct ready_read_stream
{
int* suspended_;
ready_read_awaitable read_some(
MutableBufferSequence auto)
{ return ready_read_awaitable{suspended_}; }
};

// Reports not-ready, then resumes the awaiting coroutine from
// await_suspend. This exercises the type-erased await_suspend
// thunk, which the always-ready test mocks never reach.
Expand Down Expand Up @@ -497,6 +520,25 @@ class any_read_stream_test
BOOST_TEST(r.success);
}

void
testReadyStreamSkipsSuspension()
{
// A concrete awaitable that reports readiness completes the
// erased read without suspending the coroutine: the wrapper
// forwards await_ready and never calls await_suspend.
int suspended = 0;
ready_read_stream rs{&suspended};
any_read_stream ars(&rs);

char buf[8];
auto aw = ars.read_some(mutable_buffer(buf, sizeof(buf)));
BOOST_TEST(aw.await_ready());
auto [ec, n] = aw.await_resume();
BOOST_TEST(!ec);
BOOST_TEST_EQ(n, 7u);
BOOST_TEST_EQ(suspended, 0);
}

void
testDestroyWithActiveAwaitable()
{
Expand Down Expand Up @@ -598,6 +640,7 @@ class any_read_stream_test
testTrichotomyEofAfterDrain();
testTrichotomyEmptyBufferExhausted();
testReadSomeManyBuffers();
testReadyStreamSkipsSuspension();
testDestroyWithActiveAwaitable();
testMoveAssignWithActiveAwaitable();
testMoveAssignOwning();
Expand Down
47 changes: 45 additions & 2 deletions test/unit/io/any_write_stream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,29 @@ struct pending_write_stream
{ return pending_write_awaitable{counter_}; }
};

// Reports readiness without consulting the io_env, like a stream
// whose data is already in user-space memory.
struct ready_write_awaitable
{
int* suspended_;
bool await_ready() const noexcept { return true; }
std::coroutine_handle<> await_suspend(std::coroutine_handle<>, io_env const*)
{
++(*suspended_);
return std::noop_coroutine();
}
io_result<std::size_t> await_resume()
{ return {std::error_code(), 7}; }
};

struct ready_write_stream
{
int* suspended_;
ready_write_awaitable write_some(
ConstBufferSequence auto)
{ return ready_write_awaitable{suspended_}; }
};

// Move constructor throws so owning construction fails after storage
// is allocated but before the stream is constructed.
struct throwing_move_write_stream
Expand Down Expand Up @@ -437,11 +460,30 @@ class any_write_stream_test
BOOST_TEST(r.success);
}

void
testReadyStreamSkipsSuspension()
{
// A concrete awaitable that reports readiness completes the
// erased write without suspending the coroutine: the wrapper
// forwards await_ready and never calls await_suspend.
int suspended = 0;
ready_write_stream rs{&suspended};
any_write_stream aws(&rs);

char const data[] = "x";
auto aw = aws.write_some(const_buffer(data, 1));
BOOST_TEST(aw.await_ready());
auto [ec, n] = aw.await_resume();
BOOST_TEST(!ec);
BOOST_TEST_EQ(n, 7u);
BOOST_TEST_EQ(suspended, 0);
}

void
testDestroyWithActiveAwaitable()
{
// Flat vtable, construct-in-await_suspend variant:
// await_suspend constructs the inner awaitable.
// await_ready constructs the inner awaitable; a pending
// stream then suspends through await_suspend.
int destroyed = 0;
pending_write_stream ps{&destroyed};
{
Expand Down Expand Up @@ -496,6 +538,7 @@ class any_write_stream_test
testWriteSomeManyBuffers();
testTrichotomySuccess();
testTrichotomyError();
testReadyStreamSkipsSuspension();
testDestroyWithActiveAwaitable();
testMoveAssignWithActiveAwaitable();
testMoveAssignOwning();
Expand Down
Loading