Skip to content

Commit 5cb0a02

Browse files
committed
Align read_some/write_some to E2 semantics and add exception docs
Adopt the relaxed error postcondition (n >= 0 on error) from the read-some-rationale consensus throughout headers, implementation, docs, examples, and papers. Fix composed algorithms (read, write, write_now, push_to) to use advance-then-check ordering. Replace .failed() with operator bool() idiom. Add error reporting and throws documentation to all concept Javadocs and the rationale doc. Introduce await-returns terminology per stakeholder feedback. Use C++ expressions for range constraints. Remove if(n > 0) guards from echo loops in favor of unconditional write-then-check pattern.
1 parent 6f55745 commit 5cb0a02

57 files changed

Lines changed: 516 additions & 452 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

doc/modules/ROOT/pages/5.buffers/5c.sequences.adoc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,10 @@ task<std::size_t> read_all(Stream& stream, Buffers buffers)
117117
while (buffer_size(remaining) > 0)
118118
{
119119
auto [ec, n] = co_await stream.read_some(remaining);
120-
if (ec.failed())
121-
break;
122120
remaining.consume(n);
123121
total += n;
122+
if (ec)
123+
break;
124124
}
125125
126126
co_return total;

doc/modules/ROOT/pages/5.buffers/5e.algorithms.adoc

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -157,11 +157,10 @@ task<std::size_t> read_full(Stream& stream, Buffers buffers)
157157
while (buffer_size(remaining) > 0)
158158
{
159159
auto [ec, n] = co_await stream.read_some(remaining);
160-
if (ec.failed())
161-
co_return total; // Return partial read on error
162-
163160
remaining.consume(n);
164161
total += n;
162+
if (ec)
163+
co_return total;
165164
}
166165
167166
co_return total;
@@ -181,11 +180,10 @@ task<std::size_t> write_full(Stream& stream, Buffers buffers)
181180
while (buffer_size(remaining) > 0)
182181
{
183182
auto [ec, n] = co_await stream.write_some(remaining);
184-
if (ec.failed())
185-
co_return total;
186-
187183
remaining.consume(n);
188184
total += n;
185+
if (ec)
186+
co_return total;
189187
}
190188
191189
co_return total;

doc/modules/ROOT/pages/5.buffers/5f.dynamic.adoc

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,8 @@ task<> read_into_buffer(Stream& stream, DynamicBuffer auto& buffer)
7676
7777
// Read into prepared space
7878
auto [ec, n] = co_await stream.read_some(space);
79-
80-
if (!ec.failed())
81-
buffer.commit(n); // Make data readable
79+
80+
buffer.commit(n); // Make data readable
8281
}
8382
----
8483

@@ -240,9 +239,9 @@ task<std::string> read_line(Stream& stream)
240239
// Prepare space and read
241240
auto space = buffer.prepare(256);
242241
auto [ec, n] = co_await stream.read_some(space);
243-
if (ec.failed())
244-
throw std::system_error(ec);
245242
buffer.commit(n);
243+
if (ec)
244+
throw std::system_error(ec);
246245
247246
// Search for newline in readable data
248247
auto data = buffer.data();

doc/modules/ROOT/pages/6.streams/6a.overview.adoc

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,10 +167,12 @@ task<> echo(any_stream& stream)
167167
for (;;)
168168
{
169169
auto [ec, n] = co_await stream.read_some(mutable_buffer(buf));
170+
171+
auto [wec, wn] = co_await write(stream, const_buffer(buf, n));
172+
170173
if (ec)
171174
co_return;
172-
173-
auto [wec, wn] = co_await write(stream, const_buffer(buf, n));
175+
174176
if (wec)
175177
co_return;
176178
}

doc/modules/ROOT/pages/6.streams/6b.streams.adoc

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,18 @@ template<MutableBufferSequence Buffers>
2828
IoAwaitable auto read_some(Buffers buffers);
2929
----
3030

31-
Returns an awaitable yielding `(error_code, std::size_t)`:
31+
Attempts to read up to `buffer_size(buffers)` bytes from the stream into the buffer sequence. Await-returns `(error_code, std::size_t)`:
3232

33-
* On success: `!ec`, and `n >= 1` bytes were read
34-
* On error: `ec`, and `n == 0`
35-
* On EOF: `ec == cond::eof`, and `n == 0`
33+
If `buffer_size(buffers) > 0`:
3634

37-
If `buffer_empty(buffers)` is true, completes immediately with `n == 0` and no error.
35+
* If `!ec`, then `n >= 1 && n \<= buffer_size(buffers)`. `n` bytes were read into the buffer sequence.
36+
* If `ec`, then `n >= 0 && n \<= buffer_size(buffers)`. `n` is the number of bytes read before the I/O condition arose.
37+
38+
If `buffer_empty(buffers)` is true, `n` is 0. The empty buffer is not itself a cause for error, but `ec` may reflect the state of the stream.
39+
40+
I/O conditions from the underlying system are reported via `ec`. Failures in the library itself (such as allocation failure) are reported via exceptions.
41+
42+
*Throws:* `std::bad_alloc` if coroutine frame allocation fails.
3843

3944
=== Partial Transfer
4045

@@ -45,7 +50,7 @@ If `buffer_empty(buffers)` is true, completes immediately with `n == 0` and no e
4550
char buf[1024];
4651
auto [ec, n] = co_await stream.read_some(mutable_buffer(buf));
4752
// n might be 1, might be 500, might be 1024
48-
// The only guarantee: if !ec && n > 0
53+
// if !ec, then n >= 1
4954
----
5055

5156
This matches underlying OS behavior—reads return when *some* data is available.
@@ -58,18 +63,15 @@ template<ReadStream Stream>
5863
task<> dump_stream(Stream& stream)
5964
{
6065
char buf[256];
61-
66+
6267
for (;;)
6368
{
6469
auto [ec, n] = co_await stream.read_some(mutable_buffer(buf));
65-
66-
if (ec == cond::eof)
67-
break; // End of stream
68-
69-
if (ec)
70-
throw std::system_error(ec);
71-
70+
7271
std::cout.write(buf, n);
72+
73+
if (ec)
74+
break;
7375
}
7476
}
7577
----
@@ -95,12 +97,18 @@ template<ConstBufferSequence Buffers>
9597
IoAwaitable auto write_some(Buffers buffers);
9698
----
9799

98-
Returns an awaitable yielding `(error_code, std::size_t)`:
100+
Attempts to write up to `buffer_size(buffers)` bytes from the buffer sequence to the stream. Await-returns `(error_code, std::size_t)`:
99101

100-
* On success: `!ec`, and `n >= 1` bytes were written
101-
* On error: `ec`, and `n == 0`
102+
If `buffer_size(buffers) > 0`:
102103

103-
If `buffer_empty(buffers)` is true, completes immediately with `n == 0` and no error.
104+
* If `!ec`, then `n >= 1 && n \<= buffer_size(buffers)`. `n` bytes were written from the buffer sequence.
105+
* If `ec`, then `n >= 0 && n \<= buffer_size(buffers)`. `n` is the number of bytes written before the I/O condition arose.
106+
107+
If `buffer_empty(buffers)` is true, `n` is 0. The empty buffer is not itself a cause for error, but `ec` may reflect the state of the stream.
108+
109+
I/O conditions from the underlying system are reported via `ec`. Failures in the library itself (such as allocation failure) are reported via exceptions.
110+
111+
*Throws:* `std::bad_alloc` if coroutine frame allocation fails.
104112

105113
=== Partial Transfer
106114

@@ -191,20 +199,15 @@ task<> handle_connection(any_stream& stream)
191199
192200
for (;;)
193201
{
194-
// Read some data
195202
auto [ec, n] = co_await stream.read_some(mutable_buffer(buf));
196-
197-
if (ec == cond::eof)
198-
co_return; // Client closed connection
199-
200-
if (ec)
201-
throw std::system_error(ec);
202-
203-
// Echo it back
203+
204204
auto [wec, wn] = co_await write(stream, const_buffer(buf, n));
205-
205+
206+
if (ec)
207+
break;
208+
206209
if (wec)
207-
throw std::system_error(wec);
210+
break;
208211
}
209212
}
210213
----

doc/modules/ROOT/pages/6.streams/6c.sources-sinks.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ template<MutableBufferSequence Buffers>
2929
IoAwaitable auto read(Buffers buffers);
3030
----
3131

32-
Returns an awaitable yielding `(error_code, std::size_t)`:
32+
Await-returns `(error_code, std::size_t)`:
3333

3434
* On success: `!ec`, and `n == buffer_size(buffers)` (buffer completely filled)
3535
* On EOF: `ec == cond::eof`, and `n` is bytes read before EOF (partial read)

doc/modules/ROOT/pages/6.streams/6d.buffer-concepts.adoc

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,11 @@ concept BufferSource =
5252
IoAwaitable auto pull(const_buffer* arr, std::size_t max_count);
5353
----
5454

55-
Returns an awaitable yielding `(error_code, std::size_t)`:
55+
Await-returns `(error_code, std::size_t)`:
5656

57-
* On success: `!ec.failed()`, fills `arr[0..count-1]` with buffer descriptors
57+
* On success: `!ec`, fills `arr[0..count-1]` with buffer descriptors
5858
* On exhausted: `count == 0` indicates no more data
59-
* On error: `ec.failed()`
59+
* On error: `ec`
6060

6161
The buffers point into the source's internal storage. You must consume all returned data before calling `pull()` again—the previous buffers become invalid.
6262

@@ -73,7 +73,7 @@ task<> process_source(Source& source)
7373
{
7474
auto [ec, count] = co_await source.pull(bufs, 8);
7575
76-
if (ec.failed())
76+
if (ec)
7777
throw std::system_error(ec);
7878
7979
if (count == 0)
@@ -228,7 +228,7 @@ task<> decompress_stream(any_buffer_source& compressed, any_write_sink& output)
228228
for (;;)
229229
{
230230
auto [ec, count] = co_await compressed.pull(bufs, 8);
231-
if (ec.failed())
231+
if (ec)
232232
throw std::system_error(ec);
233233
if (count == 0)
234234
break;

doc/modules/ROOT/pages/6.streams/6e.algorithms.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ if (ec == cond::eof)
224224
// Normal completion
225225
std::cout << "Transferred " << total << " bytes\n";
226226
}
227-
else if (ec.failed())
227+
else if (ec)
228228
{
229229
// Error occurred
230230
std::cerr << "Error after " << total << " bytes: " << ec.message() << "\n";

doc/modules/ROOT/pages/6.streams/6f.isolation.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ task<> handle_protocol(any_stream& stream)
3838
for (;;)
3939
{
4040
auto [ec, n] = co_await stream.read_some(mutable_buffer(buf));
41-
if (ec.failed())
41+
if (ec)
4242
co_return;
4343
4444
// Process and respond...

doc/modules/ROOT/pages/7.examples/7d.mock-stream-testing.adoc

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,19 @@ capy::task<bool> echo_line_uppercase(capy::any_stream& stream)
4141
// ec: std::error_code, n: std::size_t
4242
auto [ec, n] = co_await stream.read_some(capy::mutable_buffer(&c, 1));
4343
44+
if (n > 0)
45+
{
46+
if (c == '\n')
47+
break;
48+
line += static_cast<char>(std::toupper(static_cast<unsigned char>(c)));
49+
}
50+
4451
if (ec)
4552
{
4653
if (ec == capy::cond::eof)
4754
break;
4855
co_return false;
4956
}
50-
51-
if (c == '\n')
52-
break;
53-
54-
line += static_cast<char>(std::toupper(static_cast<unsigned char>(c)));
5557
}
5658
5759
line += '\n';
@@ -63,11 +65,11 @@ capy::task<bool> echo_line_uppercase(capy::any_stream& stream)
6365
// wec: std::error_code, wn: std::size_t
6466
auto [wec, wn] = co_await stream.write_some(
6567
capy::const_buffer(line.data() + written, line.size() - written));
66-
68+
69+
written += wn;
70+
6771
if (wec)
6872
co_return false;
69-
70-
written += wn;
7173
}
7274
7375
co_return true;

0 commit comments

Comments
 (0)