CPU TCP foundation#
This page covers the CPU memory, registration, TCP and notification contracts (P1.1–P1.4, P2.1–P2.2 in the completion plan). The TCP payload path uses Rust sockets; the crates.io alpha does not contain it.
Architecture correspondence#
The reference is Mooncake TENT
e1ea5d31bb5e46ff2df9f90e7c2fbaf52fcdee8c.
TENT responsibility |
Rust owner |
Delivered subset |
|---|---|---|
|
|
Immutable config, sync/async batches, ordered execution and requester/source drain |
|
|
Shared allocation owners, reference-counted ranges, descriptors, permissions and epoch fencing |
|
|
P2P descriptor open/refresh and source-owned handlers |
|
|
Host byte movement, deadlines, complete READ response and final WRITE ACK |
Notification queues and submit hooks |
|
Bounded named messages, atomic polling and batch-completion delivery |
Config and resource observations |
|
Per-engine budgets and resource counters |
ControlService owns the shared socket service, descriptor and notification
protocol, source admission and dispatch. TENT keeps its TCP payload handlers in
ControlService; OrbiTE moves the byte behavior into TcpTransport. RDMA uses
a ControlChannel for setup without going through TcpTransport. Cache keys,
replica grants and recovery policy stay with OrbitKV.
Ordinary TCP leases one exclusive stream per endpoint and authoritative source
epoch to the executing caller or batch worker. Completed frames reuse it;
WRITEs on that stream stay ordered. Concurrent direct-TCP submissions to that
peer serialize on this lane; independent peers have separate lanes. The requester pool uses
max_peer_segments slots, including borrowed and uncertain entries. At capacity
it can evict only an idle, clean, unreferenced entry. A busy or failed entry
stays charged. close_segment fences admission and drains the peer before
closing its cached stream. No new worker thread or public configuration field
is added.
The existing control workers monitor idle streams through mio readiness.
Idle streams hold neither source-memory admission nor registration loans. Their
shared cache is also bounded by max_peer_segments, in addition to the fixed
active handlers and bounded accept queue. Each complete TCP frame gets fresh
source admission and the existing handler deadline. Direct TCP waits on the
socket with deadline-bounded waits; the staged CUDA pipeline retains its
bounded nonblocking progress path.
A timeout, partial frame, invalid reply, lost acknowledgement or abandoned lease
retires the stream; it never returns to the cache. A confirmed EOF on an idle,
already completed stream can reconnect before the next request posts.
There is no transport-local replay. Failed socket retirement returns
Unquiesced, retains the descriptor and its slot, and is never attempted again.
The existing task owner keeps backing and transfer charges on that outcome.
Running TCP cancellation remains unsupported; queued cancellation posts nothing.
The control acceptor uses readiness and a bounded handler queue. A full queue closes the new connection. Peer resets/timeouts leave the listener available; resource exhaustion logs once per burst and retries on a bounded interval. Fatal listener errors are logged. Shutdown wakes and joins the acceptor and handlers. Both ends enable TCP_NODELAY. See the profile and matched measurements for the measured costs and limits.
Rust contracts#
Owned memory.
register_memory(Box<[u8]>, Permission)consumes the backing, which handles and admitted handlers keep alive throughArc. The source checks remote permissions regardless of requester metadata.External memory. Unsafe
HostMemory::from_raw_parts(address, length, owner)adopts a pre-existing host allocation;ownermust keep it alive, stable and writable. It does not pin pages or make a device pointer host-accessible.Subranges and batches.
register_host_memory(memory, offset, length, permission)registers a checked subrange; overlaps must share oneHostMemorylock.register_host_memory_batchpreflights the whole batch and publishes it atomically, so a failure leaves existing state intact.Duplicates. As in TENT’s
SegmentTracker, an exact duplicate shares one ID and charge, the first permission wins, and each call adds one reference. Duplicates inside one Rust batch are coalesced (TENT can publish them separately).IDs and unregister. Registration IDs are never reused. The final
unregister_memoryreference removes visibility; running handlers keep their backing and charges until they finish.Address lookup.
resolve_local_addressandRemoteSegment::resolve_addressneed one range covering the whole request, using TENT’s address-ascending, length-descending order, and never dereference an unregistered address.Authority.
open_segment(endpoint, expected_epoch, timeout)takes the 128-bit epoch from the authoritative source owner, and every operation rechecks it at the source, so a new process at the same address cannot inherit authority.Requests.
TransferRequest::new(transport, local_address, remote_address, length)takes absolute addresses that carry no authority. Submission resolves and retains every local registration before I/O, so a concurrent unregister cannot free admitted backing.Execution.
submit_and_waitvalidates every request first, runs them in order and stops at the first failure; the report has the completed prefix, exact bytes and failed index. A batch is not a transaction.READ and WRITE. READ receives the full payload before touching its local slice. WRITE copies its input, waits for source readiness, sends the payload and waits for an ACK after the source commits. A WRITE failure after sending returns
RemoteEffect::Unknownand is never retried.Deadlines. One absolute requester deadline covers connects, frames and payloads; a separate source deadline starts at accept. Neither is a hard real-time bound.
Source close.
close_source(timeout)permanently closes source admission and waits for handlers; a timeout keeps admission closed and can be retried.
Async batches use the same validation and executor; see async batches.
Notification contracts#
Notification::new(name, message) carries UTF-8 strings (empty strings and
repeats are valid). send_local_notification delivers in process;
send_notification(peer, notification, timeout) uses epoch-fenced TCP for other
peers. A receipt confirms enqueue, not consumption. receive_notifications()
atomically drains the queue in enqueue order, so concurrent pollers get disjoint
messages. Matching, generations and waits belong to the consumer.
submit_and_notify sends once to the batch peer after every request succeeds;
empty or failed batches report NotificationStatus::Skipped. Data completion
and report.notification are separate, and a failed notification never
authorizes replaying data. The receiver reserves count and bytes before
accepting a body, and malformed or partial bodies never enter the inbox. After
body submission without a final receipt the effect is Unknown; manual retries
can duplicate a message.
The sender tries each installed notification-capable channel in order. Only
an unavailable channel with no delivery effect permits fallback. OrbiTE’s
installed channel is TCP. The independent control RPC can receive without
that channel, and uses the same bounded inbox as self delivery. Set
tcp_notifications to disable the TCP channel or notification_rpc_fallback
to stop after its first attempt and disable RPC fallback. Both default to true.
When the RPC also fails, the sender preserves the last channel error and any
uncertain RPC effect. Receive drains all installed queues even if one fails.
Successful deliveries take precedence over an empty-channel error.
Async completion hooks retain
only destinations still eligible for retry.
Budgets and profile#
Default |
Value |
Accounting |
|---|---|---|
Bind |
|
Loopback with an OS-chosen port; other hosts need a routable bind |
Retained registrations |
1,024 / 1 GiB |
Retained ranges, including unregistered handles; protocol limit 4,096 regions |
Batch descriptors |
1,024 |
Rejected before effects if exceeded |
Allocated async batches / slots |
32 / 32,768 |
Open, finished-but-unfreed and detached batches |
In-flight requester submissions |
32 |
Async groups plus synchronous, open and notification calls |
In-flight requester bytes |
64 MiB |
Request bytes plus notification bytes |
One payload |
8 MiB |
Configurable up to 64 MiB; each source enforces its own |
Source workers / queue |
4 / 4 sockets |
Queue overflow closes the connection |
Source timeout |
5 s |
Accept-to-handler deadline, configurable in |
Pending notifications |
1,024 / 1 MiB |
Queued messages plus reserved incoming bodies |
One notification |
64 KiB |
Name plus message bytes, enforced at both ends |
The protocol is plain TCP for trusted environments: epochs fence restarts but
do not authenticate peers. There is one connection per request and sequential
batch execution. stats() samples registration, admission and notification
counters under separate locks. These budgets cover registered ranges, not an
external allocator’s own footprint.
Development wire contract#
OrbiTE uses its own pre-release control protocol; see the
wire policy. Integers are
unsigned big-endian and each connection carries one operation. Both headers
begin with the eight-byte ORBITTE\0 magic, a two-byte version, a one-byte
operation or status and one zero reserved byte.
Frame |
Remaining fields |
|---|---|
Request, 52 bytes |
Expected epoch (16), registration ID (8), offset (8), length (8) |
Reply, 36 bytes |
Actual source epoch (16), payload length (8) |
Descriptor payload |
Region count (4), segment capabilities (44, including the eight-byte maximum transfer length), then records ordered by registration ID. Each contains ID (8), base address (8), length (8), permission bits (1), memory type (1), IPC export consent (1), HP-TCP registration flag (1) and MNNVL export location (4) |
The selector guide describes the capability fields; each transfer still needs a grant. The retired provider capability byte is reserved and must be zero; old provider requests are rejected before admission. See the wire policy.
Segment capability bit 7 requires the RDMA capability and identifies a source whose immutable RDMA policy directly registers published GPU ranges. Classic scatter checks it before admitting a remote GPU fragment.
In each region’s flag byte, bit 0 marks HP-TCP registration. Bits 1 and 2 record observed dma-buf and peermem registrations respectively, only for a CUDA region on an RDMA-capable source. Unknown bits are rejected. Bit 7 marks a registration with a scoped transport or known device/NUMA ordinal. All fixed records precede the optional five-byte entries, in flagged record order. Each entry contains a TENT transport ID (one byte) and a big-endian ordinal (four bytes, all ones for unknown). Internal and local-only regions are omitted. A C P2P handle has no supplied epoch. A read-only Describe with zero expected epoch first obtains the source’s identity from its mismatch reply. The handle then issues a normal epoch-checked Describe.
For an engine with SHM enabled, capability bit 5 announces a trailer immediately
after the optional registration profiles, before any HP-TCP endpoint trailer. It starts with
a 32-bit count, a one-byte backend (0 for POSIX, 1 for a configured directory)
and three zero bytes. Each 80-byte record starts with seven ordered 64-bit fields for
registration ID, allocation offset, mapped size, grant slot, device, inode and
PID namespace inode. A 32-bit creator PID, four zero bytes and a 16-byte name
nonce follow. The leaf is orbite-shm-{namespace:016x}-{pid:08x}-{nonce:032x}.
POSIX opens it with a leading slash. The directory backend opens it relative to
the local mount. Record IDs increase and every range must fit the published
object. tests/reference/shm/describe.bin freezes a sample trailer.
Capability bit 6 announces MNNVL. A 24-byte trailer follows the SHM records
when present and precedes the HP-TCP endpoint. It contains the domain (16),
handle kind (1, fabric 0 or POSIX fd 1), EGM enable (1) and six zero bytes.
The per-region final big-endian word is zero for an ordinary registration,
one for a GPU export, or the high bit plus a host NUMA ID for EGM.
tests/reference/mnnvl/describe.bin contains a trailer and an EGM location word.
The control service sends opaque handles only in an authorized transfer grant.
MNNVL reuses the NVLink request, grant and close-receipt layouts with
MNFG and MNFC hello markers for fabric or MNPG and MNPC for POSIX. Grants use the corresponding non-cache marker. Identity binds
the fabric domain and requester process. The 64-byte share field holds
CUDA fabric bytes or a big-endian PID and fd (4 bytes each), PID namespace
inode and process start ticks (8 bytes each), followed by zero padding.
A POSIX import checks the process identity and duplicates the fd through a pidfd.
Operations: 0 describe, 1 TCP READ, 2 TCP WRITE, 3 notify, 4 RDMA READ,
5 RDMA WRITE, 6 sliced RDMA READ and 7 sliced RDMA WRITE (see
RDMA/CUDA). Control RPC notify uses operation 22. Describe requires a zero ID and range. Both notification operations use ID
zero, with offset as the name length and length as the message length; the body contains the UTF-8 name, message and a zero commit marker. The receiver sends final success only after its registered callback accepts the complete message. Permissions: 0 local-only (never
exported), 1 remote read, 2 remote write, 3 read/write. Statuses: 0
success, 1 epoch mismatch, 2 unknown region, 3 permission denied, 4
invalid range, 5 source closed, 6 exhausted, 7 protocol error, 8 internal
error, 9 ready, 10 backend unavailable, 11 remote native backend error and
12 explicit RDMA cache miss. Unknown versions, flags, IDs, enum values,
out-of-bounds ranges and oversized payloads are rejected.
READ: request → success header and the requested bytes. WRITE: request → ready → bytes → final success. Notify: request → ready → body and marker → final success. Each side commits only complete bodies, a dropped reply never authorizes replay, and the descriptor count, per-region metadata and provider trailers are bounded before decoding.
Verification#
From the checkout, with permission to bind loopback sockets:
cargo fmt --check
cargo clippy --locked --all-targets -- -D warnings
cargo test --locked
cargo run --locked --example tcp_roundtrip
cargo run --locked --example external_host
cargo run --locked --example notifications
cargo run --locked --example async_batch
cargo publish --dry-run --locked
Unit tests cover ranges and permissions, epoch parsing, budgets, admission,
close/drain and malformed or fragmented frames. Real TCP controls inject short
READs, incomplete WRITEs, lost ACKs and trickling responses. tests/tcp.rs
covers prevalidation, prefix completion, concurrency and a restarted source
process at the same port (the ignored peer_process_fixture is that gate’s
child, not a skipped case). tests/notifications.rs covers queue bounds,
concurrent senders and pollers, and data completion despite notification
failure. The CPU oracle compares one valid-input trace against
native TENT. CI runs all of this on stable and Rust 1.85; physical two-host and
hardware gates are separate.