17 Current Limitations
trev edited this page 2026-07-16 03:02:35 -04:00

Current Limitations

TrevRPC is a very early work in progress. This page documents current boundaries so users know what to expect and contributors know where the sharp edges are.

Protocol Compatibility

TrevRPC is not compatible with Connect, gRPC, or gRPC-Web on the wire. It uses a custom protobuf protocol over native QUIC, ordinary HTTP/3, or WebTransport.

This means:

  • Existing gRPC or Connect clients cannot call TrevRPC servers without a gateway.
  • TrevRPC clients cannot call existing gRPC or Connect servers directly.
  • Protobuf service definitions are portable, but generated transport bindings are TrevRPC-specific.

Transport Coverage

Current runtime transport support includes native QUIC, ordinary HTTP/3, and WebTransport:

  • Rust native QUIC uses Quinn.
  • Rust ordinary HTTP/3 and combined WebTransport serving use h3, h3-quinn, and h3-webtransport on Quinn; native WebTransport clients use web-transport-quinn.
  • Go native QUIC uses quic-go.
  • Go WebTransport uses webtransport-go.
  • C native QUIC, HTTP/3, and WebTransport use MsQuic.
  • JavaScript browser clients use the browser WebTransport API and protobuf.js.
  • JavaScript Node native client/server support uses the C/MsQuic addon through trevrpc-js/node.
  • Kotlin Netty provides native QUIC and ordinary HTTP/3 clients plus one-listener native QUIC, HTTP/3, and server-side WebTransport serving.
  • Kotlin Cronet provides an Android-oriented ordinary HTTP/3 client around an application-injected CronetEngine.
  • Kotlin does not currently provide a WebTransport client.

TLS and Channels

Routine applications use the long-lived channel APIs described in Connection Lifecycle: Rust trevrpc::client::Channel, Go *trevrpc.Channel, C trevrpc_channel*, Kotlin RpcChannel, and JavaScript Channel. Applications still own identity, trust, SNI, hostname, and mTLS policy. Cronet applications additionally own provider and engine lifecycle.

This keeps the runtime small, but users must correctly configure:

  • Certificates and trust roots.
  • Server name verification.
  • Native QUIC ALPN trevrpc/1, ordinary HTTP/3, or WebTransport negotiation.
  • QUIC transport limits outside the TrevRPC server options.

Channel reconnection restores availability for future calls only. Every RPC remains pinned to one ready connection generation and fails if that generation dies; TrevRPC does not retry, replay, resume, or move it. New calls fail immediately while reconnecting unless the application first waits for readiness. Cronet and other provider-controlled path migration or TLS ticket behavior remain outside TrevRPC's lifecycle guarantees.

Response Metadata

Wire messages support response metadata and status-frame metadata. The high-level generated service APIs currently focus on request and response bodies plus statuses. Generated handler signatures do not expose a first-class way for service implementations to attach response metadata.

Generator Limitations

Rust generator notes:

  • It emits one generated file per protobuf package.
  • It supports runtime_path, file_suffix, and package_root options.
  • If a protobuf type cannot be resolved, current code falls back to the final type name segment instead of reporting a generator error.

Go generator notes:

  • It emits output only for requested proto files that contain services.
  • It supports runtime_import and file_suffix options.
  • It emits a helper named mergeTrevrpcCallOptions in each generated file, which can conflict when multiple generated service files are placed in the same Go package.
  • It does not currently expose protoc path-mode options or service/method-specific options.

JavaScript generator notes:

  • It emits output only for requested proto files that contain services.
  • It embeds protobuf.js reflection descriptors in each generated file.
  • It emits companion .d.ts declaration files for TypeScript clients and typed Node server helpers.
  • It supports runtime_import and file_suffix options.

Kotlin generator notes:

  • It emits output only for requested proto files that contain services.
  • It uses protobuf Java message classes rather than defining protobuf messages itself.
  • It supports runtime_package and file_suffix options.
  • Unresolved protobuf types are generator errors rather than fallback names.

Streaming Transport Requirement

Rust's RpcTransport trait has a default streaming_call implementation that returns Unimplemented. Streaming RPCs require a transport implementation that explicitly supports streaming. The Quinn transport does.

Go's Transport interface requires both unary and streaming methods.

Kotlin's RpcTransport interface requires both unary and openStream. The Netty native QUIC, Netty HTTP/3, and Cronet HTTP/3 transports implement both methods. Kotlin WebTransport is currently server-only.

Browser JavaScript is WebTransport-only because browsers do not expose native QUIC. Node native QUIC client/server support is available through the addon-backed trevrpc-js/node API.

Protocol Stability

The wire version is currently 1, and the wire compatibility policy is documented in Protocol and Wire Format. The project is still pre-stable, so generated API shapes and non-wire runtime APIs may change before a stable release.

Operational Maturity

The runtime has useful production-facing hooks such as deadlines, metadata validation, authorization, metrics, frame limits, stream limits, and graceful shutdown. It does not yet have a broad ecosystem of deployment guides, conformance tests, load-test results, gateway integrations, or stable API compatibility guarantees.

Current hardening boundaries:

  • C accepted-stream handling now uses the bounded server worker pool, but deployments should still size worker counts, queue capacity, stream concurrency, and initial request timeout together.
  • Larger native QUIC buffering and flow-control settings are not production defaults. The experimental C throughput-1m profile was removed, and no named large-buffer profile is exposed. Advanced callers can still configure raw low-level MsQuic fields explicitly, but MsQuic receive windows do not bound the C application receive queue and the normal defaults remain unchanged.
  • C native MsQuic direct sends may borrow frame body memory. Frame header, encoded protobuf, and body memory passed to MsQuic must remain alive until SEND_COMPLETE; runtime direct unary/status sends wait or close-drain before freeing borrowed parts.
  • Rust Quinn, HTTP/3, and WebTransport frame encoding and decoding share an internal framed-stream helper, while transport modules still own their distinct finish, reset, cancellation, terminal-status, and drain behavior. Current h3 dependencies do not expose response-side STOP_SENDING as a server future after request FIN, so response-only cancellation is observed on the next response write, deadline, connection close, or shutdown.
  • JavaScript native inbound response and stream bodies can use external ArrayBuffer owner transfer when the C owner allocation is available. Outbound bodies are always copied into native-owned storage. A measured borrowed-send implementation reduced Node CPU per operation by approximately 16-20% for 1-3 MiB payloads at concurrency 16/64 and reduced peak RSS, but it was removed because Node-API references cannot prevent mutation, detachment, transfer, or resize of a retained backing store while MsQuic owns its pointer. Safely pinning arbitrary caller-owned JavaScript backing stores is therefore not supported.
  • JavaScript native operations use a binding-owned completion source. Same-stream and same-call outbound operations use an invocation-ordered FIFO over nonblocking serialization, while receives remain outside that FIFO so duplex traffic can progress. Receive and contention retries use a monotonic deadline heap with exponential 0.25-8 ms backoff, and idle budgets include time spent waiting for serialization. High-concurrency profiling passed duplex traffic through the server's configured concurrency-64 application limit. This is scalable relative to the rejected linear worker-local 1 ms sleep, but it remains readiness polling rather than a transport event callback, so very large pending sets still perform one readiness check per scheduled retry.
  • Browser WebTransport support depends on real browser APIs. Chromium interoperability is checked against every server implementation, while Firefox and WebKit coverage remains limited by their WebTransport availability.
  • Kotlin WebTransport support uses an internal adapter tied to Netty 4.2.16 because the required HTTP/3 stream initializer is package-private. Upgrading Netty requires running the adapter tests and Chromium WebTransport benchmark smoke check before changing the pin.
  • The public Kotlin Netty client API does not expose arbitrary raw QUIC frames. Malformed initial-frame behavior is covered by focused runtime, codec, and adapter tests.
  • Android Cronet instrumentation requires a configured device, provider, and HTTP/3 endpoint; JVM state-machine tests do not replace provider/device coverage.