@@ -54,6 +54,9 @@ impl From<std::io::Error> for ConnectionError {
5454 }
5555}
5656
57+ /// Result from a completed streaming handler: (request_id, serialized_result).
58+ type StreamingResult = ( u64 , Result < Vec < u8 > , String > ) ;
59+
5760/// A live connection with completed Hello exchange.
5861///
5962/// Generic over the transport type `T` which must implement [`MessageTransport`].
@@ -70,6 +73,10 @@ pub struct Connection<T> {
7073 in_flight_requests : HashSet < u64 > ,
7174 #[ allow( dead_code) ]
7275 our_hello : Hello ,
76+ /// Channel for receiving completed streaming handler results.
77+ /// Spawned tasks send (request_id, result) when they complete.
78+ streaming_results_tx : tokio:: sync:: mpsc:: Sender < StreamingResult > ,
79+ streaming_results_rx : tokio:: sync:: mpsc:: Receiver < StreamingResult > ,
7380}
7481
7582impl < T > Connection < T > {
@@ -211,6 +218,27 @@ where
211218 tokio:: select! {
212219 biased;
213220
221+ // Handle completed streaming handlers
222+ Some ( ( request_id, result) ) = self . streaming_results_rx. recv( ) => {
223+ let response_payload = result. map_err( ConnectionError :: Dispatch ) ?;
224+
225+ // Flush any outgoing stream data (Data/Close) BEFORE Response.
226+ // This ensures the client receives all streamed data before the
227+ // Response that signals call completion.
228+ // r[impl streaming.flush-before-response] - Stream data sent before Response.
229+ self . flush_outgoing( ) . await ?;
230+
231+ // r[impl streaming.call-complete] - Call completes when Response sent.
232+ // r[impl streaming.lifecycle.response-closes-pulls] - Pull streams close with Response.
233+ let resp = Message :: Response {
234+ request_id,
235+ metadata: Vec :: new( ) ,
236+ payload: response_payload,
237+ } ;
238+ self . io. send( & resp) . await ?;
239+ self . in_flight_requests. remove( & request_id) ;
240+ }
241+
214242 // Prioritize incoming messages over outgoing flush
215243 result = self . io. recv_timeout( Duration :: from_secs( 30 ) ) => {
216244 let msg = match result {
@@ -295,35 +323,50 @@ where
295323 }
296324
297325 // Dispatch to service - use streaming dispatch if method has Push/Pull args
298- let response_payload = if dispatcher. is_streaming ( method_id) {
299- dispatcher
300- . dispatch_streaming ( method_id, payload, & mut self . stream_registry )
301- . await
302- . map_err ( ConnectionError :: Dispatch ) ?
326+ if dispatcher. is_streaming ( method_id) {
327+ // For streaming methods, we need to continue processing messages
328+ // (Data, Close) while the handler runs. The handler reads from
329+ // Pull<T> which is backed by an mpsc channel that we route to.
330+ //
331+ // dispatch_streaming registers streams synchronously, then returns
332+ // a future. We spawn that future as a task so the message loop
333+ // can continue processing Data messages.
334+ let handler_fut = dispatcher. dispatch_streaming (
335+ method_id,
336+ payload,
337+ & mut self . stream_registry ,
338+ ) ;
339+
340+ // Spawn the handler as a task that sends its result to our channel
341+ let results_tx = self . streaming_results_tx . clone ( ) ;
342+ tokio:: spawn ( async move {
343+ let result = handler_fut. await ;
344+ // Send result to the connection's run loop
345+ // Ignore send error if connection closed
346+ let _ = results_tx. send ( ( request_id, result) ) . await ;
347+ } ) ;
303348 } else {
304- dispatcher
349+ let response_payload = dispatcher
305350 . dispatch_unary ( method_id, & payload)
306351 . await
307- . map_err ( ConnectionError :: Dispatch ) ?
308- } ;
309-
310- // r[impl core.call] - Callee sends Response for caller's Request.
311- // r[impl core.call.request-id] - Response has same request_id.
312- // r[impl unary.complete] - Send Response with matching request_id.
313- // r[impl unary.lifecycle.single-response] - Exactly one Response per Request.
314- // r[impl unary.request-id.in-flight] - Request no longer in-flight after Response.
315- // r[impl streaming.call-complete] - Call completes when Response sent.
316- // r[impl streaming.lifecycle.response-closes-pulls] - Pull streams close with Response.
317- let resp = Message :: Response {
318- request_id,
319- metadata : Vec :: new ( ) ,
320- payload : response_payload,
321- } ;
322- self . io . send ( & resp) . await ?;
323- self . in_flight_requests . remove ( & request_id) ;
324-
325- // Flush any outgoing stream data that handlers may have queued
326- self . flush_outgoing ( ) . await ?;
352+ . map_err ( ConnectionError :: Dispatch ) ?;
353+
354+ // r[impl core.call] - Callee sends Response for caller's Request.
355+ // r[impl core.call.request-id] - Response has same request_id.
356+ // r[impl unary.complete] - Send Response with matching request_id.
357+ // r[impl unary.lifecycle.single-response] - Exactly one Response per Request.
358+ // r[impl unary.request-id.in-flight] - Request no longer in-flight after Response.
359+ let resp = Message :: Response {
360+ request_id,
361+ metadata : Vec :: new ( ) ,
362+ payload : response_payload,
363+ } ;
364+ self . io . send ( & resp) . await ?;
365+ self . in_flight_requests . remove ( & request_id) ;
366+
367+ // Flush any outgoing stream data that handlers may have queued
368+ self . flush_outgoing ( ) . await ?;
369+ }
327370 }
328371 Message :: Response { request_id, .. } => {
329372 // Server doesn't expect Response messages (it sends them, not receives them).
@@ -472,6 +515,7 @@ where
472515 initial_credit : our_credit. min ( peer_credit) ,
473516 } ;
474517
518+ let ( streaming_results_tx, streaming_results_rx) = tokio:: sync:: mpsc:: channel ( 64 ) ;
475519 Ok ( Connection {
476520 io,
477521 role : Role :: Acceptor ,
@@ -481,6 +525,8 @@ where
481525 stream_registry : StreamRegistry :: new_with_credit ( negotiated. initial_credit ) ,
482526 in_flight_requests : HashSet :: new ( ) ,
483527 our_hello,
528+ streaming_results_tx,
529+ streaming_results_rx,
484530 } )
485531}
486532
@@ -551,6 +597,7 @@ where
551597 initial_credit : our_credit. min ( peer_credit) ,
552598 } ;
553599
600+ let ( streaming_results_tx, streaming_results_rx) = tokio:: sync:: mpsc:: channel ( 64 ) ;
554601 Ok ( Connection {
555602 io,
556603 role : Role :: Initiator ,
@@ -560,5 +607,7 @@ where
560607 stream_registry : StreamRegistry :: new_with_credit ( negotiated. initial_credit ) ,
561608 in_flight_requests : HashSet :: new ( ) ,
562609 our_hello,
610+ streaming_results_tx,
611+ streaming_results_rx,
563612 } )
564613}
0 commit comments