diff --git a/doc/modules/ROOT/nav.adoc b/doc/modules/ROOT/nav.adoc index 7cd9d363..e6e195b7 100644 --- a/doc/modules/ROOT/nav.adoc +++ b/doc/modules/ROOT/nav.adoc @@ -71,4 +71,7 @@ ** xref:9.design/9m.WhyNotCobalt.adoc[Why Not Cobalt?] ** xref:9.design/9n.WhyNotCobaltConcepts.adoc[Why Not Cobalt Concepts?] ** xref:9.design/9o.WhyNotTMC.adoc[Why Not TooManyCooks?] +* xref:A.specification-methods/A.intro.adoc[Methods of API Description] +** xref:A.specification-methods/Ab.cancellation.adoc[Cancellation] +** xref:A.specification-methods/Ac.contingencies.adoc[Contingencies] * xref:reference:boost/capy.adoc[Reference] diff --git a/doc/modules/ROOT/pages/A.specification-methods/A.intro.adoc b/doc/modules/ROOT/pages/A.specification-methods/A.intro.adoc new file mode 100644 index 00000000..83184b6e --- /dev/null +++ b/doc/modules/ROOT/pages/A.specification-methods/A.intro.adoc @@ -0,0 +1,16 @@ +// +// Copyright (c) 2026 Andrzej Krzemieński (akrzemi1@gmail.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/capy +// + += Methods of API Description + + + +This section describes the conventions used to specify the API of this library in the following +xref:reference:boost/capy.adoc[Reference] section. + diff --git a/doc/modules/ROOT/pages/A.specification-methods/Ab.cancellation.adoc b/doc/modules/ROOT/pages/A.specification-methods/Ab.cancellation.adoc new file mode 100644 index 00000000..145b7b1c --- /dev/null +++ b/doc/modules/ROOT/pages/A.specification-methods/Ab.cancellation.adoc @@ -0,0 +1,20 @@ +// +// Copyright (c) 2026 Andrzej Krzemieński (akrzemi1@gmail.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/capy +// + += Cancellation + +A function is said to _support IoAwaitable cancellation_ when its return type +models concept `IoAwaitable` and this return object `a` controls a coroutine which +can be prematurely stopped using the `std::stop_token` propagated through the +`IoAwaitable` protocol. Additionally, if the result type of expression `co_await a` +in the context of a Capy-coroutine is a specialization of `io_result` +then the cancelling of an operation is +considered a contingency represented by condition `cond::canceled`. + + diff --git a/doc/modules/ROOT/pages/A.specification-methods/Ac.contingencies.adoc b/doc/modules/ROOT/pages/A.specification-methods/Ac.contingencies.adoc new file mode 100644 index 00000000..54ccc9ca --- /dev/null +++ b/doc/modules/ROOT/pages/A.specification-methods/Ac.contingencies.adoc @@ -0,0 +1,44 @@ +// +// Copyright (c) 2026 Andrzej Krzemieński (akrzemi1@gmail.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/capy +// + += Contingencies + +A _contingency_ is any situation occurring during an operation +on a stream, caused by the stream's state, that prevents this operation +from reading or writing the requested number of bytes. + +These situations do not violate the postconditions of the corresponding operations, +as their postconditions never say that the requested number of bytes will indeed be +processed. + +Each stream operation that may encounter a contingency await-returns +a type which is a specialization of `capy::io_result`. These objects can be _destructured_ +using a structured binding. The first binding of such destructuring is of type +`std::error_code`. This binding, call it `ec`, is used to signal if and which +contingency occured: + + * If `ec == std::error_code{}`, no contingency occurred. + * Otherwise a contingency occurred. In order to determine which contingency occurred, + compare `ec` to error conditions, in particular to `capy::cond`. + +NOTE: Reaching the end of stream is also a contingency + (which can be interpreted as preventing an infinite read + from proceeding). + +NOTE: The stream operations can still throw exceptions to indicate conditions + unrelated to stream state that prevent these operations from satisfying + their postconditions, such as failures to grow a buffer, or + failure to allocate a coroutine frame. + +NOTE: Operations on streams often await-return `capy::io_result` + destructuring to `[ec, n]`, where `n` represents the number of processed bytes. + Upon a reported contingency, a non-zero `n` indicates the state of the partial + or sometimes even a full read. When an inner operation reports a contingency, + the outer operation usually processes its partial results before reporting + the contingency itself. \ No newline at end of file diff --git a/doc/mrdocs.yml b/doc/mrdocs.yml index 7a536534..2560be73 100644 --- a/doc/mrdocs.yml +++ b/doc/mrdocs.yml @@ -26,4 +26,7 @@ multipage: true # use-system-libc: true # use-system-stdlib: true +# Automation +auto-function-metadata: false + cmake: '-DCMAKE_CXX_STANDARD=20 -DBOOST_CAPY_MRDOCS_BUILD=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=OFF' diff --git a/include/boost/capy/read.hpp b/include/boost/capy/read.hpp index 95fcdc8b..4a85aa9c 100644 --- a/include/boost/capy/read.hpp +++ b/include/boost/capy/read.hpp @@ -25,52 +25,70 @@ namespace boost { namespace capy { -/** Asynchronously read until the buffer sequence is full. +/** Read data from a stream until the buffer sequence is full. - Reads data from the stream by calling `read_some` repeatedly - until the entire buffer sequence is filled or an error occurs. + @par Await-effects - @li The operation completes when: - @li The buffer sequence is completely filled - @li An error occurs (including `cond::eof`) - @li The operation is cancelled + Reads data from `stream` via awaiting `stream.read_some` repeatedly + until: - @par Cancellation - Supports cancellation via `stop_token` propagated through the - IoAwaitable protocol. When cancelled, returns with `cond::canceled`. + @li either the entire buffer sequence @c buffers is filled, + @li or a contingency occurs. - @param stream The stream to read from. The caller retains ownership. - @param buffers The buffer sequence to fill. The caller retains - ownership and must ensure validity until the operation completes. + If `buffer_size(buffers) == 0` then no awaiting `stream.read_some` + is performed. This is not a contingency. + + @par Await-returns + An object of type `io_result` destructuring as `[ec, n]`. + + Upon a contingency, `n` represents the number of bytes read so far, + inclusive of the last partial read. + + Contingencies: + + @li The first contingency reported from awaiting @c stream.read_some . + + Notable conditions: + + @li @c cond::canceled — Operation was cancelled, + @li @c cond::eof — Stream reached end before `buffers` was filled. + + @par Await-postcondition + `ec || n == buffer_size(buffers)`. + + @param stream The stream to read from. If the lifetime of `stream` ends + before the coroutine finishes, the behavior is undefined. + + @param buffers The buffer sequence to fill. If the lifetime of the buffer + sequence represented by `buffers` ends before the coroutine finishes, the behavior is undefined. + + + @par Remarks + Supports _IoAwaitable cancellation_. - @return An awaitable that await-returns `(error_code, std::size_t)`. - On success, `n` equals `buffer_size(buffers)`. On error, - `n` is the number of bytes read before the error. Compare - error codes to conditions: - @li `cond::eof` - Stream reached end before buffer was filled - @li `cond::canceled` - Operation was cancelled @par Example @code - task<> read_message( ReadStream auto& stream ) + capy::task<> process_message(capy::ReadStream auto& stream) { - char header[16]; - auto [ec, n] = co_await read( stream, mutable_buffer( header ) ); - if( ec == cond::eof ) + std::vector header(16); // known header size for some protocol + auto [ec, n] = co_await capy::read(stream, capy::mutable_buffer(header)); + if (ec == capy::cond::eof) co_return; // Connection closed - if( ec ) - detail::throw_system_error( ec ); - // header contains exactly 16 bytes + if (ec) + throw std::system_error(ec); + + // at this point `header` contains exactly 16 bytes } @endcode - @see read_some, ReadStream, MutableBufferSequence + @see ReadStream, MutableBufferSequence */ +template + requires ReadStream && MutableBufferSequence auto -read( - ReadStream auto& stream, - MutableBufferSequence auto buffers) -> +read(S& stream, MB buffers) -> io_task { auto consuming = buffer_slice(buffers); @@ -89,51 +107,64 @@ read( co_return {{}, total_read}; } -/** Asynchronously read all data from a stream into a dynamic buffer. +/** Read all data from a stream into a dynamic buffer. + + @par Await-effects + + Reads data from `stream` via awaiting `stream.read_some` repeatedly + and appending the results to `dynbuf`, + until a contingency occurs. - Reads data by calling `read_some` repeatedly until EOF is reached - or an error occurs. Data is appended using prepare/commit semantics. + Data is appended using prepare/commit semantics. The buffer grows with 1.5x factor when filled. - @li The operation completes when: - @li End-of-stream is reached (`cond::eof`) - @li An error occurs - @li The operation is cancelled + @par Await-returns - @par Cancellation - Supports cancellation via `stop_token` propagated through the - IoAwaitable protocol. When cancelled, returns with `cond::canceled`. + An object of type `io_result` destructuring as `[ec, n]`. + + `n` represents the total number of bytes read, + inclusive of the last partial read. + + Contingencies: + + @li The first contingency, other than one matching to @c cond::eof, reported from awaiting @c stream.read_some . + + @par Await-throws + `std::bad_alloc` when append to `dynbuf` fails. + + @param stream The stream to read from. If the lifetime of `stream` ends + before the coroutine finishes, the behavior is undefined. + + @param dynbuf The dynamic buffer to append data to. If the lifetime of the buffer + sequence represented by `dynbuf` ends before the coroutine finishes, the behavior is undefined. - @param stream The stream to read from. The caller retains ownership. - @param buffers The dynamic buffer to append data to. Must remain - valid until the operation completes. @param initial_amount Initial bytes to prepare (default 2048). - @return An awaitable that await-returns `(error_code, std::size_t)`. - On success (EOF), `ec` is clear and `n` is total bytes read. - On error, `n` is bytes read before the error. Compare error - codes to conditions: - @li `cond::canceled` - Operation was cancelled + + @par Remarks + Supports _IoAwaitable cancellation_. @par Example @code - task read_body( ReadStream auto& stream ) + capy::task read_body(capy::ReadStream auto& stream) { std::string body; - auto [ec, n] = co_await read( stream, string_dynamic_buffer( &body ) ); - if( ec ) - detail::throw_system_error( ec ); + auto [ec, n] = co_await capy::read(stream, capy::dynamic_buffer(body)); + if (ec) + throw std::system_error(ec); return body; } @endcode @see read_some, ReadStream, DynamicBufferParam */ +template + requires ReadStream && DynamicBufferParam auto read( - ReadStream auto& stream, - DynamicBufferParam auto&& buffers, + S& stream, + DB&& dynbuf, std::size_t initial_amount = 2048) -> io_task { @@ -141,10 +172,10 @@ read( std::size_t total_read = 0; for(;;) { - auto mb = buffers.prepare(amount); + auto mb = dynbuf.prepare(amount); auto const mb_size = buffer_size(mb); auto [ec, n] = co_await stream.read_some(mb); - buffers.commit(n); + dynbuf.commit(n); total_read += n; if(ec == cond::eof) co_return {{}, total_read}; @@ -155,51 +186,66 @@ read( } } -/** Asynchronously read all data from a source into a dynamic buffer. +/** Read all data from a source into a dynamic buffer. - Reads data by calling `source.read` repeatedly until EOF is reached - or an error occurs. Data is appended using prepare/commit semantics. + @par Await-effects + + Reads data from `stream` by calling `source.read` repeatedly + and appending it to `dynbuf` until a contingency occurs. + The last, potenitally partial, read is also appended. + + Data is appended using prepare/commit semantics. The buffer grows with 1.5x factor when filled. - @li The operation completes when: - @li End-of-stream is reached (`cond::eof`) - @li An error occurs - @li The operation is cancelled + @par Await-returns + + An object of type `io_result` destructuring as `[ec, n]`. + + `n` represents the total number of bytes read, + inclusive of the last partial read. + + + Contingencies: + + @li The first contingency, other than one matching to @c cond::eof, reported from awaiting @c stream.read_some . + + @par Await-throws + + `std::bad_alloc` when append to `dynbuf` fails. + + @param source The source to read from. If the lifetime of `source` ends + before the coroutine finishes, the behavior is undefined. - @par Cancellation - Supports cancellation via `stop_token` propagated through the - IoAwaitable protocol. When cancelled, returns with `cond::canceled`. + @param dynbuf The dynamic buffer to append data to. If the lifetime of the + buffer sequence represented by `dynbuf` ends before the coroutine finishes, + the behavior is undefined. - @param source The source to read from. The caller retains ownership. - @param buffers The dynamic buffer to append data to. Must remain - valid until the operation completes. @param initial_amount Initial bytes to prepare (default 2048). - @return An awaitable that await-returns `(error_code, std::size_t)`. - On success (EOF), `ec` is clear and `n` is total bytes read. - On error, `n` is bytes read before the error. Compare error - codes to conditions: - @li `cond::canceled` - Operation was cancelled + @par Remarks + Supports _IoAwaitable cancellation_. @par Example @code - task read_body( ReadSource auto& source ) + capy::task read_body(capy::ReadSource auto& source) { std::string body; - auto [ec, n] = co_await read( source, string_dynamic_buffer( &body ) ); - if( ec ) - detail::throw_system_error( ec ); + auto [ec, n] = co_await capy::read(source, capy::dynamic_buffer(body)); + if (ec) + throw std::system_error(ec); return body; } @endcode @see ReadSource, DynamicBufferParam */ +template + requires ReadSource && DynamicBufferParam auto read( - ReadSource auto& source, - DynamicBufferParam auto&& buffers, + S& source, + DB&& dynbuf, std::size_t initial_amount = 2048) -> io_task { @@ -207,10 +253,10 @@ read( std::size_t total_read = 0; for(;;) { - auto mb = buffers.prepare(amount); + auto mb = dynbuf.prepare(amount); auto const mb_size = buffer_size(mb); auto [ec, n] = co_await source.read(mb); - buffers.commit(n); + dynbuf.commit(n); total_read += n; if(ec == cond::eof) co_return {{}, total_read}; diff --git a/include/boost/capy/write.hpp b/include/boost/capy/write.hpp index e3ff3e1e..70256af3 100644 --- a/include/boost/capy/write.hpp +++ b/include/boost/capy/write.hpp @@ -26,14 +26,14 @@ namespace capy { @par Await-effects - Writes the contents of `buffers` to `stream` via awaiting on + Writes the contents of `buffers` to `stream` via awaiting `stream.write_some` with consecutive portions of data from `buffers` until: @li either the full content of @c buffers is processed, @li or a contingency occurs. - If `buffer_size(buffers) == 0` then no awaiting on `stream.write_some` + If `buffer_size(buffers) == 0` then no awaiting `stream.write_some` is performed. This is not a contingency. @@ -49,7 +49,7 @@ namespace capy { Contingencies: @li The first contingency reported from - awaiting on @c stream.write_some . + awaiting @c stream.write_some . Notable conditions: @@ -62,10 +62,6 @@ namespace capy { `ec || n == buffer_size(buffers)`. - @par Cancellation - Supports cancellation via `stop_token` propagated through the - `IoAwaitable` protocol. When cancelled, returns with `cond::canceled`. - @param stream The stream to write to. If the lifetime of `stream` ends before the coroutine finishes, the behavior is undefined. @@ -73,6 +69,9 @@ namespace capy { sequence represented by `buffers` ends before the coroutine finishes, the behavior is undefined. + @par Remarks + + Supports _IoAwaitable cancellation_. @par Example @@ -87,8 +86,6 @@ namespace capy { } @endcode - @return - @see WriteStream, ConstBufferSequence, IoAwaitable, io_result, cond. */ template