@@ -62,7 +62,7 @@ pub(crate) fn start_bridge_server(
6262 let active = Arc :: new ( AtomicUsize :: new ( 0 ) ) ;
6363 for stream in listener. incoming ( ) {
6464 match stream {
65- Ok ( stream) => {
65+ Ok ( mut stream) => {
6666 // Handle each connection on its own thread so a `/cancel`
6767 // request can be served while a `/compare` is still running
6868 // (the accept loop must not block on a single request).
@@ -71,7 +71,14 @@ pub(crate) fn start_bridge_server(
7171 concurrent = active. load( Ordering :: Relaxed ) ,
7272 "LinSync GUI bridge connection limit reached, rejecting"
7373 ) ;
74- // Drain and drop the stream so the client doesn't hang.
74+ // Return a 503 so the client sees a retry signal rather than
75+ // a silently dropped connection.
76+ let response = b"HTTP/1.1 503 Service Unavailable\r \n Content-Length: 0\r \n Connection: close\r \n \r \n " ;
77+ if let Err ( err) = stream. write_all ( response) {
78+ tracing:: warn!( error = %err, "failed to write 503 to rejected bridge connection" ) ;
79+ } else if let Err ( err) = stream. flush ( ) {
80+ tracing:: warn!( error = %err, "failed to flush 503 to rejected bridge connection" ) ;
81+ }
7582 drop ( stream) ;
7683 continue ;
7784 }
@@ -311,7 +318,7 @@ pub(crate) fn bridge_response_with_token(
311318 let req =
312319 register_cancellable_request ( & params, state, "extracting" , 3 , "Extracting text" ) ;
313320 set_progress (
314- & req. progress ,
321+ & req. progress ( ) ,
315322 "extracting" ,
316323 1 ,
317324 3 ,
@@ -325,7 +332,6 @@ pub(crate) fn bridge_response_with_token(
325332 // If the user hit Stop during the (potentially slow) plugin
326333 // extraction, discard the result and report cancellation.
327334 if req. is_cancelled ( ) {
328- remove_cancellable_request ( & req, state) ;
329335 return http_response (
330336 200 ,
331337 "OK" ,
@@ -334,7 +340,7 @@ pub(crate) fn bridge_response_with_token(
334340 ) ;
335341 }
336342 set_progress (
337- & req. progress ,
343+ & req. progress ( ) ,
338344 "finalizing" ,
339345 2 ,
340346 3 ,
@@ -367,8 +373,7 @@ pub(crate) fn bridge_response_with_token(
367373 }
368374 }
369375 }
370- set_progress ( & req. progress , "done" , 3 , 3 , String :: new ( ) ) ;
371- remove_cancellable_request ( & req, state) ;
376+ set_progress ( & req. progress ( ) , "done" , 3 , 3 , String :: new ( ) ) ;
372377 http_response ( 200 , "OK" , "application/json" , body. into_bytes ( ) )
373378 }
374379 "/profiles/list" => profiles_list_bridge_response ( paths) ,
@@ -387,19 +392,20 @@ pub(crate) fn bridge_response_with_token(
387392 } ;
388393 let req =
389394 register_cancellable_request ( & params, state, "comparing" , 1 , "Comparing images" ) ;
390- let ( mut body, result) =
391- linsync:: image_compare_bridge_response_with_profile ( query, & profile. image ) ;
395+ let ( mut body, result) = linsync:: image_compare_bridge_response_with_profile_and_cancel (
396+ query,
397+ & profile. image ,
398+ req. cancel_checker ( ) ,
399+ ) ;
392400 // If the user hit Stop during the compare, discard the result.
393401 if req. is_cancelled ( ) {
394- remove_cancellable_request ( & req, state) ;
395402 return http_response (
396403 200 ,
397404 "OK" ,
398405 "application/json" ,
399406 br#"{"cancelled":true}"# . to_vec ( ) ,
400407 ) ;
401408 }
402- remove_cancellable_request ( & req, state) ;
403409 let result_for_tab = result. clone ( ) ;
404410 let overlay_path = serde_json:: from_str :: < serde_json:: Value > ( & body)
405411 . ok ( )
@@ -447,7 +453,7 @@ pub(crate) fn bridge_response_with_token(
447453 let req =
448454 register_cancellable_request ( & params, state, "fetching" , 3 , "Fetching webpages" ) ;
449455 set_progress (
450- & req. progress ,
456+ & req. progress ( ) ,
451457 "fetching" ,
452458 1 ,
453459 3 ,
@@ -461,7 +467,6 @@ pub(crate) fn bridge_response_with_token(
461467 // If the user hit Stop during the (potentially slow) fetch/render,
462468 // discard the result and report cancellation.
463469 if req. is_cancelled ( ) {
464- remove_cancellable_request ( & req, state) ;
465470 return http_response (
466471 200 ,
467472 "OK" ,
@@ -470,7 +475,7 @@ pub(crate) fn bridge_response_with_token(
470475 ) ;
471476 }
472477 set_progress (
473- & req. progress ,
478+ & req. progress ( ) ,
474479 "finalizing" ,
475480 2 ,
476481 3 ,
@@ -492,8 +497,7 @@ pub(crate) fn bridge_response_with_token(
492497 state,
493498 ) ;
494499 }
495- set_progress ( & req. progress , "done" , 3 , 3 , String :: new ( ) ) ;
496- remove_cancellable_request ( & req, state) ;
500+ set_progress ( & req. progress ( ) , "done" , 3 , 3 , String :: new ( ) ) ;
497501 http_response ( 200 , "OK" , "application/json" , body. into_bytes ( ) )
498502 }
499503 "/compare/webpage/clear-cache" => {
@@ -1355,34 +1359,17 @@ pub(crate) fn compare_bridge_response(
13551359 // cancel flag so a concurrent `/cancel?id=X` can abort this compare. The
13561360 // flag is registered/removed under the state lock, but the long compare
13571361 // below runs WITHOUT holding the lock, so `/cancel` is never blocked by it.
1358- let ( request_id, progress) =
1359- register_progress_request ( & params, state, "starting" , 0 , "Starting compare" ) ;
1360- let should_cancel: Box < dyn Fn ( ) -> bool > = if let Some ( id) = & request_id {
1361- let flag = Arc :: new ( AtomicBool :: new ( false ) ) ;
1362- if let Ok ( mut state) = state. lock ( ) {
1363- state. compare_cancels . insert ( id. clone ( ) , Arc :: clone ( & flag) ) ;
1364- }
1365- Box :: new ( move || flag. load ( Ordering :: Relaxed ) )
1366- } else {
1367- Box :: new ( || false )
1368- } ;
1362+ let req = register_cancellable_request ( & params, state, "starting" , 0 , "Starting compare" ) ;
13691363
13701364 let maybe_tab = build_tab_for_paths_with_mode_cancellable_and_artifacts (
13711365 Path :: new ( left) ,
13721366 Path :: new ( right) ,
13731367 query_value ( & params, "mode" ) ,
13741368 & options,
1375- & * should_cancel ,
1376- progress,
1369+ req . cancel_checker ( ) ,
1370+ req . progress ( ) ,
13771371 ) ;
13781372
1379- if let Some ( id) = & request_id
1380- && let Ok ( mut state) = state. lock ( )
1381- {
1382- state. compare_cancels . remove ( id) ;
1383- }
1384- remove_progress_request ( request_id. as_deref ( ) , state) ;
1385-
13861373 let Some ( ( tab, artifact_dirs) ) = maybe_tab else {
13871374 // The compare was cancelled — leave the session state untouched.
13881375 return http_response (
@@ -3125,26 +3112,17 @@ pub(crate) fn sessions_save_bridge_response(
31253112 session_file. selected_view = compare_view_mode ( & tab. mode ) ;
31263113 persist_tab_snapshot ( & mut session_file, & tab) ;
31273114 let store = RecentSessionStore :: new ( paths. recent_sessions_file ( ) , recent_limit ( paths) ) ;
3128- let mut recent: RecentSessions = match store. load_or_default ( ) {
3129- Ok ( value) => value,
3130- Err ( err) => {
3131- return bridge_error (
3132- 500 ,
3133- "Internal Server Error" ,
3134- & format ! ( "failed to load sessions: {err}" ) ,
3135- ) ;
3115+ match store. add ( session_file) {
3116+ Ok ( _) => {
3117+ let body = serde_json:: json!( { "ok" : true } ) . to_string ( ) ;
3118+ http_response ( 200 , "OK" , "application/json" , body. into_bytes ( ) )
31363119 }
3137- } ;
3138- recent. sessions . insert ( 0 , session_file) ;
3139- if let Err ( err) = store. save ( & recent) {
3140- return bridge_error (
3120+ Err ( err) => bridge_error (
31413121 500 ,
31423122 "Internal Server Error" ,
31433123 & format ! ( "failed to save session: {err}" ) ,
3144- ) ;
3124+ ) ,
31453125 }
3146- let body = serde_json:: json!( { "ok" : true } ) . to_string ( ) ;
3147- http_response ( 200 , "OK" , "application/json" , body. into_bytes ( ) )
31483126}
31493127
31503128pub ( crate ) fn filters_list_bridge_response ( paths : & AppPaths ) -> Vec < u8 > {
0 commit comments