@@ -335,6 +335,9 @@ struct PendingRelay {
335335 sender : RelayStreamSender ,
336336 sandbox_id : String ,
337337 relay_open : RelayOpen ,
338+ /// Session that most recently received this relay open. Reconnect replay
339+ /// skips relays already sent to the session being established.
340+ last_sent_session_id : Option < String > ,
338341 created_at : Instant ,
339342}
340343
@@ -474,13 +477,13 @@ impl SupervisorSessionRegistry {
474477 & self ,
475478 sandbox_id : & str ,
476479 timeout : Duration ,
477- ) -> Result < mpsc:: Sender < GatewayMessage > , Status > {
480+ ) -> Result < ( String , mpsc:: Sender < GatewayMessage > ) , Status > {
478481 let deadline = Instant :: now ( ) + timeout;
479482 let mut backoff = SESSION_WAIT_INITIAL_BACKOFF ;
480483
481484 loop {
482- if let Some ( tx ) = self . lookup_session ( sandbox_id) {
483- return Ok ( tx ) ;
485+ if let Some ( session ) = self . lookup_session_with_id ( sandbox_id) {
486+ return Ok ( session ) ;
484487 }
485488 if Instant :: now ( ) + backoff > deadline {
486489 return Err ( Status :: unavailable ( "supervisor session not connected" ) ) ;
@@ -490,12 +493,15 @@ impl SupervisorSessionRegistry {
490493 }
491494 }
492495
493- fn lookup_session ( & self , sandbox_id : & str ) -> Option < mpsc:: Sender < GatewayMessage > > {
496+ fn lookup_session_with_id (
497+ & self ,
498+ sandbox_id : & str ,
499+ ) -> Option < ( String , mpsc:: Sender < GatewayMessage > ) > {
494500 self . sessions
495501 . lock ( )
496502 . unwrap ( )
497503 . get ( sandbox_id)
498- . map ( |s| s . tx . clone ( ) )
504+ . map ( |s| ( s . session_id . clone ( ) , s . tx . clone ( ) ) )
499505 }
500506
501507 pub fn has_session ( & self , sandbox_id : & str ) -> bool {
@@ -791,7 +797,7 @@ impl SupervisorSessionRegistry {
791797 if relay_open. channel_id . is_empty ( ) {
792798 return Err ( Status :: invalid_argument ( "relay channel_id is required" ) ) ;
793799 }
794- let tx = self
800+ let ( session_id , tx ) = self
795801 . wait_for_session ( sandbox_id, session_wait_timeout)
796802 . await ?;
797803
@@ -824,6 +830,7 @@ impl SupervisorSessionRegistry {
824830 sender : relay_tx,
825831 sandbox_id : sandbox_id. to_string ( ) ,
826832 relay_open : relay_open. clone ( ) ,
833+ last_sent_session_id : Some ( session_id) ,
827834 created_at : Instant :: now ( ) ,
828835 } ,
829836 ) ;
@@ -918,22 +925,29 @@ impl SupervisorSessionRegistry {
918925 self . remove ( sandbox_id) ;
919926 }
920927
921- pub async fn replay_pending_relays ( & self , sandbox_id : & str , tx : & mpsc:: Sender < GatewayMessage > ) {
928+ pub async fn replay_pending_relays (
929+ & self ,
930+ sandbox_id : & str ,
931+ session_id : & str ,
932+ tx : & mpsc:: Sender < GatewayMessage > ,
933+ ) {
922934 for channel_id in self . pending_channel_ids ( sandbox_id) {
923935 let relay_open = {
924- let pending = self . pending_relays . lock ( ) . unwrap ( ) ;
925- pending
926- . get ( & channel_id)
927- . map ( |pending| pending. relay_open . clone ( ) )
928- } ;
929- let Some ( relay_open) = relay_open else {
930- continue ;
936+ let mut pending = self . pending_relays . lock ( ) . unwrap ( ) ;
937+ let Some ( pending) = pending. get_mut ( & channel_id) else {
938+ continue ;
939+ } ;
940+ if pending. last_sent_session_id . as_deref ( ) == Some ( session_id) {
941+ continue ;
942+ }
943+ pending. last_sent_session_id = Some ( session_id. to_string ( ) ) ;
944+ pending. relay_open . clone ( )
931945 } ;
932946 let msg = GatewayMessage {
933947 payload : Some ( gateway_message:: Payload :: RelayOpen ( relay_open) ) ,
934948 } ;
935949 if tx. send ( msg) . await . is_err ( ) {
936- warn ! ( sandbox_id = %sandbox_id, channel_id = %channel_id, "supervisor session: failed to replay pending relay to superseding session" ) ;
950+ warn ! ( sandbox_id = %sandbox_id, channel_id = %channel_id, "supervisor session: failed to replay pending relay to new session" ) ;
937951 break ;
938952 }
939953 }
@@ -1960,12 +1974,12 @@ async fn establish_supervisor_session(
19601974 }
19611975 state. telemetry . sandbox_session_connected ( & sandbox_id) ;
19621976
1963- if superseded {
1964- state
1965- . supervisor_sessions
1966- . replay_pending_relays ( & sandbox_id , & tx )
1967- . await ;
1968- }
1977+ // The previous session may already have been removed before this one
1978+ // registers. Pending relay opens still need to reach the new session.
1979+ state
1980+ . supervisor_sessions
1981+ . replay_pending_relays ( & sandbox_id , & session_id , & tx )
1982+ . await ;
19691983
19701984 // Step 4: Spawn the session loop that reads inbound messages.
19711985 let state_clone = Arc :: clone ( & state) ;
@@ -2495,6 +2509,7 @@ mod tests {
24952509 target : Some ( relay_open:: Target :: Ssh ( SshRelayTarget { } ) ) ,
24962510 service_id : String :: new ( ) ,
24972511 } ,
2512+ last_sent_session_id : None ,
24982513 created_at,
24992514 }
25002515 }
@@ -2950,13 +2965,13 @@ mod tests {
29502965 let superseded = registry. register (
29512966 "sbx" . to_string ( ) ,
29522967 "s-new" . to_string ( ) ,
2953- tx_new,
2968+ tx_new. clone ( ) ,
29542969 make_shutdown ( ) ,
29552970 ) ;
29562971 assert ! ( superseded) ;
29572972
29582973 registry
2959- . replay_pending_relays ( "sbx" , & registry . lookup_session ( "sbx" ) . unwrap ( ) )
2974+ . replay_pending_relays ( "sbx" , "s-new" , & tx_new )
29602975 . await ;
29612976
29622977 let replayed = rx_new
@@ -2971,6 +2986,89 @@ mod tests {
29712986 }
29722987 }
29732988
2989+ #[ tokio:: test]
2990+ async fn replay_pending_relays_reissues_open_after_disconnected_session ( ) {
2991+ let registry = SupervisorSessionRegistry :: new ( ) ;
2992+ let ( tx_old, mut rx_old) = mpsc:: channel :: < GatewayMessage > ( 4 ) ;
2993+ let ( tx_new, mut rx_new) = mpsc:: channel :: < GatewayMessage > ( 4 ) ;
2994+
2995+ registry. register (
2996+ "sbx" . to_string ( ) ,
2997+ "s-old" . to_string ( ) ,
2998+ tx_old,
2999+ make_shutdown ( ) ,
3000+ ) ;
3001+ let ( channel_id, _relay_rx) = registry
3002+ . open_relay ( "sbx" , Duration :: from_secs ( 1 ) )
3003+ . await
3004+ . expect ( "open_relay should succeed" ) ;
3005+ rx_old
3006+ . recv ( )
3007+ . await
3008+ . expect ( "old session should receive RelayOpen" ) ;
3009+
3010+ assert_eq ! ( registry. remove_if_current( "sbx" , "s-old" ) , Some ( false ) ) ;
3011+ let superseded = registry. register (
3012+ "sbx" . to_string ( ) ,
3013+ "s-new" . to_string ( ) ,
3014+ tx_new. clone ( ) ,
3015+ make_shutdown ( ) ,
3016+ ) ;
3017+ assert ! ( !superseded, "old session was removed before reconnect" ) ;
3018+
3019+ registry
3020+ . replay_pending_relays ( "sbx" , "s-new" , & tx_new)
3021+ . await ;
3022+
3023+ let replayed = rx_new
3024+ . recv ( )
3025+ . await
3026+ . expect ( "new session should receive RelayOpen" ) ;
3027+ match replayed. payload {
3028+ Some ( gateway_message:: Payload :: RelayOpen ( open) ) => {
3029+ assert_eq ! ( open. channel_id, channel_id) ;
3030+ }
3031+ other => panic ! ( "expected RelayOpen, got {other:?}" ) ,
3032+ }
3033+ }
3034+
3035+ #[ tokio:: test]
3036+ async fn replay_pending_relays_skips_open_already_sent_to_session ( ) {
3037+ let registry = SupervisorSessionRegistry :: new ( ) ;
3038+ let ( tx, mut rx) = mpsc:: channel :: < GatewayMessage > ( 4 ) ;
3039+
3040+ registry. register (
3041+ "sbx" . to_string ( ) ,
3042+ "s-new" . to_string ( ) ,
3043+ tx. clone ( ) ,
3044+ make_shutdown ( ) ,
3045+ ) ;
3046+
3047+ let ( channel_id, _relay_rx) = registry
3048+ . open_relay ( "sbx" , Duration :: from_secs ( 1 ) )
3049+ . await
3050+ . expect ( "open_relay should succeed" ) ;
3051+ let original = rx
3052+ . recv ( )
3053+ . await
3054+ . expect ( "new session should receive RelayOpen" ) ;
3055+ assert ! ( matches!(
3056+ original. payload,
3057+ Some ( gateway_message:: Payload :: RelayOpen ( ref open) )
3058+ if open. channel_id == channel_id
3059+ ) ) ;
3060+
3061+ // Model establish_supervisor_session reaching its unconditional replay
3062+ // after open_relay observed the newly registered session.
3063+ registry. replay_pending_relays ( "sbx" , "s-new" , & tx) . await ;
3064+
3065+ let duplicate = tokio:: time:: timeout ( Duration :: from_millis ( 50 ) , rx. recv ( ) ) . await ;
3066+ assert ! (
3067+ duplicate. is_err( ) ,
3068+ "RelayOpen for {channel_id} was delivered twice: {duplicate:?}"
3069+ ) ;
3070+ }
3071+
29743072 #[ tokio:: test]
29753073 async fn require_persisted_sandbox_rejects_missing_sandbox ( ) {
29763074 let store = test_store ( ) . await ;
0 commit comments