diff options
| -rw-r--r-- | src/ot_fullscrape.c | 2 | ||||
| -rw-r--r-- | src/trackerlogic.c | 15 | ||||
| -rw-r--r-- | src/trackerlogic.h | 12 | ||||
| -rw-r--r-- | tests/integration/harness.py | 5 | ||||
| -rw-r--r-- | tests/integration/test_fullscrape.py | 12 | ||||
| -rw-r--r-- | tests/integration/test_statefile.py | 53 |
6 files changed, 81 insertions, 18 deletions
diff --git a/src/ot_fullscrape.c b/src/ot_fullscrape.c index 895d7d2..85be65d 100644 --- a/src/ot_fullscrape.c +++ b/src/ot_fullscrape.c | |||
| @@ -131,7 +131,7 @@ static void fullscrape_write_one( ot_tasktype mode, char **cursor, ot_torrent *t | |||
| 131 | case TASK_FULLSCRAPE_TRACKERSTATE: | 131 | case TASK_FULLSCRAPE_TRACKERSTATE: |
| 132 | hex_from_hash(r, *hash); | 132 | hex_from_hash(r, *hash); |
| 133 | r += 2 * sizeof(ot_hash); | 133 | r += 2 * sizeof(ot_hash); |
| 134 | fmt_append(&r, r_end, ":%zd:%zd\n", torrent->peer_list6->base, down_count); | 134 | fmt_append(&r, r_end, ":%zd:%zd\n", (ssize_t)time_since_minutes(torrent->peer_list6->base), down_count); |
| 135 | break; | 135 | break; |
| 136 | } | 136 | } |
| 137 | *cursor = r; | 137 | *cursor = r; |
diff --git a/src/trackerlogic.c b/src/trackerlogic.c index 8e30a48..60d5b18 100644 --- a/src/trackerlogic.c +++ b/src/trackerlogic.c | |||
| @@ -38,10 +38,17 @@ void free_peerlist(ot_peerlist *peer_list) { | |||
| 38 | free(peer_list); | 38 | free(peer_list); |
| 39 | } | 39 | } |
| 40 | 40 | ||
| 41 | void add_torrent_from_saved_state(ot_hash const hash, ot_time base, size_t down_count) { | 41 | /* How many minutes before this moment the torrent was last touched, which is |
| 42 | what a state dump records. */ | ||
| 43 | void add_torrent_from_saved_state(ot_hash const hash, ot_time age, size_t down_count) { | ||
| 42 | int exactmatch; | 44 | int exactmatch; |
| 43 | ot_torrent *torrent; | 45 | ot_torrent *torrent; |
| 44 | ot_vector *torrents_list = mutex_bucket_lock_by_hash(hash); | 46 | ot_vector *torrents_list; |
| 47 | |||
| 48 | if (age > OT_TORRENT_TIMEOUT) | ||
| 49 | return; | ||
| 50 | |||
| 51 | torrents_list = mutex_bucket_lock_by_hash(hash); | ||
| 45 | 52 | ||
| 46 | if (!accesslist_hashisvalid(hash)) { | 53 | if (!accesslist_hashisvalid(hash)) { |
| 47 | mutex_bucket_unlock_by_hash(hash, 0); | 54 | mutex_bucket_unlock_by_hash(hash, 0); |
| @@ -66,8 +73,8 @@ void add_torrent_from_saved_state(ot_hash const hash, ot_time base, size_t down_ | |||
| 66 | 73 | ||
| 67 | memset(torrent->peer_list6, 0, sizeof(ot_peerlist)); | 74 | memset(torrent->peer_list6, 0, sizeof(ot_peerlist)); |
| 68 | memset(torrent->peer_list4, 0, sizeof(ot_peerlist)); | 75 | memset(torrent->peer_list4, 0, sizeof(ot_peerlist)); |
| 69 | torrent->peer_list6->base = base; | 76 | torrent->peer_list6->base = time_now_minutes() - age; |
| 70 | torrent->peer_list4->base = base; | 77 | torrent->peer_list4->base = time_now_minutes() - age; |
| 71 | torrent->peer_list6->down_count = down_count; | 78 | torrent->peer_list6->down_count = down_count; |
| 72 | torrent->peer_list4->down_count = down_count; | 79 | torrent->peer_list4->down_count = down_count; |
| 73 | 80 | ||
diff --git a/src/trackerlogic.h b/src/trackerlogic.h index c8d859c..f6aa8cc 100644 --- a/src/trackerlogic.h +++ b/src/trackerlogic.h | |||
| @@ -76,12 +76,12 @@ typedef struct { | |||
| 76 | on startup */ | 76 | on startup */ |
| 77 | #define RANDOMTORRENTS (1024 * 1024 * 1) | 77 | #define RANDOMTORRENTS (1024 * 1024 * 1) |
| 78 | 78 | ||
| 79 | #if defined(CLOCK_REALTIME_COARSE) | 79 | #if defined(CLOCK_MONOTONIC_COARSE) |
| 80 | #define OT_CLOCK_CHEAP CLOCK_REALTIME_COARSE /* Linux */ | 80 | #define OT_CLOCK_CHEAP CLOCK_MONOTONIC_COARSE /* Linux and FreeBSD */ |
| 81 | #elif defined(CLOCK_REALTIME_FAST) | 81 | #elif defined(CLOCK_MONOTONIC_FAST) |
| 82 | #define OT_CLOCK_CHEAP CLOCK_REALTIME_FAST /* the BSDs */ | 82 | #define OT_CLOCK_CHEAP CLOCK_MONOTONIC_FAST /* the other BSDs */ |
| 83 | #else | 83 | #else |
| 84 | #define OT_CLOCK_CHEAP CLOCK_REALTIME /* macOS and whatever else */ | 84 | #define OT_CLOCK_CHEAP CLOCK_MONOTONIC /* macOS and whatever else */ |
| 85 | #endif | 85 | #endif |
| 86 | 86 | ||
| 87 | static inline ot_time time_now_seconds(void) { | 87 | static inline ot_time time_now_seconds(void) { |
| @@ -285,7 +285,7 @@ size_t add_peer_to_torrent_and_return_peers(PROTO_FLAG proto, struct ot_workstru | |||
| 285 | size_t remove_peer_from_torrent(PROTO_FLAG proto, struct ot_workstruct *ws); | 285 | size_t remove_peer_from_torrent(PROTO_FLAG proto, struct ot_workstruct *ws); |
| 286 | void return_tcp_scrape_for_torrent(ot_hash const *hash_list, size_t amount, char **reply, const char *reply_end); | 286 | void return_tcp_scrape_for_torrent(ot_hash const *hash_list, size_t amount, char **reply, const char *reply_end); |
| 287 | size_t return_udp_scrape_for_torrent(ot_hash const hash, char *reply); | 287 | size_t return_udp_scrape_for_torrent(ot_hash const hash, char *reply); |
| 288 | void add_torrent_from_saved_state(ot_hash const hash, ot_time base, size_t down_count); | 288 | void add_torrent_from_saved_state(ot_hash const hash, ot_time age, size_t down_count); |
| 289 | #ifdef _DEBUG_RANDOMTORRENTS | 289 | #ifdef _DEBUG_RANDOMTORRENTS |
| 290 | void trackerlogic_add_random_torrents(size_t amount); | 290 | void trackerlogic_add_random_torrents(size_t amount); |
| 291 | #endif | 291 | #endif |
diff --git a/tests/integration/harness.py b/tests/integration/harness.py index 10aa6de..e5ec621 100644 --- a/tests/integration/harness.py +++ b/tests/integration/harness.py | |||
| @@ -77,7 +77,7 @@ def free_port(attempts=50): | |||
| 77 | class Tracker: | 77 | class Tracker: |
| 78 | """One tracker process, listening on loopback.""" | 78 | """One tracker process, listening on loopback.""" |
| 79 | 79 | ||
| 80 | def __init__(self, extra_config="", files=None): | 80 | def __init__(self, extra_config="", files=None, extra_args=()): |
| 81 | self.port = free_port() | 81 | self.port = free_port() |
| 82 | # Only used by a config that asks for live sync, but allocated either | 82 | # Only used by a config that asks for live sync, but allocated either |
| 83 | # way so {livesync_port} always resolves. | 83 | # way so {livesync_port} always resolves. |
| @@ -99,7 +99,8 @@ class Tracker: | |||
| 99 | # where LeakSanitizer exists and off where it does not. Asking for it | 99 | # where LeakSanitizer exists and off where it does not. Asking for it |
| 100 | # by name makes the tracker refuse to start on macOS and FreeBSD. | 100 | # by name makes the tracker refuse to start on macOS and FreeBSD. |
| 101 | environment = dict(os.environ, ASAN_OPTIONS="abort_on_error=0") | 101 | environment = dict(os.environ, ASAN_OPTIONS="abort_on_error=0") |
| 102 | self.process = subprocess.Popen([TRACKER, "-f", self.config], stdout=self.log, stderr=self.log, env=environment) | 102 | argv = [TRACKER, "-f", self.config] + [str(a) for a in extra_args] |
| 103 | self.process = subprocess.Popen(argv, stdout=self.log, stderr=self.log, env=environment) | ||
| 103 | self._wait_for_port() | 104 | self._wait_for_port() |
| 104 | except BaseException: | 105 | except BaseException: |
| 105 | self._cleanup() | 106 | self._cleanup() |
diff --git a/tests/integration/test_fullscrape.py b/tests/integration/test_fullscrape.py index be7b502..3150612 100644 --- a/tests/integration/test_fullscrape.py +++ b/tests/integration/test_fullscrape.py | |||
| @@ -78,13 +78,15 @@ class FullScrape(TrackerTestCase): | |||
| 78 | escaped = line.rsplit(b":", 2)[0] | 78 | escaped = line.rsplit(b":", 2)[0] |
| 79 | self.assertIn(b"%", escaped, "these hashes are mostly unprintable bytes") | 79 | self.assertIn(b"%", escaped, "these hashes are mostly unprintable bytes") |
| 80 | 80 | ||
| 81 | def test_the_state_dump_carries_a_base_time_rather_than_a_peer_count(self): | 81 | def test_the_state_dump_carries_an_age_rather_than_a_peer_count(self): |
| 82 | """What a restart reads back, so its second column is the torrent's | 82 | """What a restart reads back. The second column is how many minutes |
| 83 | own clock and not a count of anybody.""" | 83 | ago the torrent was last touched, not a stamp and not a count of |
| 84 | anybody: the clock counts from boot, so an absolute time would mean | ||
| 85 | nothing to the process that reads it.""" | ||
| 84 | entries = self.entries_from_lines(self.scrape_all("/stats?mode=statedump")) | 86 | entries = self.entries_from_lines(self.scrape_all("/stats?mode=statedump")) |
| 85 | self.assertIn(self.seeding.hex().upper().encode(), entries) | 87 | self.assertIn(self.seeding.hex().upper().encode(), entries) |
| 86 | base, completed = entries[self.seeding.hex().upper().encode()] | 88 | age, completed = entries[self.seeding.hex().upper().encode()] |
| 87 | self.assertGreater(int(base), 0) | 89 | self.assertEqual(int(age), 0, "announced a moment ago") |
| 88 | self.assertEqual(int(completed), 0) | 90 | self.assertEqual(int(completed), 0) |
| 89 | 91 | ||
| 90 | # ------------------------------------------------------------ shape | 92 | # ------------------------------------------------------------ shape |
diff --git a/tests/integration/test_statefile.py b/tests/integration/test_statefile.py new file mode 100644 index 0000000..2a2d147 --- /dev/null +++ b/tests/integration/test_statefile.py | |||
| @@ -0,0 +1,53 @@ | |||
| 1 | """The state dump and the -l switch that reads it back. | ||
| 2 | |||
| 3 | Nothing exercised the load path before, and the dump's second column is now | ||
| 4 | how long ago a torrent was last touched rather than when, because the clock | ||
| 5 | counts from boot and an absolute stamp would mean nothing to the process | ||
| 6 | that reads the file. | ||
| 7 | """ | ||
| 8 | |||
| 9 | import os | ||
| 10 | import unittest | ||
| 11 | |||
| 12 | from harness import Tracker, TrackerTestCase | ||
| 13 | |||
| 14 | |||
| 15 | class StateFileRoundTrip(TrackerTestCase): | ||
| 16 | def dump(self): | ||
| 17 | return self.tracker.get("/stats?mode=statedump") | ||
| 18 | |||
| 19 | def test_a_dumped_torrent_comes_back_with_its_completed_count(self): | ||
| 20 | info_hash = self.hash_for("state-rt") | ||
| 21 | self.tracker.announce(info_hash, port=6001, left=0, event="completed") | ||
| 22 | self.assertEqual(self.tracker.scrape(info_hash)[b"files"][info_hash][b"downloaded"], 1) | ||
| 23 | |||
| 24 | path = os.path.join(self.tracker.dir, "state") | ||
| 25 | with open(path, "wb") as handle: | ||
| 26 | handle.write(self.dump()) | ||
| 27 | |||
| 28 | restarted = Tracker(extra_args=["-l", path]) | ||
| 29 | try: | ||
| 30 | files = restarted.scrape(info_hash)[b"files"] | ||
| 31 | self.assertIn(info_hash, files, "the torrent did not survive the restart") | ||
| 32 | entry = files[info_hash] | ||
| 33 | self.assertEqual(entry[b"complete"], 0, "no peers are carried over, only the torrent") | ||
| 34 | self.assertEqual(entry[b"incomplete"], 0) | ||
| 35 | complaints = restarted.stop() | ||
| 36 | self.assertEqual(complaints, [], "the tracker reported:\n" + "\n".join(complaints)) | ||
| 37 | finally: | ||
| 38 | restarted._cleanup() | ||
| 39 | |||
| 40 | def test_a_torrent_older_than_the_tracker_would_keep_is_not_loaded(self): | ||
| 41 | """An age past the torrent timeout would be cleaned away on the first | ||
| 42 | sweep, so it is never built in the first place.""" | ||
| 43 | info_hash = self.hash_for("state-stale") | ||
| 44 | path = os.path.join(self.tracker.dir, "stale") | ||
| 45 | with open(path, "w") as handle: | ||
| 46 | handle.write("%s:%d:7\n" % (info_hash.hex(), 24 * 60 + 1)) | ||
| 47 | |||
| 48 | restarted = Tracker(extra_args=["-l", path]) | ||
| 49 | try: | ||
| 50 | self.assertNotIn(info_hash, restarted.scrape(info_hash)[b"files"]) | ||
| 51 | restarted.stop() | ||
| 52 | finally: | ||
| 53 | restarted._cleanup() | ||
