diff --git a/apps/swarm/main.c b/apps/swarm/main.c index 755320f..da0eea6 100644 --- a/apps/swarm/main.c +++ b/apps/swarm/main.c @@ -58,6 +58,10 @@ static uint32_t target_peer_count(void) { return (uint32_t)NAUT_MIN(value, MAX_TARGET_PEERS); } +/* Below this many connected peers the swarm is "starved": re-announce as often + * as the tracker's min interval allows instead of waiting the full interval. */ +#define LOW_PEER_THRESHOLD 10 + static double tracker_delay_seconds(int32_t interval) { if (interval <= 0) return TRACKER_DEFAULT_INTERVAL; if (interval < (int32_t)TRACKER_MIN_INTERVAL) @@ -65,6 +69,20 @@ static double tracker_delay_seconds(int32_t interval) { return (double)interval; } +/* Seconds to wait before the next tracker announce. Normally the tracker's full + * advertised interval, but when the swarm is starved (< LOW_PEER_THRESHOLD + * connected peers) we re-announce sooner — down to the tracker's min_interval, + * never below our 60s floor — so a thin swarm can actually recover. */ +static double next_announce_delay(int32_t interval, int32_t min_interval, + uint32_t peers_connected) { + double full = interval > 0 ? tracker_delay_seconds(interval) + : TRACKER_FAILURE_RETRY_INTERVAL; + if (peers_connected >= LOW_PEER_THRESHOLD) return full; + double floor_s = min_interval > 0 ? (double)min_interval : TRACKER_MIN_INTERVAL; + if (floor_s < TRACKER_MIN_INTERVAL) floor_s = TRACKER_MIN_INTERVAL; + return floor_s < full ? floor_s : full; +} + static void random_bytes(uint8_t *output, size_t length) { int fd = open("/dev/urandom", O_RDONLY); size_t offset = 0; @@ -385,6 +403,7 @@ static bool discover_trackers(const uint8_t info_hash[20], uint64_t total_length naut_tracker_event event, endpoint_t **eps, size_t *neps, size_t *cap, int32_t *announce_interval, + int32_t *announce_min_interval, naut_swarm_tracker_stats *tracker_stats, uint32_t tracker_count) { naut_announce_req req; @@ -399,6 +418,7 @@ static bool discover_trackers(const uint8_t info_hash[20], uint64_t total_length req.numwant = 100; memcpy(&req.key, peerid + 8, sizeof req.key); if (announce_interval) *announce_interval = 0; + if (announce_min_interval) *announce_min_interval = 0; size_t tier_start = 0; while (tier_start < num_trackers) { @@ -451,6 +471,8 @@ static bool discover_trackers(const uint8_t info_hash[20], uint64_t total_length tier_succeeded = true; if (announce_interval && response.interval > 0) *announce_interval = response.interval; + if (announce_min_interval && response.min_interval > 0) + *announce_min_interval = response.min_interval; if (tracker_stat) { tracker_set_status(tracker_stat, "working", ""); tracker_stat->seeds = response.seeders; @@ -574,7 +596,7 @@ naut_err naut_swarm_run(const naut_swarm_config *config) { endpoint_t *endpoints = NULL; size_t neps = 0, epcap = 0; uint32_t target_peers = target_peer_count(); - int32_t tracker_interval = 0; + int32_t tracker_interval = 0, tracker_min_interval = 0; naut_swarm_tracker_stats tracker_stats[NAUT_SWARM_MAX_TRACKER_STATS]; uint32_t tracker_count = 0; if (config->num_peers > 0) { @@ -608,8 +630,8 @@ naut_err naut_swarm_run(const naut_swarm_config *config) { if (!discover_trackers(hash, 0, trackers, num_trackers, tracker_tiers, peerid, 0, 0, NAUT_TEV_STARTED, &endpoints, &neps, &epcap, - &tracker_interval, tracker_stats, - tracker_count) || + &tracker_interval, &tracker_min_interval, + tracker_stats, tracker_count) || (neps < target_peers && !discover_dht(hash, &endpoints, &neps, &epcap))) { NAUT_ERROR("out of memory collecting discovered peers"); @@ -731,8 +753,8 @@ naut_err naut_swarm_run(const naut_swarm_config *config) { mi.tracker_tiers, peerid, resumed_bytes, resumed_left, NAUT_TEV_STARTED, &endpoints, &neps, &epcap, - &tracker_interval, tracker_stats, - tracker_count) || + &tracker_interval, &tracker_min_interval, + tracker_stats, tracker_count) || (neps < target_peers && !discover_dht(mi.infohash_v1, &endpoints, &neps, &epcap))) { NAUT_ERROR("out of memory collecting discovered peers"); @@ -787,10 +809,11 @@ naut_err naut_swarm_run(const naut_swarm_config *config) { feed_engine(eng, torrent_id, endpoints, neps, &fed); double t0 = now(); + /* Use the discovered endpoint count as the initial peer proxy: if we start + * thin, schedule a quick re-announce instead of waiting the full interval. */ double next_tracker_announce = - t0 + (tracker_interval > 0 - ? tracker_delay_seconds(tracker_interval) - : TRACKER_FAILURE_RETRY_INTERVAL); + t0 + next_announce_delay(tracker_interval, tracker_min_interval, + (uint32_t)neps); double next_dht_lookup = t0 + DHT_REFRESH_INTERVAL; naut_err run_error = NAUT_OK; bool cancelled = false; @@ -827,21 +850,20 @@ naut_err naut_swarm_run(const naut_swarm_config *config) { uint64_t downloaded = naut_download_bytes_done(d); uint64_t left = (uint64_t)mi.total_length > downloaded ? (uint64_t)mi.total_length - downloaded : 0; - int32_t interval = 0; + int32_t interval = 0, min_interval = 0; if (!discover_trackers(mi.infohash_v1, (uint64_t)mi.total_length, mi.trackers, mi.num_trackers, mi.tracker_tiers, peerid, downloaded, left, NAUT_TEV_NONE, &endpoints, &neps, &epcap, - &interval, tracker_stats, - tracker_count)) { + &interval, &min_interval, + tracker_stats, tracker_count)) { run_error = NAUT_ERR_NOMEM; break; } - next_tracker_announce = t + (interval > 0 - ? tracker_delay_seconds(interval) - : TRACKER_FAILURE_RETRY_INTERVAL); + next_tracker_announce = t + next_announce_delay( + interval, min_interval, ts.peers_connected); feed_engine(eng, torrent_id, endpoints, neps, &fed); } if (t >= next_dht_lookup) { diff --git a/include/naut/tracker.h b/include/naut/tracker.h index ea16ee9..76ed8bb 100644 --- a/include/naut/tracker.h +++ b/include/naut/tracker.h @@ -29,6 +29,7 @@ typedef struct { typedef struct { int32_t interval; + int32_t min_interval; /* tracker's floor, 0 if not advertised */ int32_t seeders, leechers; /* -1 if absent */ naut_peer_addr *peers; size_t num_peers; diff --git a/src/discovery/tracker_client.c b/src/discovery/tracker_client.c index 97d1fe5..ef975be 100644 --- a/src/discovery/tracker_client.c +++ b/src/discovery/tracker_client.c @@ -51,6 +51,7 @@ static naut_err collect_peers(const tracker_peer *peers, size_t count, const tracker_announce_response *resp, naut_tracker_response *out) { out->interval = (int32_t)resp->interval; + out->min_interval = (int32_t)resp->min_interval; out->seeders = (int32_t)resp->complete; out->leechers = (int32_t)resp->incomplete; out->peers = NULL;