summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--man5/opentracker.conf.57
-rw-r--r--opentracker.conf.sample7
-rw-r--r--src/ot_clean.c4
-rw-r--r--src/ot_config.c7
-rw-r--r--src/trackerlogic.h5
-rw-r--r--tests/integration/test_config.py21
-rw-r--r--tests/t_clean.c36
7 files changed, 86 insertions, 1 deletions
diff --git a/man5/opentracker.conf.5 b/man5/opentracker.conf.5
index 472d83b..cc4c942 100644
--- a/man5/opentracker.conf.5
+++ b/man5/opentracker.conf.5
@@ -108,6 +108,13 @@ Specifies the user opentracker will setuid to after binding to potentially privi
108.Pp 108.Pp
109.It tracker.redirect_url Ar URL 109.It tracker.redirect_url Ar URL
110Specifies the URL opentracker will redirect to in response to a "GET / HTTP" request. 110Specifies the URL opentracker will redirect to in response to a "GET / HTTP" request.
111.Pp
112.It tracker.empty_torrent_timeout Ar minutes
113Specifies how long a torrent that has no peers left in either family is held for
114its download count alone.
115Defaults to and is limited to 1440 minutes. Lower values save memory at the cost
116of less realiable download counts. A value of zero immediately destroys empty
117torrents.
111.El 118.El
112.Sh TUNING 119.Sh TUNING
113A worker is a thread that blocks in a read until a datagram arrives. Where the 120A worker is a thread that blocks in a read until a datagram arrives. Where the
diff --git a/opentracker.conf.sample b/opentracker.conf.sample
index ba8497f..da0b601 100644
--- a/opentracker.conf.sample
+++ b/opentracker.conf.sample
@@ -186,3 +186,10 @@
186# redirect to another location (shell option -r). 186# redirect to another location (shell option -r).
187# 187#
188# tracker.redirect_url https://your.tracker.local/ 188# tracker.redirect_url https://your.tracker.local/
189
190# VII) How long a torrent that has fallen empty is kept for its download count
191# alone, in minutes. Default and maximum is 1440 minutes (24 hours).
192# Trade-off: lower value less memory usage at the cost of less reliable
193# download counts.
194#
195# tracker.empty_torrent_timeout 60
diff --git a/src/ot_clean.c b/src/ot_clean.c
index 77cb4ba..9e2186f 100644
--- a/src/ot_clean.c
+++ b/src/ot_clean.c
@@ -16,6 +16,8 @@
16#include "ot_vector.h" 16#include "ot_vector.h"
17#include "trackerlogic.h" 17#include "trackerlogic.h"
18 18
19ot_time g_empty_torrent_timeout = OT_TORRENT_TIMEOUT;
20
19/* Returns amount of removed peers */ 21/* Returns amount of removed peers */
20static ssize_t clean_single_bucket(ot_peer *peers, size_t peer_count, size_t peer_size, time_t timedout, size_t *removed_seeders) { 22static ssize_t clean_single_bucket(ot_peer *peers, size_t peer_count, size_t peer_size, time_t timedout, size_t *removed_seeders) {
21 ot_peer *last_peer, *insert_point; 23 ot_peer *last_peer, *insert_point;
@@ -106,7 +108,7 @@ int clean_single_torrent(ot_torrent *torrent) {
106 /* Already empty and nothing arrived before the peer timeout: keep it only 108 /* Already empty and nothing arrived before the peer timeout: keep it only
107 while a down count is worth remembering */ 109 while a down count is worth remembering */
108 if (timedout > OT_PEER_TIMEOUT && !torrent->peer_list6 && !torrent->peer_list4) 110 if (timedout > OT_PEER_TIMEOUT && !torrent->peer_list6 && !torrent->peer_list4)
109 return torrent->down_count ? 0 : 1; 111 return (torrent->down_count && timedout <= g_empty_torrent_timeout) ? 0 : 1;
110 112
111 /* Peers past the peer timeout are gone regardless, which caps how far the 113 /* Peers past the peer timeout are gone regardless, which caps how far the
112 lists are aged in this pass */ 114 lists are aged in this pass */
diff --git a/src/ot_config.c b/src/ot_config.c
index 9c5e8f0..8af6cfc 100644
--- a/src/ot_config.c
+++ b/src/ot_config.c
@@ -192,6 +192,7 @@ typedef enum {
192 CONFIG_UNKNOWN, 192 CONFIG_UNKNOWN,
193 CONFIG_TRACKER_ROOTDIR, 193 CONFIG_TRACKER_ROOTDIR,
194 CONFIG_TRACKER_USER, 194 CONFIG_TRACKER_USER,
195 CONFIG_TRACKER_EMPTY_TIMEOUT,
195 CONFIG_LISTEN_TCP_UDP, 196 CONFIG_LISTEN_TCP_UDP,
196 CONFIG_LISTEN_TCP, 197 CONFIG_LISTEN_TCP,
197 CONFIG_LISTEN_UDP, 198 CONFIG_LISTEN_UDP,
@@ -223,6 +224,7 @@ typedef struct {
223static const config_key config_keys[] = { 224static const config_key config_keys[] = {
224 CONFIG_KEY("tracker.rootdir", CONFIG_TRACKER_ROOTDIR), 225 CONFIG_KEY("tracker.rootdir", CONFIG_TRACKER_ROOTDIR),
225 CONFIG_KEY("tracker.user", CONFIG_TRACKER_USER), 226 CONFIG_KEY("tracker.user", CONFIG_TRACKER_USER),
227 CONFIG_KEY("tracker.empty_torrent_timeout", CONFIG_TRACKER_EMPTY_TIMEOUT),
226 CONFIG_KEY("listen.tcp_udp", CONFIG_LISTEN_TCP_UDP), 228 CONFIG_KEY("listen.tcp_udp", CONFIG_LISTEN_TCP_UDP),
227 CONFIG_KEY("listen.tcp", CONFIG_LISTEN_TCP), 229 CONFIG_KEY("listen.tcp", CONFIG_LISTEN_TCP),
228 CONFIG_KEY("listen.udp", CONFIG_LISTEN_UDP), 230 CONFIG_KEY("listen.udp", CONFIG_LISTEN_UDP),
@@ -316,6 +318,11 @@ static int parse_configfile(char *config_filename) {
316 case CONFIG_TRACKER_USER: 318 case CONFIG_TRACKER_USER:
317 set_config_option(&g_serveruser, p); 319 set_config_option(&g_serveruser, p);
318 break; 320 break;
321 case CONFIG_TRACKER_EMPTY_TIMEOUT:
322 if (!scan_uint(p, &value, UINT_MAX))
323 goto parse_error;
324 g_empty_torrent_timeout = (ot_time)value;
325 break;
319 case CONFIG_LISTEN_TCP_UDP: 326 case CONFIG_LISTEN_TCP_UDP:
320 case CONFIG_LISTEN_TCP: 327 case CONFIG_LISTEN_TCP:
321 case CONFIG_LISTEN_UDP: { 328 case CONFIG_LISTEN_UDP: {
diff --git a/src/trackerlogic.h b/src/trackerlogic.h
index 6b53406..c4b7774 100644
--- a/src/trackerlogic.h
+++ b/src/trackerlogic.h
@@ -44,6 +44,11 @@ typedef struct {
44#define OT_TORRENT_TIMEOUT_HOURS 24 44#define OT_TORRENT_TIMEOUT_HOURS 24
45#define OT_TORRENT_TIMEOUT (60 * OT_TORRENT_TIMEOUT_HOURS) 45#define OT_TORRENT_TIMEOUT (60 * OT_TORRENT_TIMEOUT_HOURS)
46 46
47/* How long a torrent kept only for its download count, with no peers in either
48 family, is held, in minutes. Defaults to OT_TORRENT_TIMEOUT and is lowered by
49 tracker.empty_torrent_timeout. */
50extern ot_time g_empty_torrent_timeout;
51
47#define OT_CLIENT_REQUEST_INTERVAL_RANDOM \ 52#define OT_CLIENT_REQUEST_INTERVAL_RANDOM \
48 (OT_CLIENT_REQUEST_INTERVAL - OT_CLIENT_REQUEST_VARIATION / 2 + (int)(nrand48(ws->rand48_state) % OT_CLIENT_REQUEST_VARIATION)) 53 (OT_CLIENT_REQUEST_INTERVAL - OT_CLIENT_REQUEST_VARIATION / 2 + (int)(nrand48(ws->rand48_state) % OT_CLIENT_REQUEST_VARIATION))
49#define OT_CLIENT_REJECT_INTERVAL_RANDOM \ 54#define OT_CLIENT_REJECT_INTERVAL_RANDOM \
diff --git a/tests/integration/test_config.py b/tests/integration/test_config.py
index 2f6bdc4..fe5cf9d 100644
--- a/tests/integration/test_config.py
+++ b/tests/integration/test_config.py
@@ -49,3 +49,24 @@ class AListenLineThatDoesNotParse(unittest.TestCase):
49 49
50 def test_so_does_an_option_it_does_not_know(self): 50 def test_so_does_an_option_it_does_not_know(self):
51 self.assertIn("want an address", self.refuses("listen.tcp 127.0.0.1:7999 wokers=2\n")) 51 self.assertIn("want an address", self.refuses("listen.tcp 127.0.0.1:7999 wokers=2\n"))
52
53
54class EmptyTorrentTimeout(TrackerTestCase):
55 config = "tracker.empty_torrent_timeout 60\n"
56
57 def test_the_tracker_accepts_the_option_and_serves(self):
58 self.assertEqual(self.tracker.announce(self.hash_for("emptyto"))[b"complete"], 1)
59 self.assertNotIn("Unhandled line", self.tracker.output())
60 self.assertNotIn("Parse error", self.tracker.output())
61
62
63class EmptyTorrentTimeoutThatDoesNotParse(unittest.TestCase):
64 def test_a_non_numeric_value_warns_but_the_tracker_serves(self):
65 """A scalar that will not parse warns and is ignored, unlike a listen
66 line, which stops the tracker."""
67 tracker = Tracker("tracker.empty_torrent_timeout later\n")
68 try:
69 self.assertEqual(tracker.announce(b"emptytobad\0\0\0\0\0\0\0\0\0\0")[b"complete"], 1)
70 self.assertIn("Parse error", tracker.output())
71 finally:
72 tracker._cleanup()
diff --git a/tests/t_clean.c b/tests/t_clean.c
index 2fadd03..0237cc6 100644
--- a/tests/t_clean.c
+++ b/tests/t_clean.c
@@ -122,6 +122,41 @@ static void case_an_idle_swarm_still_ages_out(void) {
122 workstruct_deinit(&ws); 122 workstruct_deinit(&ws);
123} 123}
124 124
125static void case_empty_torrent_timeout_bounds_a_counter_only_torrent(void) {
126 struct ot_workstruct ws;
127 ot_hash hash;
128 ot_torrent *torrent;
129 ot_time saved = g_empty_torrent_timeout;
130
131 test_case("the empty torrent timeout bounds a counter-only torrent's life");
132 memset(hash, 0x33, sizeof(hash));
133 CHECK_INT(workstruct_init(&ws, G_INBUF_SIZE), 0);
134
135 announce(&ws, &hash, 7, 1);
136 torrent = torrent_of(&hash);
137 CHECK(torrent != NULL);
138 torrent->down_count = 1; /* one completed download to remember it by */
139
140 /* Age the only peer out: the torrent falls empty but keeps its count */
141 step_clock_back(torrent, -OT_PEER_TIMEOUT);
142 CHECK_INT(clean_single_torrent(torrent), 0);
143 CHECK_INT(peers_in(&hash), 0);
144
145 /* Carry it a little past a short empty-torrent timeout */
146 step_clock_back(torrent, -10);
147
148 /* The default, matching the torrent timeout, still holds it for the count */
149 g_empty_torrent_timeout = OT_TORRENT_TIMEOUT;
150 CHECK_INT(clean_single_torrent(torrent), 0);
151
152 /* A timeout it has now exceeded lets the next sweep drop it */
153 g_empty_torrent_timeout = OT_PEER_TIMEOUT + 5;
154 CHECK_INT(clean_single_torrent(torrent), 1);
155
156 g_empty_torrent_timeout = saved;
157 workstruct_deinit(&ws);
158}
159
125void test_clean(void) { 160void test_clean(void) {
126 sigset_t hup; 161 sigset_t hup;
127 162
@@ -134,5 +169,6 @@ void test_clean(void) {
134 trackerlogic_init(); 169 trackerlogic_init();
135 case_a_clock_that_went_back_keeps_the_swarm(); 170 case_a_clock_that_went_back_keeps_the_swarm();
136 case_an_idle_swarm_still_ages_out(); 171 case_an_idle_swarm_still_ages_out();
172 case_empty_torrent_timeout_bounds_a_counter_only_torrent();
137 trackerlogic_deinit(); 173 trackerlogic_deinit();
138} 174}