Use one owner for each runtime job.
Session owns lifecycle. Tasks own cooperative work. Network owns byte transport. Telemetry observes completed work without replacing its result.
Choose the narrowest public entry
| Task | Header | Owner |
|---|---|---|
| Open, reuse, and close a runtime | <rund/session.hpp> | rund::Session, one-shot rund::run, and telemetry configuration. |
| Spawn and await cooperative work | <rund/task.hpp> | rund::task tasks, groups, channels, yield, sleep, and cancellation. |
| Admit files, environment, random, and logical time | <rund/host.hpp> | rund::host host-observation boundary. |
| Move bytes over sockets | <rund/net.hpp> | rund::net socket ownership, readiness, stream/datagram I/O, and bounded servers. |
| Account retained storage | <rund/storage.hpp> | rund::storage hierarchical budget admission. |
| Optional deterministic placement | <cluster/cluster.hpp> | rund::cluster shard placement and retry identity. |
Every entry still links through the single runD::sdk CMake target.
Start with one-shot Session work
Use rund::run for one bounded callback. It opens the Session, runs the callback,
drains scheduled work, closes, and returns one result.
#include <rund/session.hpp>
int main() {
const rund::Session::Result result =
rund::run(rund::SessionConfig{.workers = 1u}, [] {});
return result.exit_code();
}
Open once for repeated work
rund::Session session;
auto opened = session.open(config);
if (!opened) return opened.exit_code();
for (const auto& tick : ticks) {
auto completed = session.scope([&] { simulate(tick); });
if (!completed) { operation = completed.exit_code(); break; }
}
auto closed = session.close();
return operation == 0 ? closed.exit_code() : operation;
Reuse one Session for repeated ticks. Ordinary code calls blocking close() once;
use drain() only when you deliberately need to observe the Draining state.
Spawn cooperative work inside a Session
Include <rund/task.hpp> for bounded work that shares the active Session
scheduler. Keep the returned Handle, check admission, and join it before captured state goes
out of scope.
#include <rund/session.hpp>
#include <rund/task.hpp>
int main() {
int value = 0;
rund::task::Status task_result = rund::task::Status::success();
const rund::Session::Result hosted =
rund::run(rund::SessionConfig{.workers = 1u}, [&] {
const rund::task::Handle task =
rund::task::spawn("set-value", [&] { value = 42; });
if (!task) {
task_result = rund::task::Status::fail(task.code());
return;
}
task_result = rund::task::join(task);
});
if (!hosted) {
return hosted.exit_code();
}
if (!task_result) {
return task_result.exit_code();
}
return value == 42 ? 0 : 2;
}
Handle::code() is the admission failure; Status::code() is the
joined task failure. A missing runtime requires an active Session scope. A capacity failure
requires fewer live tasks or a larger caller-configured scheduler capacity. Neither failure
means the callable ran.
Admit host input once
Include <rund/host.hpp> for environment, file, random, logical-clock, and
timer operations. This example reads an environment value while the Session records the host
observation.
#include <rund/host.hpp>
#include <rund/session.hpp>
#include <string>
#include <utility>
int main() {
int operation = 0;
std::string path{};
const rund::Session::Result hosted =
rund::run(rund::SessionConfig{.workers = 1u}, [&] {
auto value = rund::host::env::get("PATH");
if (!value) {
operation = value.exit_code();
return;
}
path = std::move(*value);
});
if (!hosted) {
return hosted.exit_code();
}
if (operation != 0) {
return operation;
}
(void)path;
return 0;
}
A missing environment variable is a successful empty value; a failed Result means the
operation itself could not be admitted or represented. Inspect its code() or
error(), fix the input or resource condition, and rerun the containing scope.
Reserve storage before retaining bytes
Include <rund/storage.hpp> when several producers must share one hard
in-process byte budget. Reserve a conservative allocated-byte ceiling, commit measured use,
then refund when the retained data is released.
#include <rund/storage.hpp>
int main() {
rund::storage::Budget budget{1024u};
if (!budget) {
return budget.exit_code();
}
auto reservation = budget.reserve(256u);
if (!reservation) {
return reservation.exit_code();
}
const rund::storage::Status committed = reservation.commit(
rund::storage::Usage{.physical_bytes = 128u, .allocated_bytes = 256u});
if (!committed) {
return committed.exit_code();
}
const rund::storage::Report used = budget.report();
if (!used) {
return used.exit_code();
}
if (used.allocated_bytes != 256u || used.available_bytes != 768u) {
return 2;
}
const rund::storage::Status refunded = reservation.refund();
return refunded.exit_code();
}
StorageCapacityExceeded means another live reservation or committed allocation
owns the bytes; release it or change the owner-set budget before retrying. A commit failure
means the supplied measured allocation exceeded its reservation or was otherwise invalid.
The Reservation keeps the exactly-once refund obligation until refund or destruction.
Place a shard from an ordered candidate set
Include <cluster/cluster.hpp> only when the application needs optional
deterministic placement or retry identity. The application supplies stable shard, candidate,
and epoch identities; runD returns one typed decision.
#include <cluster/cluster.hpp>
#include <array>
int main() {
const rund::cluster::ShardRef shard{
rund::cluster::JobId{10u},
rund::cluster::ShardId{20u},
};
constexpr std::array<rund::cluster::NodeId, 2u> candidates{
rund::cluster::NodeId{40u},
rund::cluster::NodeId{50u},
};
const rund::cluster::PlacementResult placed =
rund::cluster::place_shard(rund::cluster::PlacementRequest{
shard, candidates, rund::cluster::PlacementEpoch{7u}});
if (!placed) {
return placed.exit_code();
}
return placed.placement.node == candidates.front() ? 0 : 2;
}
PlacementCode::ShardRequired means the shard identity is incomplete;
NodeRequired means the ordered candidate set has no valid node. Correct those
inputs and evaluate again. Candidate order is part of the caller-supplied decision input.
Network owns transport, not protocol meaning
net::Socket is the move-only owner. SocketView is a borrowed,
non-closing capability. Scalar stream and datagram operations are move-only awaitables; the
application owns buffers and protocol interpretation.
#include <rund/net.hpp>
#include <rund/task.hpp>
#include <cstddef>
#include <span>
rund::task::Task<void> Read(const rund::net::SocketView socket,
const std::span<std::byte> bytes,
rund::net::ReceiveResult &result) {
result = co_await rund::net::receive(socket, bytes);
}
rund::task::Task<void> SendPacket(const rund::net::SocketView socket,
const std::span<const std::byte> bytes,
const rund::net::Address peer,
rund::net::datagram::SendResult &result) {
result = co_await rund::net::datagram::send(socket, bytes, peer);
}
Readiness sets, accept/connect, drains, frames, and servers add explicit budgets. Scope exit closes owning sockets; a borrowed view cannot close or outlive its owner.
Observe cost without changing the result
Bind one lvalue observer to SessionConfig. Basic telemetry reports stable
identities, counters, hashes, and findings. Detail additionally measures phase timing.
#include <rund/replay.hpp>
#include <cstdio>
#include <string_view>
int main() {
auto observe = [](const rund::telemetry::Event &event) {
rund::telemetry::describe(event, [](const std::string_view text) {
std::printf("%.*s", static_cast<int>(text.size()), text.data());
});
std::putchar('\n');
};
rund::SessionConfig config{.workers = 1u};
config.telemetry = rund::telemetry::bind(observe);
rund::Session session{};
const rund::Session::Status opened = session.open(config);
if (!opened) {
return opened.exit_code();
}
const rund::replay::Live result =
rund::replay::live(session, [](rund::replay::Context &) {});
const rund::Session::Status closed = session.close();
if (!result) {
return result.exit_code();
}
return closed.exit_code();
}
The observer must outlive terminal operations and Session::close(). A finding
suggests an action; it never replaces the operation's typed outcome.