Skip to content

Raw client

RpcClient is the blocking native client for VGI-RPC's ordered Arrow IPC transports. It supports a directly spawned worker, caller-owned Arrow streams, Unix sockets, and trusted-network TCP.

#include <vgi_rpc/client.h>

vgi_rpc::RpcClientOptions options;
options.protocol = "MyService.v1";  // the routing key; required for application calls
auto client = vgi_rpc::RpcClient::spawn({"python", "worker.py"}, options);
auto service = client.describe();
auto result = client.call_unary("add", params);

auto producer = client.open_producer("scan", scan_params);
while (auto batch = producer.tick()) {
    consume(batch->batch);
}

The client is dynamic and schema-first: callers build exact Arrow record batches and receive owned AnnotatedBatch values. list_protocols(), describe_protocol(name) and describe() ask vgi_rpc.Reflection.v1 over the client's own connection, which they never close; a server without reflection throws ReflectionNotSupportedError (see Discovering protocols). Descriptions are validated rather than returned as a partial model when the peer supplies malformed schemas or duplicate methods. RpcClientOptions::protocol is the routing key the peer hosts its methods under. Every application request carries it (WIRE_PROTOCOL ยง3.1); an application call on a client without one throws std::invalid_argument before anything is sent. Reflection and reserved __name__ methods need none.

Transports

ClientTransport::spawn() executes the argument vector directly, never via a shell. On POSIX it uses posix_spawnp; on Windows it uses CreateProcessW with Windows-compatible argument quoting and binary inherited pipes. Closing a subprocess first permits cooperative EOF shutdown, then escalates through the configured bounded termination periods.

connect_unix() and connect_tcp() are currently POSIX-only, matching the raw server transports. Socket connect, read, and write bounds are configured with SocketTransportOptions. The connect deadline applies independently to each resolved address; platform DNS resolution itself is outside that timer.

Raw TCP has no authentication or TLS and must be confined to a trusted network. Use HttpClient when either security property is required.

Streams and ownership

Only one operation may use a raw connection at a time. A live ClientStream reserves it until the stream finishes, is explicitly closed/cancelled, or is destroyed. Producer tick() and bidirectional exchange() follow the raw lockstep protocol; raw streams are their own continuation handle and do not have stateless HTTP resume tokens.

close() and cancel() perform the protocol drain and can block up to the transport's configured I/O deadline. Destruction never attempts an unbounded drain: abandoning a live stream aborts the underlying connection. Returned Arrow batches own their backing buffers and remain valid after later reads.

Shared memory

Set RpcClientOptions::shared_memory_bytes or call enable_shared_memory() to negotiate POSIX shared-memory transport through __transport_options__. Negotiation is capability-gated. Large exchange inputs and returned pointer batches are resolved transparently, and shared_memory_live_allocations() is available for lifecycle diagnostics. Shared memory is disabled by default.

External storage pointers

Externalization is not an HTTP feature. Any transport that carries record batches carries pointer batches, so a peer serving a byte stream may replace an output cycle -- or a stream header -- with a zero-row batch bearing vgi_rpc.location. RpcClient resolves those on the header substream and the data stream alike, dispatching every log batch bundled into the fetched object and returning the inner data batch's metadata plus the reader-stamped vgi_rpc.location.source / vgi_rpc.location.fetch_ms provenance.

RpcClientOptions::external_http carries the policy, and defaults to the same one HttpClient uses: HTTPS only, every resolved address globally routable, bounded redirects, and independent encoded/decoded byte caps. Set it to std::nullopt for a deployment whose byte-stream client must make no outbound HTTP request of its own; a pointer batch is then refused rather than fetched.

Deliberate scope

The raw client rejects stateless-resume envelopes; those belong to the HTTP client, because a raw stream is its own continuation handle. Raw streams currently have no response-byte cap, and subprocess pipes do not provide a per-read deadline, although subprocess abandonment uses bounded shutdown. Windows subprocess support is native; Windows Unix/TCP raw sockets remain unavailable with the current server.