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
6 changes: 0 additions & 6 deletions doc/lint/baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,6 @@
"modules/ROOT/pages/5.buffers/5a.buffers.adoc:#4:Vale.Spelling",
"modules/ROOT/pages/5.buffers/5a.buffers.adoc:#5:Vale.Spelling",
"modules/ROOT/pages/5.buffers/5a.buffers.adoc:#6:Vale.Spelling",
"modules/ROOT/pages/6.streams/6b.streams.adoc:#1:Vale.Spelling",
"modules/ROOT/pages/6.streams/6b.streams.adoc:#2:Vale.Spelling",
"modules/ROOT/pages/6.streams/6b.streams.adoc:#3:Vale.Spelling",
"modules/ROOT/pages/6.streams/6b.streams.adoc:#4:Vale.Spelling",
"modules/ROOT/pages/6.streams/6b.streams.adoc:#5:Vale.Spelling",
"modules/ROOT/pages/6.streams/6b.streams.adoc:#6:Vale.Spelling",
"modules/ROOT/pages/7.testing/7a.drivers.adoc:#1:Google.LyHyphens",
"modules/ROOT/pages/7.testing/7a.drivers.adoc:#1:Vale.Spelling",
"modules/ROOT/pages/7.testing/7a.drivers.adoc:#2:Vale.Spelling",
Expand Down
7 changes: 3 additions & 4 deletions doc/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,9 @@
** xref:4.coroutines/4h.allocators.adoc[Frame Allocators]
** xref:4.coroutines/4i.lambda-captures.adoc[Lambda Coroutine Captures]
* xref:5.buffers/5a.buffers.adoc[Buffer Sequences]
* xref:6.streams/6.intro.adoc[Stream Concepts]
** xref:6.streams/6a.overview.adoc[Overview]
** xref:6.streams/6b.streams.adoc[Streams (Partial I/O)]
** xref:6.streams/6f.isolation.adoc[Physical Isolation]
* xref:6.streams/6.intro.adoc[Streams]
** xref:6.streams/6a.concepts.adoc[Stream Concepts]
** xref:6.streams/6b.wrappers.adoc[Type-Erased Wrappers]
* xref:7.testing/7.intro.adoc[Testing]
** xref:7.testing/7a.drivers.adoc[Driving Tests]
** xref:7.testing/7b.mock-streams.adoc[Mock Streams]
Expand Down
2 changes: 1 addition & 1 deletion doc/modules/ROOT/pages/4.coroutines/4b.tasks.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Because cpp:io_result[] is a `std::tuple`, the whole standard tuple API applies:

For cpp:io_result[io_result<>] -- no payload -- a `std::error_code` converts implicitly, so `co_return some_ec;` compiles. With payloads present you must supply the whole result, as `count_ready` shows.

These two names are the vocabulary the stream concepts and the concurrent combinators are written in. xref:4.coroutines/4g.composition.adoc[Concurrent Composition] and xref:6.streams/6.intro.adoc[Stream Concepts] both assume them.
These two names are the vocabulary the stream concepts and the concurrent combinators are written in. xref:4.coroutines/4g.composition.adoc[Concurrent Composition] and xref:6.streams/6.intro.adoc[Streams] both assume them.

== Running a Task

Expand Down
24 changes: 13 additions & 11 deletions doc/modules/ROOT/pages/6.streams/6.intro.adoc
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
//
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
// 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
//

= Stream Concepts
= Streams
:page-mode: explanation

Capy organizes data flow around three concepts: cpp:ReadStream[], cpp:WriteStream[], and cpp:Stream[]. _Partial_ operations and _complete_ operations are fundamentally different things, and conflating them leads to bugs.
Capy comes with an API for byte transport in the form of _streams_.
Any concrete transport mechanism is expected to use this API in order to

Check warning on line 14 in doc/modules/ROOT/pages/6.streams/6.intro.adoc

View workflow job for this annotation

GitHub Actions / Antora Docs

[vale_adoc Capy.NoFluff] Filler/fluff — delete or rewrite (style guide C5/C9): 'in order to'.
interoperate with Capy-based programs. While such concrete mechanisms
-- like IOCP or io_uring -- are part of a separate library (Corosio),
Capy defines the API.

A socket might give you 47 bytes when you asked for 1024. That is not an error--it is the nature of the hardware. Capy's stream concepts cover the partial case directly: `read_some` and `write_some` transfer whatever the hardware allows. The complete case is a composed algorithm, not a separate concept. cpp:read[], cpp:write[], cpp:read_at_least[], and cpp:write_at_least[] loop over `read_some` or `write_some` until the buffer is satisfied or an error occurs.
The high-level idea is that data is read and written in chunks, and
you keep awaiting individual chunk transport in a loop.

== What This Section Covers
The streams API consists of

* xref:6.streams/6a.overview.adoc[Overview] -- What cpp:ReadStream[], cpp:WriteStream[], and
cpp:Stream[] model, and why partial I/O needs its own concepts.
* xref:6.streams/6b.streams.adoc[Streams (Partial I/O)] -- The cpp:ReadStream[] and
cpp:WriteStream[] concepts, and the type-erased `any_stream` wrappers.
* xref:6.streams/6f.isolation.adoc[Physical Isolation] -- Type erasure as a compilation
firewall for transport-independent, testable I/O code.
* xref:6.streams/6a.concepts.adoc[stream concepts],
* algorithms implementing loops on top of stream concepts,
* xref:6.streams/6b.wrappers.adoc[type-erased wrappers] for streams.
123 changes: 123 additions & 0 deletions doc/modules/ROOT/pages/6.streams/6a.concepts.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
//
// 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
//

= Stream Concepts
:page-mode: explanation

== ReadStream

In a read stream, data keeps coming in chunks. You keep awaiting ever-new
chunks of data. As soon as one arrives into the buffer you provided,
your coroutine is resumed, you can process that chunk, and await the
next one. This single-chunk read is reflected by operation `read_some`:

[source,cpp]
----
include::example$snippets/6a_concepts.cpp[tag=read_some_demo]
----

You provide a buffer to fill and await the result. The coroutine is resumed
as soon as the stream has something to report to you:

* either read bytes,
* or a non-nominal status (xref:A.specification-methods/Ac.contingencies.adoc[__contingency__]),
* or both.

The two returned pieces of data inform you about two things:

* `n` -- tells you how many bytes were read into the buffer.
* `ec` -- tells you about the status, `bool(ec) == false` being the nominal status.

When no contingency is reported (`!ec`), then `n > 0`. That is, `read_some` does not resume
a coroutine unless it really has something to communicate. When `n` is the size of the buffer
(full buffer fill), then `bool(ec)` is `false` (no contingency). If a contingency occurred after a
full buffer read, it will be reported in the subsequent call to `read_some`.footnote:1[The

Check warning on line 40 in doc/modules/ROOT/pages/6.streams/6a.concepts.adoc

View workflow job for this annotation

GitHub Actions / Antora Docs

[vale_adoc Capy.SimpleTense] Avoid needless future/perfect tense — prefer present simple (style guide C4): 'will '.
only exception to these rules is if you provide a buffer of zero size.]

While many values of `ec` can be returned, Capy allows you to distinguish
the following conditions, defined in enumeration cpp:cond[], relevant to stream processing.

[cols="1,3"]
|====
| name | meaning

| `eof` | The read stream transmitted all the bytes that it intended. Nothing left to read.
| `canceled` | Your own program requested the stream to stop reading, and it obeyed.
| `stream_truncated` | The transport closed without the secure handshake's goodbye message,
which might indicate a truncation attack.
| `timeout` | The read operation exceeded the time allowed for the operation.
|====

[source,cpp]
----
include::example$snippets/6a_concepts.cpp[tag=conditions]
----
<1> Check if the value of `ec` matches to condition `cond::eof`.

Due to the chunked nature of the reads from stream, a typical interaction with
a read stream involves an iteration:

[source,cpp]
----
include::example$snippets/6a_concepts.cpp[tag=read_some_pattern]
----

Such iteration is hidden between generic algorithms cpp:read[] and cpp:read_at_least[].
The contract of a read stream is represented by concept cpp:ReadStream[].



== WriteStream

When you need to send your data some place, you use a write stream.
This operation also happens in chunks. Your coroutine is being resumed after each
written chunk. This is implemented with operation `write_some`:

[source,cpp]
----
include::example$snippets/6a_concepts.cpp[tag=write_some_demo]
----

You pass the bytes to write in `buffer`. Upon resume, the write stream has written
_a chunk_ (but not necessarily all) of these bytes. The operation result is:

* `ec` -- status of the operation (or stream, depending on the value of `ec`),
* `n` -- the number of bytes written.

Any value where `bool(ec) == true` indicates a contingency (such as broken connection).

When no contingency is reported (`!ec`), then `n > 0`. That is, `write_some` does not resume
a coroutine, unless it has something to communicate. When `n` is the size of the buffer
(full buffer fill), then `bool(ec)` is `false` (no contingency). If a contingency ocurred after a
full buffer write, it will be reported in the subsequent call to `write_some`.footnote:1[]

Check warning on line 98 in doc/modules/ROOT/pages/6.streams/6a.concepts.adoc

View workflow job for this annotation

GitHub Actions / Antora Docs

[vale_adoc Capy.SimpleTense] Avoid needless future/perfect tense — prefer present simple (style guide C4): 'will '.

Note that for a common case where `n < buffer_size(buffer)` you need to rearrange the buffer,

Check warning on line 100 in doc/modules/ROOT/pages/6.streams/6a.concepts.adoc

View workflow job for this annotation

GitHub Actions / Antora Docs

[vale_adoc Capy.NoFluff] Filler/fluff — delete or rewrite (style guide C5/C9): 'Note that'.
so that the first `n` bytes is removed, before requesting another chunked write. Unlike with the read case,
writing a long content, which does not fit into a single buffer sequence, requires a nested loop.
This is why in the xref:index.adoc[Introduction] section the "echo" example reads:

[source,cpp]
----
include::example$snippets/6a_concepts.cpp[tag=write_loop]
----
<1> The outer loop: one iteration per one chunked read into the buffer.
<2> The single chunked read via member function `read_some`.
<3> Algorithm cpp:write[] with embedded inner loop that keeps awaiting
member function `write_some` until the buffer is fully written from.

Capy's generic algorithms involving write streams are cpp:write[] and cpp:write_at_least[].
The contract of a write stream is represented by concept cpp:WriteStream[].


== Stream

A type that models both cpp:ReadStream[] and cpp:WriteStream[] is a cpp:Stream[].
A TCP socket is the common case: one object carries traffic in both directions.
Also, an example above clearly uses such a "duplex" cpp:Stream[].

88 changes: 0 additions & 88 deletions doc/modules/ROOT/pages/6.streams/6a.overview.adoc

This file was deleted.

Loading
Loading