Duplex

Introduction

xpp::io::duplex(size) creates a pair of connected DuplexStreams backed by two internal ring buffers. Each half satisfies both AsyncReader and AsyncWriter — writing to one side makes data readable on the other. Like tokio's DuplexStream or Go's net.Pipe.

Works with .await(), co_await (C++20 coroutines), or .then() chains (C++11). Single-threaded — no atomics or mutex.

Example — .await()

#include <xpp/io/duplex.h>

xpp::EventLoop loop;
xpp::WaitScope scope(loop);

auto [a, b] = xpp::io::duplex(4096);

// a → b and b → a simultaneously
a.write("ping", 4).await();
char buf[8];
b.read(buf, 8).await();  // buf = "ping"

Example — co_await (C++20)

auto [a, b] = xpp::io::duplex(4096);
co_await a.write("ping", 4);
char buf[8];
co_await b.read(buf, 8);  // buf = "ping"

How it works

duplex(4096)
├── side[0]: A's receive buffer (B writes here, A reads)
├── side[1]: B's receive buffer (A writes here, B reads)
├── DuplexStream(idx=0) → reads from side[0], writes to side[1]
└── DuplexStream(idx=1) → reads from side[1], writes to side[0]

write(buf, len):
  copy to opposite side's buffer → wake opposite reader
read(buf, len):
  copy from own buffer → wake opposite writer
close():
  set opposite side's closed flag → wake opposite reader (EOF)

Writing suspends when the destination buffer is full; reading suspends when the source buffer is empty. Both use PromiseResolver for wakeup — no event loop needed, resolution is synchronous from the opposite half.

API Reference

MethodReturnsDescription
duplex(size)pair<DuplexStream, DuplexStream>Create connected pair with size-byte ring buffers per direction
DuplexStream::read(buf, len)Promise<ssize_t>Read from this side. Suspends when empty, returns 0 on EOF
DuplexStream::write(buf, len)Promise<ssize_t>Write to the other side. Suspends when buffer full
DuplexStream::flush()Promise<void>No-op (data flows directly to buffer)
DuplexStream::close()voidClose write direction. Other side sees EOF on read

Usage Examples

Bidirectional echo — .await()

xpp::EventLoop loop;
xpp::WaitScope scope(loop);

auto [client, server] = xpp::io::duplex(256);

client.write("ping", 4).await();
char buf[8];
server.read(buf, 8).await();        // server reads "ping"
server.write("pong", 4).await();    // server responds
client.read(buf, 8).await();        // client reads "pong"

Bidirectional echo — co_await (C++20)

auto [client, server] = xpp::io::duplex(256);
co_await client.write("ping", 4);
char buf[8];
co_await server.read(buf, 8);       // server reads "ping"
co_await server.write("pong", 4);   // server responds
co_await client.read(buf, 8);       // client reads "pong"

Test I/O utilities without TCP — .await()

auto [a, b] = xpp::io::duplex(4096);

// Producer
b.write("hello world", 11).await();
b.close();

// Consumer with BufReader
xpp::io::BufReader buf{std::move(a)};
auto data = xpp::io::read_all(buf).await();
// data == "hello world"

Test I/O utilities without TCP — co_await (C++20)

auto [a, b] = xpp::io::duplex(4096);

auto wp = [](xpp::io::DuplexStream &w) -> xpp::Promise<void> {
    co_await w.write("hello world", 11);
    w.close();
};
auto rp = [](xpp::io::DuplexStream &r) -> xpp::Promise<void> {
    xpp::io::BufReader buf{std::move(r)};
    auto data = co_await xpp::io::read_all(buf);
};