Skip to content

Commit 50e2b56

Browse files
pranavtbhatmeta-codesync[bot]
authored andcommitted
Borrow opaque C++ messages in Rust
Summary: Add `BorrowedMessageAdapter` so synchronous Rust handlers can inspect callback-scoped views of opaque inline C++ messages without taking them out of `TypeErasedBox`. The original box remains intact and can be forwarded unchanged. Reviewed By: robertroeser Differential Revision: D116714816 fbshipit-source-id: fea6937a4d3d2f3822b6fc7b66a4003848c28814
1 parent 1367be2 commit 50e2b56

4 files changed

Lines changed: 59 additions & 3 deletions

File tree

thrift/lib/rust/channel_pipeline/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ impl RustHandler for CountingHandler {
4646
}
4747
```
4848

49+
Opaque inline C++ messages can be inspected without taking them out of the
50+
pipeline box. Implement `BorrowedMessageAdapter`, borrow the callback-scoped
51+
view with `msg.borrow::<Adapter>()`, drop the view, and call
52+
`ctx.forward_read(msg)` or `ctx.forward_write(msg)` to forward the original box
53+
unchanged. This path performs no message allocation or copy.
54+
4955
Expose a factory from the handler's own CXX bridge:
5056

5157
```rust

thrift/lib/rust/channel_pipeline/src/erased.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,32 @@ use crate::adapter::RustMessageAdapter;
4545
use crate::ffi::ffi;
4646
use crate::ffi::ffi::TypeErasedBox;
4747

48+
/// Adapter for inspecting a C++ pipeline message without taking it out of its
49+
/// [`TypeErasedBox`].
50+
///
51+
/// This is the zero-allocation path for opaque inline C++ messages. The view is
52+
/// tied to the callback-scoped box borrow and must not be retained. After the
53+
/// view is dropped, the handler can forward the original box unchanged.
54+
///
55+
/// # Safety
56+
///
57+
/// Implementations must return a view of the exact C++ type checked by
58+
/// `holds`, and the view must not outlive or move the value in `message`.
59+
pub unsafe trait BorrowedMessageAdapter {
60+
type View<'a>
61+
where
62+
Self: 'a;
63+
64+
fn holds(message: &TypeErasedBox) -> bool;
65+
66+
/// # Safety
67+
///
68+
/// `message` must currently contain the exact C++ type recognized by
69+
/// [`Self::holds`]. The contained value must not be moved, replaced, or
70+
/// otherwise invalidated for the lifetime of the returned view.
71+
unsafe fn borrow<'a>(message: Pin<&'a mut TypeErasedBox>) -> Self::View<'a>;
72+
}
73+
4874
/// Provides the dev-mode type check used by [`RustTypeErasedBox::take`].
4975
/// Implemented once per message type via a tiny per-type C++ thunk that compares
5076
/// `typeid` — exactly like the per-type C++ `RustMessageAdapter<T>`. In release
@@ -121,6 +147,23 @@ impl<'a> RustTypeErasedBox<'a> {
121147
M::from_cpp(cpp)
122148
}
123149

150+
/// Borrow a typed view of the message while leaving the box intact.
151+
///
152+
/// Drop the returned view before forwarding `self` with
153+
/// [`CallbackContext::forward_read`](crate::CallbackContext::forward_read)
154+
/// or [`CallbackContext::forward_write`](crate::CallbackContext::forward_write).
155+
pub fn borrow<M: BorrowedMessageAdapter>(&mut self) -> M::View<'_> {
156+
assert!(
157+
M::holds(self.inner.as_ref().get_ref()),
158+
"RustTypeErasedBox::borrow: box does not hold the requested type"
159+
);
160+
// SAFETY: the unconditional `M::holds` check establishes that the box
161+
// contains the adapter's exact C++ type. The returned view is tied to
162+
// this exclusive pinned borrow, so the value cannot be moved or
163+
// replaced while the view is live.
164+
unsafe { M::borrow(self.inner.as_mut()) }
165+
}
166+
124167
/// True if the box currently holds no value.
125168
pub fn is_empty(&self) -> bool {
126169
ffi::rust_teb_is_empty(&self.inner)

thrift/lib/rust/channel_pipeline/src/ffi.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,8 @@ pub(crate) mod ffi {
246246
}
247247
}
248248

249+
pub use ffi::TypeErasedBox as FfiTypeErasedBox;
250+
249251
use crate::context::CallbackContext;
250252
use crate::erased::RustTypeErasedBox;
251253
use crate::ffi::ffi::TypeErasedBox;

thrift/lib/rust/channel_pipeline/src/lib.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@
148148
//! | [`PipelineError`] | Owned Rust error converted to `folly::exception_wrapper` |
149149
//! | [`HandlerResult`] | FFI-stable return value (`Success`, `Backpressure`, `Error`) |
150150
//! | [`RustMessageAdapter`] | Trait describing how a message type crosses the FFI boundary |
151+
//! | [`BorrowedMessageAdapter`] | Callback-scoped view of an opaque inline C++ message without taking it from the box |
151152
//! | [`CoroReadHandle`] / [`CoroWriteHandle`] / [`CoroExceptionHandle`] | Adapters from a pipeline callback to an `async` handler body |
152153
//! | [`ContextReadMessage`] / [`ContextWriteMessage`] | Message traits describing how to resume a captured continuation |
153154
//! | [`ErasedCheck`] | Dev-build type check for erased message recovery |
@@ -217,9 +218,11 @@
217218
//!
218219
//! # Message adapter extension
219220
//!
220-
//! Any type implementing [`RustMessageAdapter`] can flow through a Rust
221-
//! handler; the message type itself is the identity, so there is no numeric
222-
//! type id and no central registry. A future Rust handler at the framing layer
221+
//! An owned type implementing [`RustMessageAdapter`] can flow through a Rust
222+
//! handler. An opaque inline C++ type can instead implement
223+
//! [`BorrowedMessageAdapter`] and forward the original box unchanged after the
224+
//! view is dropped. The message type itself is the identity, so there is no
225+
//! numeric type id and no central registry. A future Rust handler at the framing layer
223226
//! that needs `ParsedFrame`/`ComposedFrame` should use an opaque
224227
//! `UniquePtr<ParsedFrame>` boxed via CXX methods (never mirror the C++ layout)
225228
//! or serialize via `cxx-thrift-utils`. The single-message-type-per-layer
@@ -259,8 +262,10 @@ pub use coro_handler::ContextWriteMessage;
259262
pub use coro_handler::CoroExceptionHandle;
260263
pub use coro_handler::CoroReadHandle;
261264
pub use coro_handler::CoroWriteHandle;
265+
pub use erased::BorrowedMessageAdapter;
262266
pub use erased::ErasedCheck;
263267
pub use erased::RustTypeErasedBox;
268+
pub use ffi::FfiTypeErasedBox;
264269
pub use ffi::RustHandlerOpaque;
265270
pub use ffi::box_handler;
266271
pub use handler::HandlerResult;

0 commit comments

Comments
 (0)