@@ -38,8 +38,7 @@ const SYSTEM_NODE_CATALOG_KEY: [u8; 32] = [
3838const MAX_SYSTEM_NODES : usize = 100 ;
3939const CATALOG_MAX_BYTES : usize = 262_144 ;
4040const NODE_TEST_CONCURRENCY : usize = 4 ;
41- const BACKGROUND_HEALTHY_NODE_TARGET : usize = 10 ;
42- const BACKGROUND_HEALTHY_NODE_MINIMUM : usize = 5 ;
41+ const AUTO_POOL_NODE_TARGET : usize = 10 ;
4342const HEALTH_CHECK_INTERVALS : [ u32 ; 6 ] = [
4443 0 ,
4544 60 ,
@@ -63,7 +62,7 @@ struct CatalogUpdate {
6362 recovery_applied_at : Option < chrono:: DateTime < Utc > > ,
6463}
6564
66- struct BackgroundNodeTestResult {
65+ struct NodePoolTestResult {
6766 tested : Vec < ( String , HealthSummary ) > ,
6867 pool_ids : HashSet < String > ,
6968}
@@ -237,7 +236,7 @@ impl AppCore {
237236 * self . full_node_test_result . lock ( ) = None ;
238237 let nodes = self . nodes ( ) ?;
239238 let old_health = self . health ( ) ?;
240- let result = test_background_node_pool ( & nodes, & old_health, test, |_, _| { } ) ?;
239+ let result = test_node_pool ( & nodes, & old_health, test, |_, _| { } ) ?;
241240 self . persist_test_results (
242241 result. tested ,
243242 AutomaticPoolUpdate :: Replace ( result. pool_ids ) ,
@@ -564,7 +563,7 @@ impl AppCore {
564563 let _run = self
565564 . full_node_test_lock
566565 . try_lock ( )
567- . ok_or_else ( || "全量线路检测正在进行 ,请稍后再试" . to_string ( ) ) ?;
566+ . ok_or_else ( || "线路检测正在进行 ,请稍后再试" . to_string ( ) ) ?;
568567 self . execute_full_node_test_locked ( & on_progress)
569568 }
570569
@@ -614,14 +613,10 @@ impl AppCore {
614613 where
615614 F : Fn ( usize , usize ) + Sync ,
616615 {
617- let nodes: Vec < NodeDefinition > = self
618- . nodes ( ) ?
619- . into_iter ( )
620- . filter ( |node| node. enabled )
621- . collect ( ) ;
616+ let nodes = self . nodes ( ) ?;
622617 let old_health = self . health ( ) ?;
623- let tested = test_nodes_bounded ( & nodes, & old_health, git:: test_node, on_progress) ?;
624- self . persist_test_results ( tested, AutomaticPoolUpdate :: Rebuild )
618+ let result = test_node_pool ( & nodes, & old_health, git:: test_node, on_progress) ?;
619+ self . persist_test_results ( result . tested , AutomaticPoolUpdate :: Rebuild )
625620 }
626621
627622 fn test_background_nodes_locked < F > ( & self , on_progress : & F ) -> Result < Vec < NodeEntry > , String >
@@ -630,7 +625,7 @@ impl AppCore {
630625 {
631626 let nodes = self . nodes ( ) ?;
632627 let old_health = self . health ( ) ?;
633- let result = test_background_node_pool ( & nodes, & old_health, git:: test_node, on_progress) ?;
628+ let result = test_node_pool ( & nodes, & old_health, git:: test_node, on_progress) ?;
634629 self . persist_test_results ( result. tested , AutomaticPoolUpdate :: Replace ( result. pool_ids ) )
635630 }
636631
@@ -689,7 +684,7 @@ impl AppCore {
689684 . is_some_and ( |summary| summary. in_auto_pool && is_usable_health ( summary) )
690685 } )
691686 . count ( )
692- < BACKGROUND_HEALTHY_NODE_MINIMUM )
687+ < AUTO_POOL_NODE_TARGET )
693688 }
694689
695690 fn resume_acceleration_after_catalog_refresh (
@@ -1430,23 +1425,34 @@ fn select_auto_pool(
14301425 }
14311426 }
14321427 for ( node, _) in candidates {
1433- if selected. len ( ) == BACKGROUND_HEALTHY_NODE_TARGET {
1428+ if selected. len ( ) == AUTO_POOL_NODE_TARGET {
14341429 break ;
14351430 }
14361431 selected. insert ( node. id . clone ( ) ) ;
14371432 }
14381433 selected
14391434}
14401435
1441- fn test_background_node_pool < F > (
1436+ fn test_node_pool < F > (
14421437 nodes : & [ NodeDefinition ] ,
14431438 old_health : & HashMap < String , HealthSummary > ,
14441439 test : F ,
14451440 on_progress : impl Fn ( usize , usize ) + Sync ,
1446- ) -> Result < BackgroundNodeTestResult , String >
1441+ ) -> Result < NodePoolTestResult , String >
14471442where
14481443 F : Fn ( & NodeDefinition , & HealthSummary ) -> HealthSummary + Sync ,
14491444{
1445+ let usable_count = Mutex :: new ( 0usize ) ;
1446+ on_progress ( 0 , AUTO_POOL_NODE_TARGET ) ;
1447+ let test_and_report = |node : & NodeDefinition , previous : & HealthSummary | {
1448+ let summary = test ( node, previous) ;
1449+ if is_usable_health ( & summary) {
1450+ let mut usable = usable_count. lock ( ) ;
1451+ * usable += 1 ;
1452+ on_progress ( * usable, AUTO_POOL_NODE_TARGET ) ;
1453+ }
1454+ summary
1455+ } ;
14501456 let maintained: Vec < NodeDefinition > = nodes
14511457 . iter ( )
14521458 . filter ( |node| {
@@ -1455,50 +1461,36 @@ where
14551461 . get ( & node. id )
14561462 . is_some_and ( |summary| summary. in_auto_pool )
14571463 } )
1458- . take ( BACKGROUND_HEALTHY_NODE_TARGET )
1464+ . take ( AUTO_POOL_NODE_TARGET )
14591465 . cloned ( )
14601466 . collect ( ) ;
14611467 let selected: HashSet < String > = maintained. iter ( ) . map ( |node| node. id . clone ( ) ) . collect ( ) ;
1462- let mut tested = test_nodes_bounded ( & maintained, old_health, & test, |completed, total| {
1463- on_progress ( completed, total)
1464- } ) ?;
1468+ let mut tested = test_nodes_bounded ( & maintained, old_health, & test_and_report, |_, _| { } ) ?;
14651469
1466- let mut usable = tested
1467- . iter ( )
1468- . filter ( |( _, summary) | is_usable_health ( summary) )
1469- . count ( ) ;
1470- if usable >= BACKGROUND_HEALTHY_NODE_MINIMUM {
1470+ let mut usable = * usable_count. lock ( ) ;
1471+ if usable >= AUTO_POOL_NODE_TARGET {
14711472 let pool_ids = tested
14721473 . iter ( )
14731474 . filter ( |( _, summary) | is_usable_health ( summary) )
14741475 . map ( |( node_id, _) | node_id. clone ( ) )
14751476 . collect ( ) ;
1476- return Ok ( BackgroundNodeTestResult { tested, pool_ids } ) ;
1477+ return Ok ( NodePoolTestResult { tested, pool_ids } ) ;
14771478 }
14781479
14791480 let discovery: Vec < NodeDefinition > = nodes
14801481 . iter ( )
1481- . filter ( |node| node. enabled && node . built_in && !selected. contains ( & node. id ) )
1482+ . filter ( |node| node. enabled && !selected. contains ( & node. id ) )
14821483 . cloned ( )
14831484 . collect ( ) ;
14841485 let mut cursor = 0 ;
1485- let mut completed = tested. len ( ) ;
1486- while usable < BACKGROUND_HEALTHY_NODE_TARGET && cursor < discovery. len ( ) {
1487- let remaining = BACKGROUND_HEALTHY_NODE_TARGET - usable;
1486+ while usable < AUTO_POOL_NODE_TARGET && cursor < discovery. len ( ) {
1487+ let remaining = AUTO_POOL_NODE_TARGET - usable;
14881488 let batch_len = NODE_TEST_CONCURRENCY
14891489 . min ( remaining)
14901490 . min ( discovery. len ( ) - cursor) ;
14911491 let batch = & discovery[ cursor..cursor + batch_len] ;
1492- let total = completed + batch_len;
1493- let batch_results = test_nodes_bounded ( batch, old_health, & test, |batch_completed, _| {
1494- on_progress ( completed + batch_completed, total)
1495- } ) ?;
1496- for ( _, summary) in & batch_results {
1497- if is_usable_health ( summary) {
1498- usable += 1 ;
1499- }
1500- }
1501- completed += batch_results. len ( ) ;
1492+ let batch_results = test_nodes_bounded ( batch, old_health, & test_and_report, |_, _| { } ) ?;
1493+ usable = * usable_count. lock ( ) ;
15021494 tested. extend ( batch_results) ;
15031495 cursor += batch_len;
15041496 }
@@ -1507,7 +1499,7 @@ where
15071499 . filter ( |( _, summary) | is_usable_health ( summary) )
15081500 . map ( |( node_id, _) | node_id. clone ( ) )
15091501 . collect ( ) ;
1510- Ok ( BackgroundNodeTestResult { tested, pool_ids } )
1502+ Ok ( NodePoolTestResult { tested, pool_ids } )
15111503}
15121504
15131505fn test_nodes_bounded < F > (
@@ -2059,7 +2051,7 @@ mod tests {
20592051 core. test_all_nodes_with_progress( |_, _| { } )
20602052 . err( )
20612053 . as_deref( ) ,
2062- Some ( "全量线路检测正在进行 ,请稍后再试" )
2054+ Some ( "线路检测正在进行 ,请稍后再试" )
20632055 ) ;
20642056 drop ( active) ;
20652057 assert ! ( core. full_node_test_lock. try_lock( ) . is_some( ) ) ;
@@ -2272,11 +2264,10 @@ mod tests {
22722264 . collect ( ) ;
22732265
22742266 let result =
2275- test_background_node_pool ( & nodes, & health, |_, previous| previous. clone ( ) , |_, _| { } )
2276- . unwrap ( ) ;
2267+ test_node_pool ( & nodes, & health, |_, previous| previous. clone ( ) , |_, _| { } ) . unwrap ( ) ;
22772268
2278- assert_eq ! ( result. tested. len( ) , BACKGROUND_HEALTHY_NODE_TARGET ) ;
2279- assert_eq ! ( result. pool_ids. len( ) , BACKGROUND_HEALTHY_NODE_TARGET ) ;
2269+ assert_eq ! ( result. tested. len( ) , AUTO_POOL_NODE_TARGET ) ;
2270+ assert_eq ! ( result. pool_ids. len( ) , AUTO_POOL_NODE_TARGET ) ;
22802271 assert_eq ! (
22812272 result
22822273 . tested
@@ -2290,7 +2281,7 @@ mod tests {
22902281 }
22912282
22922283 #[ test]
2293- fn background_health_check_discovers_until_ten_when_pool_falls_below_five ( ) {
2284+ fn node_pool_detection_discovers_until_ten_from_a_partial_pool ( ) {
22942285 let nodes: Vec < NodeDefinition > = ( 0 ..30 )
22952286 . map ( |index| NodeDefinition {
22962287 id : format ! ( "node-{index}" ) ,
@@ -2315,7 +2306,7 @@ mod tests {
23152306 } )
23162307 . collect ( ) ;
23172308
2318- let result = test_background_node_pool (
2309+ let result = test_node_pool (
23192310 & nodes,
23202311 & health,
23212312 |_, _| HealthSummary {
@@ -2326,32 +2317,32 @@ mod tests {
23262317 )
23272318 . unwrap ( ) ;
23282319
2329- assert_eq ! ( result. tested. len( ) , BACKGROUND_HEALTHY_NODE_TARGET ) ;
2330- assert_eq ! ( result. pool_ids. len( ) , BACKGROUND_HEALTHY_NODE_TARGET ) ;
2320+ assert_eq ! ( result. tested. len( ) , AUTO_POOL_NODE_TARGET ) ;
2321+ assert_eq ! ( result. pool_ids. len( ) , AUTO_POOL_NODE_TARGET ) ;
23312322 assert_eq ! (
23322323 result
23332324 . tested
23342325 . iter( )
23352326 . filter( |( _, summary) | is_usable_health( summary) )
23362327 . count( ) ,
2337- BACKGROUND_HEALTHY_NODE_TARGET
2328+ AUTO_POOL_NODE_TARGET
23382329 ) ;
23392330 }
23402331
23412332 #[ test]
2342- fn background_health_check_does_not_discover_when_five_nodes_remain_usable ( ) {
2333+ fn node_pool_detection_fills_to_ten_with_custom_nodes ( ) {
23432334 let nodes: Vec < NodeDefinition > = ( 0 ..20 )
23442335 . map ( |index| NodeDefinition {
23452336 id : format ! ( "node-{index}" ) ,
23462337 name : format ! ( "Node {index}" ) ,
23472338 rewrite_base : format ! ( "https://proxy-{index}.example/https://github.com/" ) ,
23482339 enabled : true ,
2349- built_in : true ,
2340+ built_in : false ,
23502341 } )
23512342 . collect ( ) ;
23522343 let health = nodes
23532344 . iter ( )
2354- . take ( BACKGROUND_HEALTHY_NODE_MINIMUM )
2345+ . take ( 5 )
23552346 . map ( |node| {
23562347 (
23572348 node. id . clone ( ) ,
@@ -2364,12 +2355,66 @@ mod tests {
23642355 } )
23652356 . collect ( ) ;
23662357
2367- let result =
2368- test_background_node_pool ( & nodes, & health, |_, previous| previous. clone ( ) , |_, _| { } )
2369- . unwrap ( ) ;
2358+ let result = test_node_pool (
2359+ & nodes,
2360+ & health,
2361+ |_, _| HealthSummary {
2362+ status : NodeStatus :: Available ,
2363+ ..HealthSummary :: default ( )
2364+ } ,
2365+ |_, _| { } ,
2366+ )
2367+ . unwrap ( ) ;
2368+
2369+ assert_eq ! ( result. tested. len( ) , AUTO_POOL_NODE_TARGET ) ;
2370+ assert_eq ! ( result. pool_ids. len( ) , AUTO_POOL_NODE_TARGET ) ;
2371+ }
23702372
2371- assert_eq ! ( result. tested. len( ) , BACKGROUND_HEALTHY_NODE_MINIMUM ) ;
2372- assert_eq ! ( result. pool_ids. len( ) , BACKGROUND_HEALTHY_NODE_MINIMUM ) ;
2373+ #[ test]
2374+ fn node_pool_detection_continues_past_failures_and_stops_at_ten_usable_nodes ( ) {
2375+ let nodes: Vec < NodeDefinition > = ( 0 ..30 )
2376+ . map ( |index| NodeDefinition {
2377+ id : format ! ( "node-{index}" ) ,
2378+ name : format ! ( "Node {index}" ) ,
2379+ rewrite_base : format ! ( "https://proxy-{index}.example/https://github.com/" ) ,
2380+ enabled : true ,
2381+ built_in : true ,
2382+ } )
2383+ . collect ( ) ;
2384+
2385+ let progress = Mutex :: new ( Vec :: new ( ) ) ;
2386+ let result = test_node_pool (
2387+ & nodes,
2388+ & HashMap :: new ( ) ,
2389+ |node, _| HealthSummary {
2390+ status : if node
2391+ . id
2392+ . strip_prefix ( "node-" )
2393+ . unwrap ( )
2394+ . parse :: < usize > ( )
2395+ . unwrap ( )
2396+ % 2
2397+ == 0
2398+ {
2399+ NodeStatus :: Available
2400+ } else {
2401+ NodeStatus :: Unavailable
2402+ } ,
2403+ ..HealthSummary :: default ( )
2404+ } ,
2405+ |completed, total| progress. lock ( ) . push ( ( completed, total) ) ,
2406+ )
2407+ . unwrap ( ) ;
2408+
2409+ assert_eq ! ( result. tested. len( ) , 19 ) ;
2410+ assert_eq ! ( result. pool_ids. len( ) , AUTO_POOL_NODE_TARGET ) ;
2411+ assert_eq ! ( result. tested. last( ) . unwrap( ) . 0 , "node-18" ) ;
2412+ assert_eq ! (
2413+ progress. into_inner( ) ,
2414+ ( 0 ..=AUTO_POOL_NODE_TARGET )
2415+ . map( |completed| ( completed, AUTO_POOL_NODE_TARGET ) )
2416+ . collect:: <Vec <_>>( )
2417+ ) ;
23732418 }
23742419
23752420 #[ test]
@@ -2413,7 +2458,7 @@ mod tests {
24132458 . map ( |( node_id, _) | node_id. as_str ( ) )
24142459 . collect ( ) ;
24152460
2416- assert_eq ! ( pool_ids. len( ) , BACKGROUND_HEALTHY_NODE_TARGET ) ;
2461+ assert_eq ! ( pool_ids. len( ) , AUTO_POOL_NODE_TARGET ) ;
24172462 assert ! ( !pool_ids. contains( "node-10" ) ) ;
24182463
24192464 core. persist_test_results (
@@ -2428,7 +2473,7 @@ mod tests {
24282473 . values( )
24292474 . filter( |summary| summary. in_auto_pool)
24302475 . count( ) ,
2431- BACKGROUND_HEALTHY_NODE_TARGET
2476+ AUTO_POOL_NODE_TARGET
24322477 ) ;
24332478 }
24342479
@@ -2723,11 +2768,15 @@ mod tests {
27232768 . collect:: <Vec <_>>( )
27242769 ) ;
27252770 let progress = progress. into_inner ( ) ;
2726- let total = core. nodes ( ) . unwrap ( ) . len ( ) ;
2771+ let usable = owner_result
2772+ . iter ( )
2773+ . filter ( |node| is_usable_health ( & node. health ) )
2774+ . count ( ) ;
2775+ assert ! ( usable <= AUTO_POOL_NODE_TARGET ) ;
27272776 assert_eq ! (
27282777 progress,
2729- ( 0 ..=total )
2730- . map( |completed| ( completed, total ) )
2778+ ( 0 ..=usable )
2779+ . map( |completed| ( completed, AUTO_POOL_NODE_TARGET ) )
27312780 . collect:: <Vec <_>>( )
27322781 ) ;
27332782
0 commit comments