Skip to content

Commit aabdd7c

Browse files
committed
fix(emule): defer cold-start nodes.dat download to first peer connection
The startup download fired immediately, so in a container whose network isn't ready yet it failed and — having no retry — left Kad unseeded until the next restart. Move the fetch to the first PeerConnected event (libp2p has proven outbound connectivity by then) and retry on later connections until it succeeds: mark ready only on success, release the in-flight guard on failure. Purely event-driven, no timers or polling; the one-time first-run delay is a few seconds. Restarts are unaffected (start_kad_task already seeds from an existing nodes.dat).
1 parent 2e1938e commit aabdd7c

1 file changed

Lines changed: 65 additions & 34 deletions

File tree

‎rucio-daemon/src/lib.rs‎

Lines changed: 65 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)