Skip to content

Add codec layer for server side handlers#2540

Open
sauravzg wants to merge 7 commits intohyperium:masterfrom
sauravzg:codec_handlers
Open

Add codec layer for server side handlers#2540
sauravzg wants to merge 7 commits intohyperium:masterfrom
sauravzg:codec_handlers

Conversation

@sauravzg
Copy link
Collaborator

@sauravzg sauravzg commented Mar 6, 2026

Implement Core Serialization Infrastructure and Generic Byte Stream Handlers

Description

This sits on top of #2539 and only the last two commits should be in scope of review.

This pull request establishes the core serialization and compression infrastructure within the codec module and introduces the generic byte stream handler abstractions required to seamlessly bridge raw network buffers with strongly-typed message handlers.

Key Architectural Additions

  • Zero-Copy Serialization API: Introduces a high-performance serialization ecosystem leveraging gRPC's Buf and BufMut extensions. This API is explicitly designed to support and optimize zero-copy operations over chains of non-contiguous memory buffers.
  • Global Codec & Compression Registry: Implements a lock-free global registry supporting Protobuf and Prost serialization, alongside integrated compression schemes including Gzip, Deflate, and Zstd.
  • GenericByteStreamMethodHandler Trait: Defines a standardized interface for server-side processing of raw gRPC byte streams (ReqB: Buf), creating a distinct abstraction layer for transport-level buffering.
  • CodecMessageStreamHandler Adapter: Introduces an adapter that implements GenericByteStreamMethodHandler while wrapping an underlying, strongly-typed MessageStreamHandler. This component bridges the transport and application layers by transparently managing the encoding and decoding of message streams utilizing the registered codecs.

Known gaps

  • Performance: This currently results in a copy for staging protobuf API. This is a small fix to move from BytesMut to vec[BytesMut]
  • Performance: Buffer batching is not implemented. This is likely to be complex and might be better to implement at the transport layer
  • Performance: There's a minor use of enum to avoid dynamic dispatch which can be eliminated but needs codec API modifications or additional trait constraints on the ByteStreamhandler

sauravzg added 7 commits March 6, 2026 13:40
These traits are supposed to provide abstractions over standard references and protobuf views and muts to eliminate hard dependency on protobuf

The biggest challenge in the message module is to solve the long-standing difficulty of reborrowing mutable message views.

The Problem: Reborrowing GATs
When working with Generic Associated Types (GATs) for mutable views (e.g., type Mut<'a>), we frequently need to "reborrow" a view to pass it to a child function without consuming the original. However, traditional approaches fail here:
1. Foreign Types: We cannot add a .reborrow() method directly to protobuf::Mut as it is a foreign type.
2. HRTB Hell: Defining a separate Reborrow trait and requiring for<'a> T::Mut<'a>: Reborrow leads to complex Higher-Ranked Trait Bound errors (e.g., "implementation is not general enough") that are extremely difficult to satisfy in Rust's current type system.
The Solution: Static Operator Pattern
We bypass these issues by moving the reborrowing logic into the provider trait itself as a static operator.
* Introduced AsMut::reborrow_view<'a, 'b>(view: &'b mut Self::Mut<'a>) -> Self::Mut<'b>.
* This bundles the reborrowing logic with the type definition, avoiding the need for extra traits or complex HRTB bounds on the view type itself.
Changes:
* as_mut.rs: Defined AsMut with the reborrow_view static operator. Implemented it for protobuf::Message and added tests verifying that views can be reborrowed and mutated without consuming the original.
* as_view.rs: Defined AsView for immutable views.
This introduces a push-based stream API (`PushStream`) for gRPC server responses, modeled after internal C++ patterns to address limitations in the current pull-based model.

Key architectural decisions:
* **Push vs. Pull:** Standard streams yield `Result<Message, Error>`, but gRPC strictly requires messages followed by a terminal error. A unified async execution context also prevents partial consumption bugs and eliminates the performance overhead of constantly wrapping/unwrapping items in a `Result`.
* **Generics over Dynamic Dispatch:** Generics are used instead of `dyn` APIs (like `RecvStream`) to enable nightly specialization. This provides power users with a workaround for Rust's limited native RTTI without requiring custom vtables.
* **Struct Wrappers over Raw Traits:** The underlying traits are encapsulated in struct wrappers to future-proof the API. This gives the gRPC layer the flexibility to transparently manage features like task locals later without breaking public stability.

Known Pitfalls:
* **Stateless Consumer API:** The consumer API is completely stateless and is never explicitly made aware of the end of the stream. Consequently, there is currently no zero-cost or trivial way to implement asynchronous cleanups when the stream concludes.
Introduce the public API traits for server-side gRPC methods (`UnaryMethod`, `ClientStreamingMethod`, `ServerStreamingMethod`, `BidiStreamingMethod`).
These traits are designed to be the primary interface for codegen.

Currently, the API models a handler as an async function with a (requeststream, responsesink) interface.

Note on design:
- The public API is scoped to access request and response messages only.
- Modifications to headers and trailers are intended to be handled via "interceptor" APIs, following the Java gRPC model.
- Contextual information is expected to be accessed via a read-only local context.

Known pitfalls:
- Unary inputs are references, implying that they will not trivially work with spawns. While responses can be shallow copied due to them being mut refs, requests will have to be copied if they need to be sent to a different task.
…c API

This change introduces data objects for the internal generic API.
The primary objects are
- RequestStream: which is essentially metadata + input stream
- Lazy: It's a delayed computation struct that is responsible for holding a set of lazy transformations to be performed on a mutable ref to an object. This is needed to allow laze deserialization for cases like arena
- ResponseWriter: A compile time state machine which guarantees the following potential states. "Trailer only" or "Metadata , Message* , Trailers" . This is achieved via compile time state transitions ensuring that's it's impossible to generate an incorrect order. The pain of having to implement these state transitions is simplified via extension API to to trivial maps.
- MessageWrappers: Outgoing and Incoming wrappers to allow tweaking per read(input) and write options(output).


- Known Pitfalls
  - Since, `Lazy` transformations are evaluated lazily, it's difficult to model complex transformations that need shared state without additional cost.
…r handling all types of gRPC methods on the server side, along with adapters for specific method types.

Key changes:
- Introduce `StreamingMethodHandler` trait to provide a common interface for method execution.
- Add `UnaryMethodAdapter`, `ServerStreamingAdapter`, `ClientStreamingAdapter`, and `BidiStreamingAdapter` to bridge specific method traits to the unified handler.
- This generic API has intentionally been kept private due to the pitfalls below.


Known pitfalls:
- Lazy and ResponseHolder are not very great abstractions to workaround the fact that we may not always have ownership of the underlying message.
- Asymmetry between the API: Request is lazy while response is in a holder.
- The API is very generic heavy, but this should be acceptable at least until we talk about interceptors. C++ has a similar setup with Generic handler until being type erased in the codec layer.
…the codec module.

Supports Protobuf and Prost, alongside Gzip, Deflate, and Zstd compression via a global lock-free registry.

The serialization API is designed to allow supporting zero copy improvements using grpc's Buf and BufMut extensions to model chain of non contiguos buffers.
…r adapter

This commit introduces the `GenericByteStreamMethodHandler` trait to provide a standard interface for handling raw gRPC byte streams (`ReqB: Buf`).

Additionally, it adds the `CodecMessageStreamHandler`, which bridges the gap between raw byte streams and strongly-typed gRPC messages. It acts as an adapter that wraps an underlying `MessageStreamHandler` and implements `GenericByteStreamMethodHandler`, transparently managing the encoding and decoding of message streams using standard Codecs.

Finally, the `method_handler` module is updated to export these new components and introduces a `CodecRespB` type alias for the default codec response body type.
@sauravzg sauravzg requested a review from dfawley March 6, 2026 15:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant