@@ -9,7 +9,6 @@ use std::{
99 sync:: Arc ,
1010 time:: Duration ,
1111} ;
12- use tokio:: sync:: Mutex ;
1312use tokio:: time:: Instant ;
1413
1514/// Tombstone lifetime. Bridges the `register()` → `associate_instance()`
@@ -22,6 +21,7 @@ use bytes::Bytes;
2221use derive_builder:: Builder ;
2322use futures:: { SinkExt , StreamExt } ;
2423use local_ip_address:: { Error , list_afinet_netifas, local_ip, local_ipv6} ;
24+ use parking_lot:: Mutex ;
2525
2626use serde:: { Deserialize , Serialize } ;
2727use tokio:: {
@@ -259,7 +259,7 @@ impl TcpStreamServer {
259259 send_subject : Option < & str > ,
260260 id : & EndpointInstanceId ,
261261 ) -> bool {
262- let mut state = self . state . lock ( ) . await ;
262+ let mut state = self . state . lock ( ) ;
263263 let now = Instant :: now ( ) ;
264264 prune_tombstones ( & mut state. removed_instances , now) ;
265265 if state. removed_instances . contains_key ( id) {
@@ -296,7 +296,7 @@ impl TcpStreamServer {
296296 /// Cancel one pending response-stream registration. Drops the
297297 /// `oneshot::Sender` so the waiting receiver resolves with `RecvError`.
298298 pub async fn cancel_recv_stream ( & self , subject : & str ) {
299- let mut state = self . state . lock ( ) . await ;
299+ let mut state = self . state . lock ( ) ;
300300 state. rx_subjects . remove ( subject) ;
301301 if let Some ( key) = state. subject_instance . remove ( subject)
302302 && let Some ( subjects) = state. instance_subjects . get_mut ( & key)
@@ -314,7 +314,7 @@ impl TcpStreamServer {
314314 /// `(StreamType::Request, _)` tag from `instance_subjects` so the per-
315315 /// instance bookkeeping stays consistent.
316316 pub async fn cancel_send_stream ( & self , subject : & str ) {
317- let mut state = self . state . lock ( ) . await ;
317+ let mut state = self . state . lock ( ) ;
318318 state. tx_subjects . remove ( subject) ;
319319 if let Some ( key) = state. subject_instance . remove ( subject)
320320 && let Some ( subjects) = state. instance_subjects . get_mut ( & key)
@@ -331,7 +331,7 @@ impl TcpStreamServer {
331331 /// `associate_instance` — and tombstone the id so any racing associate
332332 /// for the same id cancels too. Returns the number of streams cancelled.
333333 pub async fn cancel_instance_streams ( & self , id : & EndpointInstanceId ) -> usize {
334- let mut state = self . state . lock ( ) . await ;
334+ let mut state = self . state . lock ( ) ;
335335 let now = Instant :: now ( ) ;
336336 prune_tombstones ( & mut state. removed_instances , now) ;
337337 state. removed_instances . insert ( id. clone ( ) , now) ;
@@ -357,25 +357,63 @@ impl TcpStreamServer {
357357 /// Drop the tombstone for an instance that has reappeared in discovery,
358358 /// so future subjects for that identity are tracked normally.
359359 pub async fn clear_instance_tombstone ( & self , id : & EndpointInstanceId ) {
360- let mut state = self . state . lock ( ) . await ;
360+ let mut state = self . state . lock ( ) ;
361361 state. removed_instances . remove ( id) ;
362362 }
363363
364- #[ allow( clippy:: await_holding_lock) ]
365364 async fn start ( local_ip : String , local_port : u16 , state : Arc < Mutex < State > > ) -> Result < u16 > {
366365 let addr = format ! ( "{}:{}" , local_ip, local_port) ;
367366 let state_clone = state. clone ( ) ;
368- let mut guard = state. lock ( ) . await ;
369- if guard. handle . is_some ( ) {
370- panic ! ( "TcpStreamServer already started" ) ;
371- }
372367 let ( ready_tx, ready_rx) = tokio:: sync:: oneshot:: channel :: < Result < u16 > > ( ) ;
373- let handle = tokio:: spawn ( tcp_listener ( addr, state_clone, ready_tx) ) ;
374- guard. handle = Some ( handle) ;
375- drop ( guard) ;
368+ {
369+ let mut guard = state. lock ( ) ;
370+ if guard. handle . is_some ( ) {
371+ panic ! ( "TcpStreamServer already started" ) ;
372+ }
373+ guard. handle = Some ( tokio:: spawn ( tcp_listener ( addr, state_clone, ready_tx) ) ) ;
374+ }
376375 let local_port = ready_rx. await ??;
377376 Ok ( local_port)
378377 }
378+
379+ fn insert_request_stream ( & self , subject : String , connection : RequestedSendConnection ) {
380+ self . state . lock ( ) . tx_subjects . insert ( subject, connection) ;
381+ }
382+
383+ fn insert_response_stream ( & self , subject : String , connection : RequestedRecvConnection ) {
384+ self . state . lock ( ) . rx_subjects . insert ( subject, connection) ;
385+ }
386+
387+ fn take_request_stream ( state : & Mutex < State > , subject : & str ) -> Option < RequestedSendConnection > {
388+ let mut state = state. lock ( ) ;
389+ let connection = state. tx_subjects . remove ( subject) ;
390+ if let Some ( key) = state. subject_instance . remove ( subject)
391+ && let Some ( subjects) = state. instance_subjects . get_mut ( & key)
392+ {
393+ subjects. remove ( & ( StreamType :: Request , subject. to_string ( ) ) ) ;
394+ if subjects. is_empty ( ) {
395+ state. instance_subjects . remove ( & key) ;
396+ }
397+ }
398+ connection
399+ }
400+
401+ fn take_response_stream (
402+ state : & Mutex < State > ,
403+ subject : & str ,
404+ ) -> Option < RequestedRecvConnection > {
405+ let mut state = state. lock ( ) ;
406+ let connection = state. rx_subjects . remove ( subject) ;
407+ if let Some ( key) = state. subject_instance . remove ( subject)
408+ && let Some ( subjects) = state. instance_subjects . get_mut ( & key)
409+ {
410+ subjects. remove ( & ( StreamType :: Response , subject. to_string ( ) ) ) ;
411+ if subjects. is_empty ( ) {
412+ state. instance_subjects . remove ( & key) ;
413+ }
414+ }
415+ connection
416+ }
379417}
380418
381419// todo - possible rename ResponseService to ResponseServer
@@ -409,6 +447,7 @@ impl ResponseService for TcpStreamServer {
409447
410448 let send_stream = if options. enable_request_stream {
411449 let sender_subject = uuid:: Uuid :: new_v4 ( ) . to_string ( ) ;
450+ let registry_subject = sender_subject. clone ( ) ;
412451
413452 let ( pending_sender_tx, pending_sender_rx) = oneshot:: channel ( ) ;
414453
@@ -418,11 +457,6 @@ impl ResponseService for TcpStreamServer {
418457 send_buffer_count : options. send_buffer_count ,
419458 } ;
420459
421- let mut state = self . state . lock ( ) . await ;
422- state
423- . tx_subjects
424- . insert ( sender_subject. clone ( ) , connection_info) ;
425-
426460 let cleanup_subject = sender_subject. clone ( ) ;
427461 let cleanup_state = self . state . clone ( ) ;
428462 let registered_stream = RegisteredStream :: new (
@@ -438,7 +472,7 @@ impl ResponseService for TcpStreamServer {
438472 . with_cleanup ( move || {
439473 // Drop is sync; fire-and-forget the lock acquisition.
440474 tokio:: spawn ( async move {
441- let mut state = cleanup_state. lock ( ) . await ;
475+ let mut state = cleanup_state. lock ( ) ;
442476 state. tx_subjects . remove ( & cleanup_subject) ;
443477 if let Some ( key) = state. subject_instance . remove ( & cleanup_subject)
444478 && let Some ( subjects) = state. instance_subjects . get_mut ( & key)
@@ -451,6 +485,8 @@ impl ResponseService for TcpStreamServer {
451485 } ) ;
452486 } ) ;
453487
488+ self . insert_request_stream ( registry_subject, connection_info) ;
489+
454490 Some ( registered_stream)
455491 } else {
456492 None
@@ -459,18 +495,14 @@ impl ResponseService for TcpStreamServer {
459495 let recv_stream = if options. enable_response_stream {
460496 let ( pending_recver_tx, pending_recver_rx) = oneshot:: channel ( ) ;
461497 let receiver_subject = uuid:: Uuid :: new_v4 ( ) . to_string ( ) ;
498+ let registry_subject = receiver_subject. clone ( ) ;
462499
463500 let connection_info = RequestedRecvConnection {
464501 context : options. context . clone ( ) ,
465502 connection : pending_recver_tx,
466503 send_buffer_count : options. send_buffer_count ,
467504 } ;
468505
469- let mut state = self . state . lock ( ) . await ;
470- state
471- . rx_subjects
472- . insert ( receiver_subject. clone ( ) , connection_info) ;
473-
474506 let cleanup_subject = receiver_subject. clone ( ) ;
475507 let cleanup_state = self . state . clone ( ) ;
476508 let registered_stream = RegisteredStream :: new (
@@ -486,7 +518,7 @@ impl ResponseService for TcpStreamServer {
486518 . with_cleanup ( move || {
487519 // Drop is sync; fire-and-forget the lock acquisition.
488520 tokio:: spawn ( async move {
489- let mut state = cleanup_state. lock ( ) . await ;
521+ let mut state = cleanup_state. lock ( ) ;
490522 state. rx_subjects . remove ( & cleanup_subject) ;
491523 if let Some ( key) = state. subject_instance . remove ( & cleanup_subject)
492524 && let Some ( subjects) = state. instance_subjects . get_mut ( & key)
@@ -499,6 +531,8 @@ impl ResponseService for TcpStreamServer {
499531 } ) ;
500532 } ) ;
501533
534+ self . insert_response_stream ( registry_subject, connection_info) ;
535+
502536 Some ( registered_stream)
503537 } else {
504538 None
@@ -653,22 +687,12 @@ async fn tcp_listener(
653687 // Request stream is unidirectional; we don't read from the downstream.
654688 drop ( reader) ;
655689
656- let request_stream = {
657- let mut guard = state. lock ( ) . await ;
658- let conn = guard. tx_subjects . remove ( & subject) . ok_or ( error ! (
690+ let request_stream = TcpStreamServer :: take_request_stream ( & state, & subject) . ok_or_else ( || {
691+ error ! (
659692 "Subject not found: {}; downstream subscriber specified a subject unknown to the upstream publisher" ,
660693 subject
661- ) ) ?;
662- if let Some ( key) = guard. subject_instance . remove ( & subject)
663- && let Some ( subjects) = guard. instance_subjects . get_mut ( & key)
664- {
665- subjects. remove ( & ( StreamType :: Request , subject. clone ( ) ) ) ;
666- if subjects. is_empty ( ) {
667- guard. instance_subjects . remove ( & key) ;
668- }
669- }
670- conn
671- } ;
694+ )
695+ } ) ?;
672696
673697 let RequestedSendConnection {
674698 context,
@@ -771,22 +795,9 @@ async fn tcp_listener(
771795 mut reader : FramedRead < tokio:: io:: ReadHalf < tokio:: net:: TcpStream > , TwoPartCodec > ,
772796 writer : FramedWrite < tokio:: io:: WriteHalf < tokio:: net:: TcpStream > , TwoPartCodec > ,
773797 ) -> Result < ( ) > {
774- let response_stream = {
775- let mut guard = state. lock ( ) . await ;
776- let conn = guard
777- . rx_subjects
778- . remove ( & subject)
779- . ok_or ( error ! ( "Subject not found: {}; upstream publisher specified a subject unknown to the downsteam subscriber" , subject) ) ?;
780- if let Some ( key) = guard. subject_instance . remove ( & subject)
781- && let Some ( subjects) = guard. instance_subjects . get_mut ( & key)
782- {
783- subjects. remove ( & ( StreamType :: Response , subject. clone ( ) ) ) ;
784- if subjects. is_empty ( ) {
785- guard. instance_subjects . remove ( & key) ;
786- }
787- }
788- conn
789- } ;
798+ let response_stream = TcpStreamServer :: take_response_stream ( & state, & subject) . ok_or_else ( || {
799+ error ! ( "Subject not found: {}; upstream publisher specified a subject unknown to the downsteam subscriber" , subject)
800+ } ) ?;
790801
791802 // unwrap response_stream
792803 let RequestedRecvConnection {
@@ -1036,6 +1047,7 @@ mod tests {
10361047 use crate :: engine:: AsyncEngineContextProvider ;
10371048 use crate :: pipeline:: Context ;
10381049 use crate :: pipeline:: network:: DEFAULT_SEND_BUFFER_COUNT ;
1050+ use crate :: pipeline:: network:: tcp:: client:: TcpClient ;
10391051 use tokio:: io:: { AsyncWriteExt , ReadHalf , WriteHalf } ;
10401052 use tokio:: net:: TcpStream ;
10411053
@@ -1138,7 +1150,7 @@ mod tests {
11381150
11391151 let _pending = server. register ( options) . await ;
11401152
1141- let state = server. state . lock ( ) . await ;
1153+ let state = server. state . lock ( ) ;
11421154 assert_eq ! ( state. tx_subjects. len( ) , 1 , "one request stream registered" ) ;
11431155 assert_eq ! ( state. rx_subjects. len( ) , 1 , "one response stream registered" ) ;
11441156 assert ! (
@@ -1439,7 +1451,7 @@ mod tests {
14391451
14401452 // Verify it's in rx_subjects
14411453 {
1442- let state = server. state . lock ( ) . await ;
1454+ let state = server. state . lock ( ) ;
14431455 assert ! ( state. rx_subjects. contains_key( & subject) ) ;
14441456 }
14451457
@@ -1451,7 +1463,7 @@ mod tests {
14511463
14521464 // Verify it's been removed from rx_subjects
14531465 {
1454- let state = server. state . lock ( ) . await ;
1466+ let state = server. state . lock ( ) ;
14551467 assert ! (
14561468 !state. rx_subjects. contains_key( & subject) ,
14571469 "RAII cleanup should have removed the rx_subjects entry"
@@ -1486,7 +1498,7 @@ mod tests {
14861498
14871499 // The entry should still be in rx_subjects (cleanup was disarmed)
14881500 {
1489- let state = server. state . lock ( ) . await ;
1501+ let state = server. state . lock ( ) ;
14901502 assert ! (
14911503 state. rx_subjects. contains_key( & subject) ,
14921504 "into_parts() should disarm the RAII cleanup"
@@ -1643,7 +1655,7 @@ mod tests {
16431655 // Tombstone the identity.
16441656 server. cancel_instance_streams ( & id) . await ;
16451657 {
1646- let state = server. state . lock ( ) . await ;
1658+ let state = server. state . lock ( ) ;
16471659 assert ! ( state. removed_instances. contains_key( & id) ) ;
16481660 }
16491661
@@ -1661,7 +1673,7 @@ mod tests {
16611673 // The expired tombstone must have been pruned (lazy pruning fires on
16621674 // every associate_instance/cancel_instance_streams call).
16631675 {
1664- let state = server. state . lock ( ) . await ;
1676+ let state = server. state . lock ( ) ;
16651677 assert ! (
16661678 !state. removed_instances. contains_key( & id) ,
16671679 "expired tombstone should be pruned, not retained"
@@ -1702,7 +1714,7 @@ mod tests {
17021714 tokio:: time:: advance ( TOMBSTONE_TTL + Duration :: from_secs ( 1 ) ) . await ;
17031715 server. cancel_instance_streams ( & id_new) . await ;
17041716
1705- let state = server. state . lock ( ) . await ;
1717+ let state = server. state . lock ( ) ;
17061718 assert ! (
17071719 !state. removed_instances. contains_key( & id_old) ,
17081720 "old tombstone should be pruned by the next cancel_instance_streams call"
@@ -1728,7 +1740,7 @@ mod tests {
17281740 server. cancel_instance_streams ( & id_a) . await ;
17291741 server. clear_instance_tombstone ( & id_b) . await ;
17301742
1731- let state = server. state . lock ( ) . await ;
1743+ let state = server. state . lock ( ) ;
17321744 assert ! (
17331745 state. removed_instances. contains_key( & id_a) ,
17341746 "clearing a different identity must not remove id_a's tombstone"
@@ -1957,6 +1969,63 @@ mod tests {
19571969 }
19581970 }
19591971
1972+ #[ tokio:: test( flavor = "multi_thread" , worker_threads = 4 ) ]
1973+ async fn test_concurrent_response_registration_and_call_home ( ) {
1974+ const STREAMS : usize = 128 ;
1975+
1976+ let result = time:: timeout ( Duration :: from_secs ( 20 ) , async {
1977+ let server = test_server ( ) . await ;
1978+ let mut pending_streams = Vec :: with_capacity ( STREAMS ) ;
1979+ let mut client_tasks = Vec :: with_capacity ( STREAMS ) ;
1980+
1981+ for idx in 0 ..STREAMS {
1982+ let context = Context :: new ( ( ) ) ;
1983+ let options = StreamOptions :: builder ( )
1984+ . context ( context. context ( ) )
1985+ . enable_request_stream ( false )
1986+ . enable_response_stream ( true )
1987+ . build ( )
1988+ . unwrap ( ) ;
1989+
1990+ let pending = server. register ( options) . await ;
1991+ let registered_stream = pending. recv_stream . unwrap ( ) ;
1992+ let ( connection_info, stream_provider) = registered_stream. into_parts ( ) ;
1993+ let client_context =
1994+ Context :: with_id_and_metadata ( ( ) , context. id ( ) . to_string ( ) , Default :: default ( ) ) ;
1995+ let payload = Bytes :: from ( format ! ( "payload-{idx}" ) ) ;
1996+
1997+ pending_streams. push ( ( idx, payload. clone ( ) , stream_provider) ) ;
1998+ client_tasks. push ( tokio:: spawn ( async move {
1999+ let mut sender = TcpClient :: create_response_stream (
2000+ client_context. context ( ) ,
2001+ connection_info,
2002+ None ,
2003+ )
2004+ . await
2005+ . unwrap ( ) ;
2006+ sender. send_prologue ( None ) . await . unwrap ( ) ;
2007+ sender. send ( payload) . await . unwrap ( ) ;
2008+ } ) ) ;
2009+ }
2010+
2011+ for task in client_tasks {
2012+ task. await . unwrap ( ) ;
2013+ }
2014+
2015+ for ( idx, expected, stream_provider) in pending_streams {
2016+ let mut stream = stream_provider. await . unwrap ( ) . unwrap ( ) ;
2017+ let actual = stream. rx . recv ( ) . await . unwrap ( ) ;
2018+ assert_eq ! ( actual, expected, "payload mismatch for stream {idx}" ) ;
2019+ }
2020+ } )
2021+ . await ;
2022+
2023+ assert ! (
2024+ result. is_ok( ) ,
2025+ "concurrent response registration and call-home timed out"
2026+ ) ;
2027+ }
2028+
19602029 // ==================== request_stream_send_handler integration tests ====================
19612030 //
19622031 // These exercise the closing-message contract of `request_stream_send_handler`
0 commit comments