Transfer Algorithms

This section explains the composed read/write operations and transfer algorithms.

Prerequisites

Composed Read/Write

The partial operations (read_some, write_some) often require looping. Capy provides composed operations that handle the loops for you.

read

Fills a buffer completely by looping read_some:

#include <boost/capy/read.hpp>

template<ReadStream Stream, MutableBufferSequence Buffers>
io_task<std::size_t>
read(Stream& stream, Buffers buffers);

Await-returns io_result<std::size_t>, destructuring as [ec, n]. Keeps reading until:

  • Buffer is full (n == buffer_size(buffers))

  • The underlying read_some reports a condition before the buffer is full (the condition is propagated with the partial count)

Example:

char buf[1024];
auto [ec, n] = co_await read(stream, make_buffer(buf));
// n == 1024, or ec indicates why not

read with DynamicBuffer

Reads into a growable dynamic buffer, stopping at end-of-stream, when the buffer reaches max_size(), or on a non-EOF error:

template<ReadStream Stream, DynamicBufferParam Buffer>
io_task<std::size_t>
read(Stream& stream, Buffer&& buffer, std::size_t initial_amount = 2048);

Example:

std::string storage;
auto buffer = dynamic_buffer(storage);
auto [ec, n] = co_await read(stream, buffer);
// storage holds all data read up to EOF or max_size(); n is the byte count

read from a ReadSource with DynamicBuffer

A third overload accepts a ReadSource and a dynamic buffer. It drives the source’s complete-read read (rather than read_some), appending until EOF or until the buffer reaches max_size():

template<ReadSource Source, DynamicBufferParam Buffer>
io_task<std::size_t>
read(Source& source, Buffer&& buffer, std::size_t initial_amount = 2048);

n is the total number of bytes read, inclusive of the final partial read.

write

Writes all data by looping write_some:

#include <boost/capy/write.hpp>

template<WriteStream Stream, ConstBufferSequence Buffers>
io_task<std::size_t>
write(Stream& stream, Buffers buffers);

Keeps writing until:

  • All data is written (n == buffer_size(buffers))

  • Error occurs (returns error with partial count)

Example:

co_await write(stream, make_buffer("Hello, World!"));

read_until

Reads from a stream into a dynamic buffer until a match condition is satisfied. Useful for delimiter-based protocols (e.g. reading a line or an HTTP header block):

#include <boost/capy/read_until.hpp>

// Match-condition overload
template<ReadStream Stream, class Buffer, MatchCondition Match>
io_task<std::size_t>
read_until(Stream& stream, Buffer&& buffer, Match match,
    std::size_t initial_amount = 2048);

// Delimiter-string convenience overload
template<ReadStream Stream, class Buffer>
io_task<std::size_t>
read_until(Stream& stream, Buffer&& buffer, std::string_view delim,
    std::size_t initial_amount = 2048);

If !ec, the match succeeded and n is the number of bytes through the end of the match (the position one past the matched delimiter). Notable conditions:

  • cond::eof — end-of-stream reached before a match; n is the buffer size

  • cond::not_foundmax_size() reached before a match

A MatchCondition is a callable (std::string_view data, std::size_t* hint) returning the position past the match, or std::string_view::npos on no match. When hint is non-null it may receive an overlap hint so a delimiter spanning two reads is not missed. The match_delim struct adapts a std::string_view delimiter to this interface and underlies the convenience overload.

std::string line;
auto [ec, n] = co_await read_until(
    stream, string_dynamic_buffer(&line), "\r\n");
if (ec == cond::eof)
    co_return line;        // partial line at EOF
if (ec)
    throw std::system_error(ec);
line.resize(n - 2);        // n includes the "\r\n"; strip it

write_now

write_now eagerly writes a complete buffer sequence, attempting to finish synchronously. If every underlying write_some completes without suspending, the whole operation completes in await_ready with no coroutine suspension. It caches a single coroutine frame and reuses it across calls, avoiding repeated allocation on a hot write path:

#include <boost/capy/io/write_now.hpp>

template<WriteStream Stream>
class write_now;

Construct it from a stream, then call it like a function. Only one operation may be outstanding at a time:

write_now wn(stream);
auto [ec, n] = co_await wn(make_buffer("hello"));
if (ec)
    throw std::system_error(ec);

Transfer Algorithms

Transfer algorithms move data between sources/sinks and streams.

push_to

Transfers data from a BufferSource to a destination:

#include <boost/capy/io/push_to.hpp>

// To WriteSink (with EOF propagation)
template<BufferSource Source, WriteSink Sink>
io_task<std::size_t>
push_to(Source& source, Sink& sink);

// To WriteStream (streaming, no EOF)
template<BufferSource Source, WriteStream Stream>
io_task<std::size_t>
push_to(Source& source, Stream& stream);

The source provides buffers via pull(). Data is pushed to the destination. Buffer ownership stays with the source: no intermediate copying when possible.

Example:

// Transfer file to network
mmap_source file("large_file.bin");
co_await push_to(file, socket);

pull_from

Transfers data from a source to a BufferSink:

#include <boost/capy/io/pull_from.hpp>

// From ReadSource
template<ReadSource Source, BufferSink Sink>
io_task<std::size_t>
pull_from(Source& source, Sink& sink);

// From ReadStream (streaming)
template<ReadStream Stream, BufferSink Sink>
io_task<std::size_t>
pull_from(Stream& stream, Sink& sink);

The sink provides writable buffers via prepare(). Data is pulled from the source directly into the sink’s buffers.

Example:

// Receive network data into compression buffer
compression_sink compressor;
co_await pull_from(socket, compressor);

Why No buffer-to-buffer?

There is no push_to(BufferSource, BufferSink) because it would require redundant copying. The source owns read-only buffers; the sink owns writable buffers. Transferring between them would need an intermediate copy, defeating the zero-copy purpose.

Instead, compose with an intermediate stage:

// Transform: BufferSource -> processing -> BufferSink
task<> process_pipeline(any_buffer_source& source, any_buffer_sink& sink)
{
    const_buffer src_arr[8];

    while (true)
    {
        auto [ec, src_bufs] = co_await source.pull(src_arr);
        if (ec == cond::eof)
            break;

        std::size_t consumed = 0;
        for (auto const& b : src_bufs)
        {
            auto processed = transform(b);

            // Write processed data to sink
            mutable_buffer dst_arr[8];
            auto dst_bufs = sink.prepare(dst_arr);

            std::size_t copied = buffer_copy(
                dst_bufs,
                make_buffer(processed));

            co_await sink.commit(copied);
            consumed += b.size();
        }

        source.consume(consumed);
    }

    co_await sink.commit_eof(0);
}

Naming Convention

The algorithm names reflect buffer ownership:

Name Meaning

push_to

Source provides buffers → push data to destination

pull_from

Sink provides buffers → pull data from source

The preposition indicates the direction of buffer provision, not data flow.

Error Handling

All transfer algorithms return (error_code, std::size_t):

  • error_code — Success, EOF, or error condition

  • std::size_t — Total bytes transferred before return

On error, partial transfer may have occurred. The returned count indicates how much was transferred.

Example:

auto [ec, total] = co_await push_to(source, sink);

if (ec == cond::eof)
{
    // Normal completion
    std::cout << "Transferred " << total << " bytes\n";
}
else if (ec)
{
    // Error occurred
    std::cerr << "Error after " << total << " bytes: " << ec.message() << "\n";
}

Reference

Header Description

<boost/capy/read.hpp>

Composed read operations

<boost/capy/write.hpp>

Composed write operations

<boost/capy/read_until.hpp>

Read until a match condition or delimiter

<boost/capy/io/write_now.hpp>

Eager write with frame caching

<boost/capy/io/push_to.hpp>

BufferSource → WriteSink/WriteStream transfer

<boost/capy/io/pull_from.hpp>

ReadSource/ReadStream → BufferSink transfer

You have now learned about transfer algorithms. Continue to Physical Isolation to learn how type erasure enables compilation firewalls.