@@ -12,6 +12,7 @@ use futures::{SinkExt, StreamExt};
1212use tokio_util:: codec:: { FramedRead , FramedWrite , LinesCodec } ;
1313
1414/// Manages the lifecycle of backend processes and routes messages between clients and backends.
15+
1516#[ derive( Clone ) ]
1617pub struct BackendManager {
1718 state : Arc < Mutex < BackendManagerState > > ,
@@ -177,7 +178,10 @@ impl BackendManager {
177178 stderr_task. abort ( ) ;
178179
179180 if exit_reason == BackendExitReason :: Normal {
180- info ! ( "Backend {} input channel closed permanently. Stopping manager loop." , backend_name_clone) ;
181+ info ! (
182+ "Backend {} input channel closed permanently. Stopping manager loop." ,
183+ backend_name_clone
184+ ) ;
181185 break ; // Exit the loop entirely to prevent infinite restarts!
182186 }
183187
@@ -249,9 +253,12 @@ impl BackendManager {
249253 ) {
250254 // TODO: Need to detect if the backend process hangs and is not responsive.
251255 // When a backend process hangs, reading from its stdout may blocks forever.
252- let mut stdout_reader = FramedRead :: new ( stdout, LinesCodec :: new_with_max_length ( 1048576 ) ) ;
256+ let mut stdout_reader = FramedRead :: new (
257+ stdout,
258+ LinesCodec :: new_with_max_length ( protocol:: MAX_MESSAGE_LINE_LENGTH ) ,
259+ ) ;
253260 while let Some ( result) = stdout_reader. next ( ) . await {
254- last_output_time. store ( Self :: current_ms ( ) , Ordering :: SeqCst ) ;
261+ last_output_time. store ( Self :: current_ms ( ) , Ordering :: Relaxed ) ;
255262 let line = match result {
256263 Ok ( l) => l,
257264 Err ( e) => {
@@ -301,7 +308,10 @@ impl BackendManager {
301308 backend_name : & str ,
302309 last_output_time : Arc < AtomicU64 > ,
303310 ) -> BackendExitReason {
304- let mut stdin_writer = FramedWrite :: new ( stdin, LinesCodec :: new_with_max_length ( 1048576 ) ) ;
311+ let mut stdin_writer = FramedWrite :: new (
312+ stdin,
313+ LinesCodec :: new_with_max_length ( protocol:: MAX_MESSAGE_LINE_LENGTH ) ,
314+ ) ;
305315 let mut last_request_time: Option < u64 > = None ;
306316
307317 let mut watchdog_interval = tokio:: time:: interval ( Duration :: from_secs ( 1 ) ) ;
@@ -310,15 +320,18 @@ impl BackendManager {
310320 tokio:: select! {
311321 msg = stdin_rx. recv( ) => {
312322 let Some ( data) = msg else {
323+ // mpsc::Receiver returns None when all Senders are dropped (e.g., Launcher shutdown
324+ // or explicit backend removal from BackendManager). In that case, no further input
325+ // will ever arrive. Thus, we exit normally without restarting the process.
313326 info!( "Backend {} stdin channel closed. Exiting input loop." , backend_name) ;
314327 return BackendExitReason :: Normal ;
315328 } ;
316329 let now = Self :: current_ms( ) ;
317330 last_request_time = Some ( now) ;
318- info!( "Backend {} received request from channel. Data len: {}. req_t={} " , backend_name, data. len( ) , now ) ;
331+ info!( "Backend {} received request from channel. Data len: {}." , backend_name, data. len( ) ) ;
319332
320333 // LinesCodec expects data without the newline, it will add it for us.
321- let write_res = tokio:: time:: timeout( Duration :: from_secs ( 5 ) , stdin_writer. send( data) ) . await ;
334+ let write_res = tokio:: time:: timeout( protocol :: BACKEND_WRITE_TIMEOUT , stdin_writer. send( data) ) . await ;
322335 if let Err ( _) = write_res {
323336 error!( "Timeout writing to backend {}. Forcing restart." , backend_name) ;
324337 let _ = child_process. kill( ) . await ;
@@ -333,15 +346,13 @@ impl BackendManager {
333346 _ = watchdog_interval. tick( ) => {
334347 let now = Self :: current_ms( ) ;
335348 if let Some ( req_t) = last_request_time {
336- let last_out = last_output_time. load( Ordering :: SeqCst ) ;
337- // Log tick status occasionally or at least for debugging
338- debug!( "Watchdog tick for {}: last_out={}, req_t={}, now={}, delta={}" ,
339- backend_name, last_out, req_t, now, now as i64 - req_t as i64 ) ;
340-
341- // If no output has been received since the last request and it's been more than 15 seconds
342- if last_out < req_t && ( now - req_t) > 15000 {
343- error!( "Backend {} seems to be hung (no output for 15s after request). last_out={}, req_t={}, now={}. Forcing restart." ,
344- backend_name, last_out, req_t, now) ;
349+ let last_out = last_output_time. load( Ordering :: Relaxed ) ;
350+ debug!( "Watchdog tick for {}: last_out={}, req_t={}, now={}, delta={}ms" ,
351+ backend_name, last_out, req_t, now, now. saturating_sub( req_t) ) ;
352+
353+ if last_out < req_t && now - req_t > protocol:: HANG_TIMEOUT . as_millis( ) as u64 {
354+ error!( "Backend {} seems to be hung (no output for {}ms after request). Forcing restart." ,
355+ backend_name, protocol:: HANG_TIMEOUT . as_millis( ) ) ;
345356 let _ = child_process. kill( ) . await ;
346357 return BackendExitReason :: Error ;
347358 }
0 commit comments