diff --git a/doc/modules/ROOT/pages/4.guide/4g.composed-operations.adoc b/doc/modules/ROOT/pages/4.guide/4g.composed-operations.adoc index 41de82220..161d1e247 100644 --- a/doc/modules/ROOT/pages/4.guide/4g.composed-operations.adoc +++ b/doc/modules/ROOT/pages/4.guide/4g.composed-operations.adoc @@ -17,8 +17,9 @@ and `write_some()` functions to provide higher-level guarantees. Code snippets assume: [source,cpp] ---- -#include -#include +#include +#include +#include #include namespace corosio = boost::corosio; @@ -42,19 +43,19 @@ auto [ec, n] = co_await s.read_some( For many use cases, you need to transfer a specific amount of data. Composed operations provide these guarantees. -== corosio::read() +== capy::read() The `read()` function reads until the buffer is full or an error occurs: [source,cpp] ---- char buf[1024]; -auto [ec, n] = co_await corosio::read( +auto [ec, n] = co_await capy::read( stream, capy::mutable_buffer(buf, sizeof(buf))); // Either: // - n == 1024 and ec is default (success) -// - ec is error::eof and n < 1024 (reached end of stream) +// - ec == capy::cond::eof and n < 1024 (reached end of stream) // - ec is some other error ---- @@ -62,68 +63,62 @@ auto [ec, n] = co_await corosio::read( [source,cpp] ---- -template -capy::task> -read(io_stream& ios, MutableBufferSequence const& buffers); +auto +read( + ReadStream auto& stream, + MutableBufferSequence auto const& buffers) -> + capy::io_task; ---- === Behavior 1. Calls `read_some()` repeatedly until all buffers are filled -2. If `read_some()` returns 0 bytes, returns `capy::error::eof` -3. If an error occurs, returns immediately with bytes read so far -4. On success, returns total bytes (equals `buffer_size(buffers)`) +2. If `read_some()` returns an error (including `cond::eof`), returns + immediately with bytes read so far +3. On success, returns total bytes (equals `buffer_size(buffers)`) -== corosio::read() into std::string +== capy::read() into a dynamic buffer -A special overload reads until EOF, growing the string as needed: +A second overload reads until EOF, growing the buffer as needed. Use +`capy::string_dynamic_buffer` to wrap a `std::string`: [source,cpp] ---- std::string content; -auto [ec, n] = co_await corosio::read(stream, content); +auto [ec, n] = co_await capy::read( + stream, capy::string_dynamic_buffer(&content)); -// Either: -// - ec == capy::error::eof (normal termination) -// - ec is some error -// content contains all data read +// On success (EOF reached): ec is default, n is total bytes read +// On error: ec is the error, n is bytes read before the error ---- === Signature [source,cpp] ---- -capy::task> -read(io_stream& ios, std::string& s); +auto +read( + ReadStream auto& stream, + DynamicBufferParam auto&& buffers, + std::size_t initial_amount = 2048) -> + capy::io_task; ---- === Behavior -1. Preserves existing string content -2. Grows string as needed (starts with 2048 bytes, grows 1.5x) -3. Reads until EOF or error -4. Resizes string to actual data size before returning -5. Returns `n` = new bytes read (not including original content) - -=== Growth Strategy - -The function uses an efficient growth strategy: - -* Initial capacity: existing size + 2048 -* Growth factor: 1.5x when buffer fills -* Maximum: `string::max_size()` - -If the string reaches `max_size()` with more data available, returns -`errc::value_too_large`. +1. Prepares `initial_amount` bytes via `buffers.prepare()`, then reads +2. Grows 1.5x when the prepared buffer fills completely +3. On `cond::eof`: returns success with total bytes read +4. On any other error: returns immediately with bytes read so far -== corosio::write() +== capy::write() The `write()` function writes all data or fails: [source,cpp] ---- std::string msg = "Hello, World!"; -auto [ec, n] = co_await corosio::write( +auto [ec, n] = co_await capy::write( stream, capy::const_buffer(msg.data(), msg.size())); // Either: @@ -135,17 +130,18 @@ auto [ec, n] = co_await corosio::write( [source,cpp] ---- -template -capy::task> -write(io_stream& ios, ConstBufferSequence const& buffers); +auto +write( + WriteStream auto& stream, + ConstBufferSequence auto const& buffers) -> + capy::io_task; ---- === Behavior 1. Calls `write_some()` repeatedly until all buffers are written -2. If `write_some()` returns 0 bytes, returns `errc::broken_pipe` -3. If an error occurs, returns immediately with bytes written so far -4. On success, returns total bytes (equals `buffer_size(buffers)`) +2. If an error occurs, returns immediately with bytes written so far +3. On success, returns total bytes (equals `buffer_size(buffers)`) == consuming_buffers Helper @@ -193,10 +189,10 @@ The iterator returns adjusted buffers accounting for consumed bytes. [source,cpp] ---- -auto [ec, n] = co_await corosio::read(stream, buf); +auto [ec, n] = co_await capy::read(stream, buf); if (ec) { - if (ec == capy::error::eof) + if (ec == capy::cond::eof) std::cout << "End of stream, read " << n << " bytes\n"; else std::cerr << "Error: " << ec.message() << "\n"; @@ -208,23 +204,25 @@ if (ec) [source,cpp] ---- // For write (EOF doesn't apply) -auto n = (co_await corosio::write(stream, buf)).value(); +auto [wec, n] = co_await capy::write(stream, buf); +if (wec) + capy::detail::throw_system_error(wec); // For read (need to handle EOF) -auto [ec, n] = co_await corosio::read(stream, buf); -if (ec && ec != capy::error::eof) - throw boost::system::system_error(ec); +auto [rec, rn] = co_await capy::read(stream, buf); +if (rec && rec != capy::cond::eof) + capy::detail::throw_system_error(rec); ---- == Cancellation Composed operations support cancellation through the affine protocol. When -cancelled, they return with `operation_canceled` and the partial byte count. +cancelled, they return with `cond::canceled` and the partial byte count. [source,cpp] ---- -auto [ec, n] = co_await corosio::read(stream, large_buffer); -if (ec == make_error_code(system::errc::operation_canceled)) +auto [ec, n] = co_await capy::read(stream, large_buffer); +if (ec == capy::cond::canceled) std::cout << "Cancelled after reading " << n << " bytes\n"; ---- @@ -238,11 +236,11 @@ For optimal performance with multiple buffers: ---- // Efficient: single system call per read_some() std::array bufs = {...}; -co_await corosio::read(stream, bufs); +co_await capy::read(stream, bufs); // Less efficient: may require more system calls -co_await corosio::read(stream, buf1); -co_await corosio::read(stream, buf2); +co_await capy::read(stream, buf1); +co_await capy::read(stream, buf2); ---- === Buffer Sizing @@ -252,12 +250,12 @@ Choose buffer sizes that match your expected data: * Too small: More system calls * Too large: Memory waste -For unknown-length data (like HTTP responses), use the string overload: +For unknown-length data (like HTTP responses), use the dynamic buffer overload: [source,cpp] ---- std::string response; -co_await corosio::read(stream, response); // Grows as needed +co_await capy::read(stream, capy::string_dynamic_buffer(&response)); ---- == Example: HTTP Response Reading @@ -267,11 +265,11 @@ co_await corosio::read(stream, response); // Grows as needed capy::task read_http_response(corosio::io_stream& stream) { std::string response; - auto [ec, n] = co_await corosio::read(stream, response); + auto [ec, n] = co_await capy::read( + stream, capy::string_dynamic_buffer(&response)); - // EOF is expected when server closes connection - if (ec && ec != capy::error::eof) - throw boost::system::system_error(ec); + if (ec) + capy::detail::throw_system_error(ec); co_return response; }