@@ -25,6 +25,7 @@ use std::rc::Rc;
2525
2626use crate :: adapter:: BytesPtr ;
2727use crate :: adapter:: RustMessageAdapter ;
28+ use crate :: erased:: BorrowedMessageAdapter ;
2829use crate :: event_base:: EventBaseTask ;
2930use crate :: event_base:: FirstPoll ;
3031use crate :: ffi:: ffi:: FfiCallbackContext ;
@@ -135,6 +136,74 @@ impl Drop for ContextHandle {
135136 }
136137}
137138
139+ /// Move-only ownership of an inbound message suspended at its pipeline
140+ /// position.
141+ ///
142+ /// The original type-erased C++ message remains intact inside this token. It
143+ /// may be inspected or mutated on the originating EventBase and then resumed
144+ /// exactly once. Dropping the token cancels the read. Resume and cancellation
145+ /// are safe from any thread; native code performs delivery and destruction on
146+ /// the EventBase that owns the pipeline.
147+ pub struct DeferredRead {
148+ storage : MaybeUninit < usize > ,
149+ _not_sync : PhantomData < Cell < ( ) > > ,
150+ }
151+
152+ // SAFETY: the native token has unique ownership of both the message and its
153+ // pipeline guard. Cross-thread resume and destruction consume the token and
154+ // enqueue it onto the originating EventBase before touching or destroying the
155+ // EventBase-owned values.
156+ unsafe impl Send for DeferredRead { }
157+
158+ impl DeferredRead {
159+ /// Borrow a typed view of the intact message on its originating EventBase.
160+ ///
161+ /// Returns `None` off the owning EventBase. The returned borrow prevents
162+ /// this token from being resumed or dropped while the message is in use.
163+ pub fn borrow < M : BorrowedMessageAdapter > ( & mut self ) -> Option < M :: View < ' _ > > {
164+ // SAFETY: this token uniquely owns one live native deferred-read token.
165+ // Native code returns its message only when called on the owning
166+ // EventBase, and the resulting borrow is tied to `&mut self`.
167+ let message =
168+ unsafe { crate :: ffi:: ffi:: deferred_read_message ( self . storage . as_mut_ptr ( ) . cast ( ) ) } ;
169+ if message. is_null ( ) {
170+ return None ;
171+ }
172+ // SAFETY: native code returned the address of the live message owned by
173+ // this token. `&mut self` guarantees exclusive access for the lifetime
174+ // of the pinned borrow and the token cannot move or be consumed then.
175+ let message = unsafe { & mut * message } ;
176+ assert ! (
177+ M :: holds( message) ,
178+ "DeferredRead::borrow: box does not hold the requested type"
179+ ) ;
180+ // SAFETY: the unconditional `M::holds` check establishes the adapter's
181+ // exact C++ type, and the view remains tied to this exclusive borrow.
182+ Some ( unsafe { M :: borrow ( Pin :: new_unchecked ( message) ) } )
183+ }
184+
185+ /// Resume the original inbound message from its captured pipeline
186+ /// position. Delivery is suppressed if the pipeline has closed.
187+ pub fn resume ( self ) {
188+ let mut deferred = std:: mem:: ManuallyDrop :: new ( self ) ;
189+ // SAFETY: `deferred` owns one live token and ManuallyDrop prevents its
190+ // destructor from consuming that token a second time.
191+ unsafe {
192+ crate :: ffi:: ffi:: resume_deferred_read ( deferred. storage . as_mut_ptr ( ) . cast ( ) ) ;
193+ }
194+ }
195+ }
196+
197+ impl Drop for DeferredRead {
198+ fn drop ( & mut self ) {
199+ // SAFETY: `DeferredRead` uniquely owns one token initialized by
200+ // `CallbackContext::defer_read`; native code consumes it exactly once.
201+ unsafe {
202+ crate :: ffi:: ffi:: destroy_deferred_read ( self . storage . as_mut_ptr ( ) . cast ( ) ) ;
203+ }
204+ }
205+ }
206+
138207/// Borrowed, callback-scoped view of the live C++ pipeline context.
139208///
140209/// Each data or lifecycle callback receives an exclusive mutable reference to a
@@ -218,6 +287,20 @@ impl<'callback> CallbackContext<'callback> {
218287 } ) ;
219288 }
220289
290+ /// Poll a future now and capture the pipeline continuation only if it suspends.
291+ ///
292+ /// The future is first placed at its final pinned address and polled inline on
293+ /// the current EventBase callback. If it is ready, `ready` runs immediately
294+ /// with this borrowed context and its [`HandlerResult`] becomes the callback's
295+ /// result; no [`ContextHandle`] is created. If it is pending, the task takes a
296+ /// new one-shot `ContextHandle`, later polls remain on the same EventBase, and
297+ /// `complete` receives that handle with the output. The current callback then
298+ /// returns [`HandlerResult::Success`] because ownership of its in-flight work
299+ /// has moved into the task.
300+ ///
301+ /// Panics are contained by [`EventBaseTask`]. A panic does not invoke either
302+ /// completion callback and is reported here as [`HandlerResult::Success`] so
303+ /// unwinding never crosses the C++ FFI boundary.
221304 pub ( crate ) fn spawn_deferred < T , Fut , Ready , Complete > (
222305 & mut self ,
223306 future : Fut ,
@@ -257,6 +340,67 @@ impl<'callback> CallbackContext<'callback> {
257340 handle
258341 }
259342
343+ /// Suspend the current inbound message without unpacking or copying it.
344+ ///
345+ /// Returns `None` if this callback has no message, the message is empty, or
346+ /// it was already forwarded. Dropping the returned token cancels delivery;
347+ /// [`DeferredRead::resume`] continues it from this handler's exact position.
348+ pub fn defer_read (
349+ & mut self ,
350+ message : crate :: erased:: RustTypeErasedBox < ' _ > ,
351+ ) -> Option < DeferredRead > {
352+ let _ = & message;
353+ let mut deferred = DeferredRead {
354+ storage : MaybeUninit :: uninit ( ) ,
355+ _not_sync : PhantomData ,
356+ } ;
357+ // SAFETY: storage is one pointer-aligned word and remains
358+ // exclusively owned by `deferred`. On false, native code constructed
359+ // nothing, so mem::forget prevents running a destructor on garbage.
360+ let initialized = unsafe {
361+ self . inner
362+ . as_mut ( )
363+ . init_deferred_read ( deferred. storage . as_mut_ptr ( ) . cast ( ) )
364+ } ;
365+ if initialized {
366+ Some ( deferred)
367+ } else {
368+ std:: mem:: forget ( deferred) ;
369+ None
370+ }
371+ }
372+
373+ /// Suspend the current inbound message while a future runs on this
374+ /// pipeline's EventBase.
375+ ///
376+ /// Completion receives the intact message token and may borrow, mutate,
377+ /// resume, or cancel it. The task owns the token throughout suspension, so
378+ /// panic or task destruction safely cancels the read.
379+ ///
380+ /// If the current callback has no live message, the message is empty, or it
381+ /// was already forwarded, this returns HandlerResult::Error without
382+ /// polling future or invoking complete.
383+ pub fn spawn_deferred_read < T , Fut , Complete > (
384+ & mut self ,
385+ message : crate :: erased:: RustTypeErasedBox < ' _ > ,
386+ future : Fut ,
387+ complete : Complete ,
388+ ) -> HandlerResult
389+ where
390+ T : Send + ' static ,
391+ Fut : Future < Output = T > + Send + ' static ,
392+ Complete : FnOnce ( DeferredRead , T ) + Send + ' static ,
393+ {
394+ let Some ( deferred) = self . defer_read ( message) else {
395+ return HandlerResult :: Error ;
396+ } ;
397+ let event_base = self . inner . as_ref ( ) . get_ref ( ) . event_base ( ) ;
398+ EventBaseTask :: start ( event_base, async move {
399+ complete ( deferred, future. await ) ;
400+ } ) ;
401+ HandlerResult :: Success
402+ }
403+
260404 /// Forward the inbound buffer downstream and return the result.
261405 ///
262406 /// The buffer is moved into the C++ message box and forwarded via
0 commit comments