@@ -765,40 +765,13 @@ pub async fn run_until<F: std::future::Future<Output = ()>>(
765765 // Resume any downloads interrupted by a previous crash or restart.
766766 engine. resume_interrupted ( ) . await ;
767767
768- // --- eMule: ensure nodes.dat is present (download if missing) -----------
769- // On a cold start (no nodes.dat, no kad_cache.dat) the Kad2 routing table
770- // is empty. We download nodes.dat in the background and, once it lands on
771- // disk, immediately feed its contacts into the running Kad2 task so the
772- // node starts connecting to the eMule network without waiting for the first
773- // download request.
774- #[ cfg( feature = "emule-compat" ) ]
775- if config. emule . enabled {
776- let save_path = crate :: emule:: effective_nodes_dat_path ( & config) ;
777- if !save_path. exists ( ) {
778- let kad_cold = kad_handle. clone ( ) ;
779- let config_cold = config. clone ( ) ;
780- let url = crate :: emule:: effective_nodes_dat_url ( & config) ;
781- tokio:: spawn ( async move {
782- info ! ( path = %save_path. display( ) , url = %url, "nodes.dat not found — downloading in background" ) ;
783- match crate :: emule:: bootstrap_nodes_dat ( & save_path, & url) . await {
784- Ok ( n) => {
785- info ! ( contacts = n, path = %save_path. display( ) , "nodes.dat downloaded" ) ;
786- // Feed the fresh contacts into the live Kad2 task so it
787- // starts connecting immediately (cold-start bootstrap).
788- let seeds = crate :: emule:: load_kad_seeds ( & config_cold, 200 ) ;
789- if !seeds. is_empty ( ) {
790- let seeded = kad_cold. bootstrap ( seeds) . await ;
791- info ! ( contacts = seeded, "Kad2 cold-start bootstrap complete" ) ;
792- }
793- }
794- // `{e:#}` prints the whole anyhow cause chain on one line
795- // (e.g. the DNS/connection error), not just the outermost
796- // context — essential for diagnosing container/network issues.
797- Err ( e) => warn ! ( "Failed to download nodes.dat: {e:#}" ) ,
798- }
799- } ) ;
800- }
801- }
768+ // On a cold start (no nodes.dat) the Kad2 routing table is empty and we must
769+ // download nodes.dat. We deliberately do NOT fetch it here at startup: in a
770+ // container the network is often not ready yet, so an immediate attempt fails
771+ // and (having no retry) leaves Kad unseeded until the next restart. Instead
772+ // the download is deferred to the first `PeerConnected` event — by then the
773+ // libp2p stack has proven outbound connectivity — and retried on later peer
774+ // connections until it succeeds (see the event loop below).
802775
803776 // --- eMule: resume interrupted downloads --------------------------------
804777 #[ cfg( feature = "emule-compat" ) ]
@@ -922,6 +895,17 @@ pub async fn run_until<F: std::future::Future<Output = ()>>(
922895 // table by then) so shares actually land in the DHT without waiting for the
923896 // ~12-hour reprovide tick.
924897 let mut reannounced_after_connect = false ;
898+ // First-cold-start nodes.dat fetch, deferred to real connectivity: attempted
899+ // when the first peer connects (network proven up) and retried on later peer
900+ // connections until it succeeds. `ready` starts true when there is nothing to
901+ // do (eMule off, or nodes.dat already on disk) so the handler is a no-op then;
902+ // `in_flight` prevents overlapping downloads.
903+ #[ cfg( feature = "emule-compat" ) ]
904+ let nodes_dat_ready = std:: sync:: Arc :: new ( std:: sync:: atomic:: AtomicBool :: new (
905+ !config. emule . enabled || crate :: emule:: effective_nodes_dat_path ( & config) . exists ( ) ,
906+ ) ) ;
907+ #[ cfg( feature = "emule-compat" ) ]
908+ let nodes_dat_in_flight = std:: sync:: Arc :: new ( std:: sync:: atomic:: AtomicBool :: new ( false ) ) ;
925909 // Whether the last UploadProgress push carried any rows, so we can emit one
926910 // empty snapshot when uploads drain (clearing the client's Uploads tab)
927911 // without streaming an empty list every idle second.
@@ -1389,6 +1373,53 @@ pub async fn run_until<F: std::future::Future<Output = ()>>(
13891373 }
13901374 } ) ;
13911375 }
1376+ // Cold-start nodes.dat fetch, now that connectivity is
1377+ // proven. Claim the in-flight slot atomically so only one
1378+ // download runs; on success mark ready (done for good), on
1379+ // failure release the slot so a later PeerConnected retries.
1380+ #[ cfg( feature = "emule-compat" ) ]
1381+ if !nodes_dat_ready. load( std:: sync:: atomic:: Ordering :: Relaxed )
1382+ && nodes_dat_in_flight
1383+ . compare_exchange(
1384+ false ,
1385+ true ,
1386+ std:: sync:: atomic:: Ordering :: AcqRel ,
1387+ std:: sync:: atomic:: Ordering :: Relaxed ,
1388+ )
1389+ . is_ok( )
1390+ {
1391+ let kad_cold = kad_handle. clone( ) ;
1392+ let config_cold = config. clone( ) ;
1393+ let url = crate :: emule:: effective_nodes_dat_url( & config) ;
1394+ let save_path = crate :: emule:: effective_nodes_dat_path( & config) ;
1395+ let ready = std:: sync:: Arc :: clone( & nodes_dat_ready) ;
1396+ let in_flight = std:: sync:: Arc :: clone( & nodes_dat_in_flight) ;
1397+ tokio:: spawn( async move {
1398+ info!( path = %save_path. display( ) , url = %url, "nodes.dat not found — downloading now that a peer has connected" ) ;
1399+ match crate :: emule:: bootstrap_nodes_dat( & save_path, & url) . await {
1400+ Ok ( n) => {
1401+ info!( contacts = n, path = %save_path. display( ) , "nodes.dat downloaded" ) ;
1402+ // Feed the fresh contacts into the live Kad2
1403+ // task so it starts connecting immediately.
1404+ let seeds = crate :: emule:: load_kad_seeds( & config_cold, 200 ) ;
1405+ if !seeds. is_empty( ) {
1406+ let seeded = kad_cold. bootstrap( seeds) . await ;
1407+ info!( contacts = seeded, "Kad2 cold-start bootstrap complete" ) ;
1408+ }
1409+ // Done for good; leave in_flight set so no
1410+ // further attempts run.
1411+ ready. store( true , std:: sync:: atomic:: Ordering :: Relaxed ) ;
1412+ }
1413+ // `{e:#}` prints the whole anyhow cause chain
1414+ // (DNS/connection/status), not just the outer
1415+ // context. Release the slot to retry later.
1416+ Err ( e) => {
1417+ warn!( "Failed to download nodes.dat: {e:#} — will retry when another peer connects" ) ;
1418+ in_flight. store( false , std:: sync:: atomic:: Ordering :: Release ) ;
1419+ }
1420+ }
1421+ } ) ;
1422+ }
13921423 }
13931424 Some ( node:: messages:: NodeEvent :: PeerDisconnected { peer_id } ) => {
13941425 let mut ns = node_status. write( ) . await ;
0 commit comments