swarm: re-announce promptly when the swarm is starved

The peer top-up loop re-announced to trackers only at the full advertised
interval (~30 min), so a torrent that dropped to a handful of peers would
sit there for up to half an hour with only the 5-minute DHT refresh to
help — looking like the re-announce system was dead.

Now, when fewer than LOW_PEER_THRESHOLD (10) peers are connected, the next
tracker announce is scheduled at the tracker's min_interval floor (never
below 60s) instead of the full interval, so a thin swarm actually tries to
recover. Once peers recover the full interval is used again. Capture the
tracker's min_interval (was being dropped) to stay announce-compliant.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ookami125 2026-06-24 20:04:24 -04:00
parent a0265cc1ea
commit 096535292d
3 changed files with 38 additions and 14 deletions

View file

@ -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) {

View file

@ -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;

View file

@ -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;