1 Commits
Author SHA1 Message Date
Spine 54370ad892 lock entire reap operations in a mutex 2026-02-14 12:31:19 +00:00
10 changed files with 305 additions and 161 deletions

No files matched your search

+12
View File
@@ -1,3 +1,15 @@
-- 2.1.5 (2026-02-12)
FEATURES
* connection_mother() does not need db access
* validate client whitelist before acquiring torrent lock
* add timers to measure lock acquisition, announce() and reap_peers() duration
BUGFIXES
* Wrap peer reaper code in a mutex to avoid stats desynchronization.
* Prevent upspeed and downspeed calculation from exceeding 2^31-1.
* Fix error when seeders reannounce with left > 0.
NOTE
* 2.1.4 was not officially released.
-- 2.1.3 (2024-03-09)
BREAKING
* API user endpoint output changed to JSON
+3 -2
View File
@@ -27,9 +27,10 @@ RUN cmake -Wno-dev -S /srv -B /srv/build \
pkg-config \
&& apt-get autoremove -y \
&& apt-get clean -y \
&& mkdir -p /tmp/ocelot \
&& mv /srv/build/ocelot /srv/ocelot \
&& mv /srv/ocelot.conf.dist /srv/ocelot.conf
&& mv /srv/ocelot.conf.dist /srv/ocelot.conf \
&& rm -rf /srv/build \
&& mkdir -p /tmp/ocelot
# default listen_port value in ocelot.conf
EXPOSE 34000
+2 -2
View File
@@ -1,4 +1,4 @@
// Copyright [2017-2024] Orpheus
// Copyright [2017-2026] Orpheus
#include <spdlog/spdlog.h>
@@ -18,7 +18,7 @@ std::mutex peak_open_mutex;
//---------- Connection mother - spawns middlemen and lets them deal with the connection
connection_mother::connection_mother(config * conf, worker * worker_obj, mysql * db_obj, site_comm * sc_obj, schedule * sched) : work(worker_obj), db(db_obj) {
connection_mother::connection_mother(config * conf, worker * worker_obj, site_comm * sc_obj, schedule * sched) : work(worker_obj) {
logger = spdlog::get("logger");
// Handle config stuff first
+2 -4
View File
@@ -1,7 +1,7 @@
#ifndef SRC_EVENTS_H_
#define SRC_EVENTS_H_
// Copyright [2017-2024] Orpheus
// Copyright [2017-2026] Orpheus
#ifdef EV_ERROR
#undef EV_ERROR
@@ -29,7 +29,6 @@
#include "config.h"
#include "worker.h"
#include "schedule.h"
#include "db.h"
#include "site_comm.h"
/*
@@ -58,13 +57,12 @@ class connection_mother {
int listen_socket;
worker * work;
mysql * db;
ev::io listen_event;
ev::timer schedule_event;
std::shared_ptr<spdlog::logger> logger;
public:
connection_mother(config * conf, worker * worker_obj, mysql * db_obj, site_comm * sc_obj, schedule * sched_obj);
connection_mother(config * conf, worker * worker_obj, site_comm * sc_obj, schedule * sched_obj);
~connection_mother();
void reload_config(config * conf);
int create_listen_socket();
+8 -2
View File
@@ -1,4 +1,4 @@
// Copyright [2017-2024] Orpheus
// Copyright [2017-2026] Orpheus
#include <sys/stat.h>
#include <syslog.h>
@@ -183,6 +183,12 @@ int main(int argc, char **argv) {
stats.announcements = 0;
stats.succ_announcements = 0;
stats.scrapes = 0;
stats.announce_duration = 0;
stats.torrent_lock_total = 0;
stats.torrent_lock_duration = 0;
stats.reap_total = 0;
stats.reap_lock_duration = 0;
stats.reap_duration = 0;
stats.bytes_read = 0;
stats.bytes_written = 0;
stats.auth_error_secret = 0;
@@ -199,7 +205,7 @@ int main(int argc, char **argv) {
sched = new schedule(conf, work, db, sc);
// Create connection mother, which binds to its socket and handles the event stuff
mother = new connection_mother(conf, work, db, sc, sched);
mother = new connection_mother(conf, work, sc, sched);
// Add signal handlers now that all objects have been created
struct sigaction handler{}, ignore{};
+9 -3
View File
@@ -1,13 +1,13 @@
#ifndef SRC_OCELOT_H_
#define SRC_OCELOT_H_
// Copyright [2017-2024] Orpheus
// Copyright [2017-2026] Orpheus
#define OCELOT_VERSION_MAJOR 2
#define OCELOT_VERSION_MINOR 1
#define OCELOT_VERSION_BUGFIX 3
#define OCELOT_VERSION_BUGFIX 5
#define OCELOT_VERSION_NREV 0
#define OCELOT_VERSION "2.1.3"
#define OCELOT_VERSION "2.1.5"
#include <time.h>
@@ -116,6 +116,12 @@ struct stats_t {
std::atomic<uint64_t> announcements;
std::atomic<uint64_t> succ_announcements;
std::atomic<uint64_t> scrapes;
std::atomic<uint64_t> announce_duration; // all durations measure microseconds
std::atomic<uint64_t> torrent_lock_total;
std::atomic<uint64_t> torrent_lock_duration;
std::atomic<uint64_t> reap_total;
std::atomic<uint64_t> reap_lock_duration;
std::atomic<uint64_t> reap_duration;
std::atomic<uint64_t> bytes_read;
std::atomic<uint64_t> bytes_written;
std::atomic<uint64_t> auth_error_secret;
+16 -2
View File
@@ -1,4 +1,4 @@
// Copyright [2017-2024] Orpheus
// Copyright [2017-2026] Orpheus
#include <unistd.h>
#include <fcntl.h>
@@ -100,6 +100,12 @@ std::string report(const uint32_t announce_interval, const uint32_t announce_jit
<< ITEM_NUM("scrapes", stats.scrapes) << ','
<< ITEM_NUM("leechers tracked", stats.leechers) << ','
<< ITEM_NUM("seeders tracked", stats.seeders) << ','
<< ITEM_NUM("announce duration", stats.announce_duration) << ','
<< ITEM_NUM("torrent lock total", stats.torrent_lock_total) << ','
<< ITEM_NUM("torrent lock duration", stats.torrent_lock_duration) << ','
<< ITEM_NUM("reap lock total", stats.reap_total) << ','
<< ITEM_NUM("reap lock duration", stats.reap_lock_duration) << ','
<< ITEM_NUM("reap duration", stats.reap_duration) << ','
<< ITEM_NUM("items in user queue", stats.user_queue_size) << ','
<< ITEM_NUM("items in torrent queue", stats.torrent_queue_size) << ','
<< ITEM_NUM("items in peer queue", stats.peer_queue_size) << ','
@@ -173,7 +179,7 @@ std::string report_prom_stats(const char *jemalloc) {
"#TYPE ocelot_max_client_request_len counter\n"
"ocelot_max_client_request_len " << stats.max_client_request_len << "\n"
"#TYPE ocelot_error counter counter\n"
"#TYPE ocelot_error counter\n"
"ocelot_error{kind=\"secret\"} " << stats.auth_error_secret << "\n"
"ocelot_error{kind=\"report\"} " << stats.auth_error_report << "\n"
"ocelot_error{kind=\"announce\"} " << stats.auth_error_announce_key << "\n"
@@ -181,6 +187,14 @@ std::string report_prom_stats(const char *jemalloc) {
"ocelot_error{kind=\"http\"} " << stats.http_error << "\n"
"ocelot_error{kind=\"jemalloc_parse\"} " << result << "\n"
"#TYPE ocelot_timing counter\n"
"ocelot_timing{kind=\"announce_duration\"} " << stats.announce_duration << "\n"
"ocelot_timing{kind=\"torrent_lock_total\"} " << stats.torrent_lock_total << "\n"
"ocelot_timing{kind=\"torrent_lock_duration\"} " << stats.torrent_lock_duration << "\n"
"ocelot_timing{kind=\"reap_total\"} " << stats.reap_total << "\n"
"ocelot_timing{kind=\"reap_lock_duration\"} " << stats.reap_lock_duration << "\n"
"ocelot_timing{kind=\"reap_duration\"} " << stats.reap_duration << "\n"
"#TYPE jemalloc_arena_total counter\n"
"jemalloc_arena_total " << ji.nr_arena << "\n"
+1 -1
View File
@@ -1,7 +1,7 @@
#ifndef SRC_REPORT_H_
#define SRC_REPORT_H_
// Copyright [2017-2024] Orpheus
// Copyright [2017-2026] Orpheus
#include <string>
+250 -143
View File
@@ -1,11 +1,13 @@
// Copyright [2017-2024] Orpheus
// Copyright [2017-2026] Orpheus
#include <spdlog/spdlog.h>
#include <chrono>
#include <iostream>
#include <string>
#include <map>
#include <sstream>
#include <limits>
#include <list>
#include <vector>
#include <set>
@@ -71,13 +73,6 @@ bool worker::shutdown() {
std::string worker::work(const std::string &input, std::string &ip, client_opts_t &client_opts) {
unsigned int input_length = input.length();
{
const std::lock_guard<std::mutex> lock(worker::client_len_mutex);
unsigned int max_len = stats.max_client_request_len;
if (max_len < input_length) {
stats.max_client_request_len = input_length;
}
}
//---------- Parse request - ugly but fast. Using substr exploded.
if (input_length < 60) { // Way too short to be anything useful
@@ -85,21 +80,13 @@ std::string worker::work(const std::string &input, std::string &ip, client_opts_
return error("GET string too short", client_opts);
}
size_t pos = 5; // skip 'GET /'
// Get the passkey
std::string passkey;
passkey.reserve(32);
if (input[37] != '/') {
stats.http_error++;
return error("Malformed announce", client_opts);
}
for (; pos < 37; pos++) {
passkey.push_back(input[pos]);
}
pos = 38;
std::string passkey(input.substr(5, 32));
size_t pos = 38;
// Get the action
enum action_t {
@@ -128,6 +115,22 @@ std::string worker::work(const std::string &input, std::string &ip, client_opts_
break;
}
unsigned int max_len_increased = false;
{
const std::lock_guard<std::mutex> lock(worker::client_len_mutex);
unsigned int max_len = stats.max_client_request_len;
if (max_len < input_length) {
stats.max_client_request_len = input_length;
max_len_increased = true;
}
}
if (max_len_increased) {
logger->info(
"max client request length raised to {} on action {}",
input_length, action
);
}
if (input[pos] != '?') {
// No parameters given. Probably means we're not talking to a torrent client
client_opts.html = true;
@@ -222,22 +225,22 @@ std::string worker::work(const std::string &input, std::string &ip, client_opts_
}
if (status != OPEN) {
return error("The tracker is temporarily unavailable.", client_opts);
return error("the tracker is not accepting connections", client_opts);
} else if (action == INVALID) {
stats.http_error++;
return error("Invalid action", client_opts);
return error("invalid action", client_opts);
} else if (action == UPDATE) {
if (passkey != site_password) {
stats.auth_error_secret++;
logger->error("incorrect TRACKER_SECRET received");
return error("Authentication failure", client_opts);
return error("authentication failure", client_opts);
}
return update(params, client_opts);
} else if (action == REPORT) {
if (passkey != report_password) {
stats.auth_error_report++;
logger->error("incorrect TRACKER_REPORT received");
return error("Authentication failure", client_opts);
return error("authentication failure", client_opts);
}
std::string report_action(params["get"]);
@@ -305,34 +308,6 @@ std::string worker::work(const std::string &input, std::string &ip, client_opts_
return scrape(infohashes, headers, client_opts);
}
// Let's translate the infohash into something nice
// info_hash is a url encoded (hex) base 20 number
std::string info_hash_decoded = hex_decode(params["info_hash"]);
std::lock_guard<std::mutex> tl_lock(db->torrent_list_mutex);
auto tor = torrents_list.find(info_hash_decoded);
if (tor == torrents_list.end()) {
std::lock_guard<std::mutex> dr_lock(del_reasons_lock);
auto msg = del_reasons.find(info_hash_decoded);
if (msg != del_reasons.end()) {
if (msg->second.reason != -1) {
return error("Unregistered torrent: " + get_del_reason(msg->second.reason), client_opts);
} else {
return error("Unregistered torrent", client_opts);
}
} else {
return error("Unregistered torrent", client_opts);
}
}
return announce(input, tor->second, u, params, headers, ip, client_opts);
}
const std::string bencode_warning(const std::string &message) {
return "15:warning message" + inttostr(message.length()) + ':' + message;
}
std::string worker::announce(const std::string &input, torrent &tor, user_ptr &u, params_type &params, params_type &headers, std::string &ip, client_opts_t &client_opts) {
cur_time = time(NULL);
if (params["compact"] != "1") {
stats.client_error++;
return error("Your client does not support compact announces", client_opts);
@@ -346,14 +321,38 @@ std::string worker::announce(const std::string &input, torrent &tor, user_ptr &u
const std::string peer_id = hex_decode(peer_id_iterator->second);
if (peer_id.length() != 20) {
stats.client_error++;
return error("Invalid peer ID", client_opts);
return error("Invalid peer ID ", client_opts);
}
// Let's translate the infohash into something nice
// info_hash is a url encoded (hex) base 20 number
std::string info_hash_decoded = hex_decode(params["info_hash"]);
std::chrono::steady_clock::time_point lock_begin = std::chrono::steady_clock::now();
std::lock_guard<std::mutex> tl_lock(db->torrent_list_mutex);
stats.torrent_lock_duration.fetch_add(
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - lock_begin
).count()
);
stats.torrent_lock_total++;
auto tor = torrents_list.find(info_hash_decoded);
if (tor == torrents_list.end()) {
std::lock_guard<std::mutex> dr_lock(del_reasons_lock);
auto msg = del_reasons.find(info_hash_decoded);
if (msg != del_reasons.end() && msg->second.reason != -1) {
return error("Unregistered torrent: " + get_del_reason(msg->second.reason), client_opts);
} else {
return error("Unregistered torrent", client_opts);
}
}
std::unique_lock<std::mutex> wl_lock(db->whitelist_mutex);
if (whitelist.size() > 0) {
bool found = false; // Found client in whitelist?
for (unsigned int i = 0; i < whitelist.size(); i++) {
if (peer_id.compare(0, whitelist[i].length(), whitelist[i]) == 0) {
for (const std::string& prefix : whitelist) {
if (peer_id.compare(0, prefix.length(), prefix) == 0) {
found = true;
break;
}
@@ -365,6 +364,17 @@ std::string worker::announce(const std::string &input, torrent &tor, user_ptr &u
}
wl_lock.unlock();
return announce(input, peer_id, tor->second, u, params, headers, ip, client_opts);
}
const std::string bencode_warning(const std::string &message) {
return "15:warning message" + inttostr(message.length()) + ':' + message;
}
std::string worker::announce(const std::string &input, const std::string &peer_id, torrent &tor, user_ptr &u, params_type &params, params_type &headers, std::string &ip, client_opts_t &client_opts) {
std::chrono::steady_clock::time_point announce_begin = std::chrono::steady_clock::now();
cur_time = time(NULL);
int64_t left = std::max((int64_t)0, strtoint64(params["left"]));
int64_t uploaded = std::max((int64_t)0, strtoint64(params["uploaded"]));
int64_t downloaded = std::max((int64_t)0, strtoint64(params["downloaded"]));
@@ -404,10 +414,23 @@ std::string worker::announce(const std::string &input, torrent &tor, user_ptr &u
if (left > 0) {
peer_it = tor.leechers.find(peer_key);
if (peer_it == tor.leechers.end()) {
// We could search the seed list as well, but the peer reaper will sort things out eventually
peer_it = add_peer(tor.leechers, peer_key);
inserted = true;
inc_l = true;
// Check if peer is in seeders (e.g., seeder re-announcing with left > 0)
peer_it = tor.seeders.find(peer_key);
if (peer_it != tor.seeders.end()) {
// Move from seeders to leechers
p = &peer_it->second;
auto insert = tor.leechers.insert(std::pair<std::string, peer>(peer_key, *p));
tor.seeders.erase(peer_it);
peer_it = insert.first;
peer_changed = true;
dec_s = true;
inc_l = true;
} else {
// New peer
peer_it = add_peer(tor.leechers, peer_key);
inserted = true;
inc_l = true;
}
}
} else if (completed_torrent) {
peer_it = tor.leechers.find(peer_key);
@@ -418,12 +441,19 @@ std::string worker::announce(const std::string &input, torrent &tor, user_ptr &u
inserted = true;
inc_s = true;
} else {
// Already a seeder, not a real completion
completed_torrent = false;
}
} else if (tor.seeders.find(peer_key) != tor.seeders.end()) {
// If the peer exists in both peer lists, just decrement the seed count.
// Should be cheaper than searching the seed list in the left > 0 case
dec_s = true;
} else {
// Peer is in leechers - check if also incorrectly in seeders
auto seeder_it = tor.seeders.find(peer_key);
if (seeder_it != tor.seeders.end()) {
// Peer is in both lists (invalid state) - remove from seeders
// since they're completing as a leecher, they should only be in leechers
// until we move them to seeders below
tor.seeders.erase(seeder_it);
dec_s = true;
}
}
} else {
peer_it = tor.seeders.find(peer_key);
@@ -433,9 +463,9 @@ std::string worker::announce(const std::string &input, torrent &tor, user_ptr &u
peer_it = add_peer(tor.seeders, peer_key);
inserted = true;
} else {
// Move peer from leechers to seeders
p = &peer_it->second;
std::pair<peer_list::iterator, bool> insert
= tor.seeders.insert(std::pair<std::string, peer>(peer_key, *p));
auto insert = tor.seeders.insert(std::pair<std::string, peer>(peer_key, *p));
tor.leechers.erase(peer_it);
peer_it = insert.first;
peer_changed = true;
@@ -495,8 +525,14 @@ std::string worker::announce(const std::string &input, torrent &tor, user_ptr &u
update_torrent = true;
if (cur_time > p->last_announced) {
upspeed = uploaded_change / (cur_time - p->last_announced);
downspeed = downloaded_change / (cur_time - p->last_announced);
// Note: the db stores speed values as 32 bits (which saves a lot of space).
// Therefore, clamp speeds to 2**31 - 1. The only time this ever matters is
// when there is a large change between two announces in very quick succession,
// which is probably bogus in any event.
auto delta = cur_time - p->last_announced;
auto max = std::numeric_limits<int32_t>::max();
upspeed = (uploaded_change / delta > max) ? max : uploaded_change / delta;
downspeed = (downloaded_change / delta > max) ? max : downloaded_change / delta;
}
auto sit = tor.tokened_users.find(userid);
if (tor.free_torrent == NEUTRAL) {
@@ -628,12 +664,15 @@ std::string worker::announce(const std::string &input, torrent &tor, user_ptr &u
// User is a seeder now!
if (!inserted) {
std::pair<peer_list::iterator, bool> insert
= tor.seeders.insert(std::pair<std::string, peer>(peer_key, *p));
auto insert = tor.seeders.insert(std::pair<std::string, peer>(peer_key, *p));
tor.leechers.erase(peer_it);
peer_it = insert.first;
p = &peer_it->second;
dec_l = inc_s = true;
dec_l = true;
// Only increment seeders if we actually inserted (not if key already existed)
if (insert.second) {
inc_s = true;
}
}
if (expire_token) {
s_comm->expire_token(tor.id, userid);
@@ -771,6 +810,11 @@ std::string worker::announce(const std::string &input, torrent &tor, user_ptr &u
}
if (!u->can_leech() && left > 0) {
stats.announce_duration.fetch_add(
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - announce_begin
).count()
);
return error("Access denied, leeching forbidden", client_opts);
}
@@ -805,6 +849,12 @@ std::string worker::announce(const std::string &input, torrent &tor, user_ptr &u
/*if (headers["accept-encoding"].find("gzip") != std::string::npos) {
client_opts.gzip = true;
}*/
stats.announce_duration.fetch_add(
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - announce_begin
).count()
);
return http_response(output, client_opts);
}
@@ -847,11 +897,16 @@ std::string worker::update(params_type &params, client_opts_t &client_opts) {
std::lock_guard<std::mutex> ul_lock(db->user_list_mutex);
auto u = users_list.find(oldpasskey);
if (u == users_list.end()) {
logger->warn("No user with passkey " + oldpasskey + " exists when attempting to change passkey to " + newpasskey);
logger->warn(
"no user with passkey {} exists when attempting to change passkey to {}",
oldpasskey, newpasskey
);
} else {
users_list[newpasskey] = u->second;
users_list.erase(oldpasskey);
logger->info("Changed passkey from " + oldpasskey + " to " + newpasskey + " for user " + std::to_string(u->second->get_id()));
logger->info("changed passkey from {} to {} for user {}",
oldpasskey, newpasskey, u->second->get_id()
);
}
} else if (action == "add_torrent") {
torrent *t;
@@ -875,7 +930,7 @@ std::string worker::update(params_type &params, client_opts_t &client_opts) {
} else {
t->free_torrent = NEUTRAL;
}
logger->info("Added torrent " + std::to_string(t->id) + ". FL: " + std::to_string(t->free_torrent) + " " + params["freetorrent"]);
logger->info("added torrent {}", t->id);
} else if (action == "update_torrent") {
std::string info_hash = params["info_hash"];
info_hash = hex_decode(info_hash);
@@ -891,9 +946,9 @@ std::string worker::update(params_type &params, client_opts_t &client_opts) {
auto torrent_it = torrents_list.find(info_hash);
if (torrent_it != torrents_list.end()) {
torrent_it->second.free_torrent = fl;
logger->info("Updated torrent " + std::to_string(torrent_it->second.id) + " to FL " + std::to_string(fl));
logger->info("updated torrent {} to FL {}", torrent_it->second.id, fl);
} else {
logger->warn("Failed to find torrent " + std::string(info_hash) + " to FL " + std::to_string(fl));
logger->warn("failed to find torrent {} to FL {}", info_hash, fl);
}
} else if (action == "update_torrents") {
// Each decoded infohash is exactly 20 characters long.
@@ -913,9 +968,9 @@ std::string worker::update(params_type &params, client_opts_t &client_opts) {
auto torrent_it = torrents_list.find(info_hash);
if (torrent_it != torrents_list.end()) {
torrent_it->second.free_torrent = fl;
logger->info("Updated torrent " + std::to_string(torrent_it->second.id) + " to FL " + std::to_string(fl));
logger->info("updated torrent {} to FL {}", torrent_it->second.id, fl);
} else {
logger->warn("Failed to find torrent " + info_hash + " to FL " + std::to_string(fl));
logger->warn("failed to find torrent {} to FL {}", info_hash, fl);
}
}
} else if (action == "add_token") {
@@ -926,7 +981,7 @@ std::string worker::update(params_type &params, client_opts_t &client_opts) {
if (torrent_it != torrents_list.end()) {
torrent_it->second.tokened_users.insert(userid);
} else {
logger->warn("Failed to find torrent to add a token for user " + std::to_string(userid));
logger->warn("failed to find torrent to add a token for user {}", userid);
}
} else if (action == "remove_token") {
std::string info_hash = hex_decode(params["info_hash"]);
@@ -936,7 +991,10 @@ std::string worker::update(params_type &params, client_opts_t &client_opts) {
if (torrent_it != torrents_list.end()) {
torrent_it->second.tokened_users.erase(userid);
} else {
logger->warn("Failed to find torrent " + info_hash + " to remove token for user " + std::to_string(userid));
logger->warn(
"failed to find torrent {} to remove token for user ",
info_hash, userid
);
}
} else if (action == "delete_torrent") {
std::string info_hash = params["info_hash"];
@@ -949,7 +1007,10 @@ std::string worker::update(params_type &params, client_opts_t &client_opts) {
std::lock_guard<std::mutex> tl_lock(db->torrent_list_mutex);
auto torrent_it = torrents_list.find(info_hash);
if (torrent_it != torrents_list.end()) {
logger->info("Deleting torrent " + std::to_string(torrent_it->second.id) + " for the reason '" + get_del_reason(reason) + "'");
logger->info(
"deleted torrent {} for the reason '{}'",
torrent_it->second.id, get_del_reason(reason)
);
stats.leechers -= torrent_it->second.leechers.size();
stats.seeders -= torrent_it->second.seeders.size();
for (auto &p : torrent_it->second.leechers) {
@@ -965,7 +1026,7 @@ std::string worker::update(params_type &params, client_opts_t &client_opts) {
del_reasons[info_hash] = msg;
torrents_list.erase(torrent_it);
} else {
logger->warn("Failed to find torrent " + bintohex(info_hash) + " to delete ");
logger->warn("failed to find torrent {} to delete", bintohex(info_hash));
}
} else if (action == "add_user") {
std::string passkey = params["passkey"];
@@ -976,9 +1037,12 @@ std::string worker::update(params_type &params, client_opts_t &client_opts) {
bool protect_ip = params["visible"] == "0";
user_ptr tmp_user = std::make_shared<user>(userid, true, protect_ip);
users_list.insert(std::pair<std::string, user_ptr>(passkey, tmp_user));
logger->info("Added user " + passkey + " with id " + std::to_string(userid));
logger->info("added user {} with id {}", passkey, userid);
} else {
logger->warn("Tried to add already known user " + passkey + " with id " + std::to_string(userid));
logger->warn(
"tried to add already known user {} with id {}",
passkey, userid
);
u->second->set_deleted(false);
}
} else if (action == "remove_user") {
@@ -986,7 +1050,10 @@ std::string worker::update(params_type &params, client_opts_t &client_opts) {
std::lock_guard<std::mutex> ul_lock(db->user_list_mutex);
auto u = users_list.find(passkey);
if (u != users_list.end()) {
logger->info("Removed user " + passkey + " with id " + std::to_string(u->second->get_id()));
logger->info("removed user {} with id {}",
passkey,
u->second->get_id()
);
u->second->set_deleted(true);
users_list.erase(u);
}
@@ -998,7 +1065,10 @@ std::string worker::update(params_type &params, client_opts_t &client_opts) {
std::string passkey = passkeys.substr(pos, 32);
auto u = users_list.find(passkey);
if (u != users_list.end()) {
logger->info("Removed user " + passkey);
logger->info(
"removed user {} with id {}",
passkey, u->second->get_id()
);
u->second->set_deleted(true);
users_list.erase(passkey);
}
@@ -1016,27 +1086,33 @@ std::string worker::update(params_type &params, client_opts_t &client_opts) {
std::lock_guard<std::mutex> ul_lock(db->user_list_mutex);
user_list::iterator i = users_list.find(passkey);
if (i == users_list.end()) {
logger->warn("No user with passkey " + passkey + " found when attempting to change leeching status!");
logger->warn(
"no user with passkey {} found when attempting to change leeching status!",
passkey
);
} else {
i->second->set_protected(protect_ip);
i->second->set_leechstatus(can_leech);
logger->info("Updated user " + passkey);
logger->info(
"updated user {} protect({}) leech({})",
passkey, protect_ip, can_leech
);
}
} else if (action == "add_whitelist") {
std::string peer_id = params["peer_id"];
std::lock_guard<std::mutex> wl_lock(db->whitelist_mutex);
whitelist.push_back(peer_id);
logger->info("Whitelisted " + peer_id);
logger->info("whitelisted {}", peer_id);
} else if (action == "remove_whitelist") {
std::string peer_id = params["peer_id"];
std::lock_guard<std::mutex> wl_lock(db->whitelist_mutex);
for (unsigned int i = 0; i < whitelist.size(); i++) {
if (whitelist[i].compare(peer_id) == 0) {
whitelist.erase(whitelist.begin() + i);
logger->info("dewhitelisted {}", peer_id);
break;
}
}
logger->info("De-whitelisted " + peer_id);
} else if (action == "edit_whitelist") {
std::string new_peer_id = params["new_peer_id"];
std::string old_peer_id = params["old_peer_id"];
@@ -1048,17 +1124,17 @@ std::string worker::update(params_type &params, client_opts_t &client_opts) {
}
}
whitelist.push_back(new_peer_id);
logger->info("Edited whitelist item from " + old_peer_id + " to " + new_peer_id);
logger->info("whitelist edited from {} to {}", old_peer_id, new_peer_id);
} else if (action == "update_announce_interval") {
const std::string interval = params["new_announce_interval"];
conf->set("announce_interval", interval);
announce_interval = conf->get_uint("announce_interval");
logger->info("Edited announce interval to " + std::to_string(announce_interval));
logger->info("edited announce interval to {}", announce_interval);
} else if (action == "update_announce_jitter") {
const std::string new_jitter = params["new_announce_jitter"];
conf->set("announce_jitter", new_jitter);
jitter = std::uniform_int_distribution<int>(0, conf->get_uint("announce_jitter"));
logger->info("Edited announce jitter to " + std::to_string(conf->get_uint("announce_jitter")));
logger->info("edited announce jitter to {}" , conf->get_uint("announce_jitter"));
} else if (action == "info_torrent") {
std::stringstream output;
std::string info_hash_hex = params["info_hash"];
@@ -1099,71 +1175,102 @@ void worker::do_start_reaper() {
}
void worker::reap_peers() {
logger->info("Starting peer reaper");
cur_time = time(NULL);
time_t max_time = time(NULL) - peers_timeout;
logger->info("starting peer reaper, max time {}, timeout {}", max_time, peers_timeout);
unsigned int reaped_l = 0, reaped_s = 0;
unsigned int cleared_torrents = 0;
for (auto t = torrents_list.begin(); t != torrents_list.end(); ++t) {
bool reaped_this = false; // True if at least one peer was deleted from the current torrent
auto p = t->second.leechers.begin();
peer_list::iterator del_p;
while (p != t->second.leechers.end()) {
if (p->second.last_announced + peers_timeout < cur_time) {
std::lock_guard<std::mutex> tl_lock(db->torrent_list_mutex);
del_p = p++;
del_p->second.user->decr_leeching();
t->second.leechers.erase(del_p);
reaped_this = true;
reaped_l++;
} else {
++p;
unsigned int total_torrent = 0;
unsigned int total_peer = 0;
unsigned int total_leecher = 0;
std::chrono::steady_clock::time_point lock_begin;
std::chrono::steady_clock::time_point lock_end;
std::chrono::steady_clock::time_point reap_end;
{
// Hold the lock for the entire reap operation to prevent iterator
// invalidation from concurrent announce() calls modifying the peer lists.
lock_begin = std::chrono::steady_clock::now();
std::lock_guard<std::mutex> tl_lock(db->torrent_list_mutex);
lock_end = std::chrono::steady_clock::now();
for (auto t = torrents_list.begin(); t != torrents_list.end(); ++t) {
total_torrent++;
bool reaped_this = false; // True if at least one peer was deleted from the current torrent
auto p = t->second.leechers.begin();
while (p != t->second.leechers.end()) {
total_leecher++;
if (p->second.last_announced > max_time) {
p++;
} else {
p->second.user->decr_leeching();
p = t->second.leechers.erase(p);
reaped_this = true;
reaped_l++;
}
}
p = t->second.seeders.begin();
while (p != t->second.seeders.end()) {
total_peer++;
if (p->second.last_announced > max_time) {
p++;
} else {
p->second.user->decr_seeding();
p = t->second.seeders.erase(p);
reaped_this = true;
reaped_s++;
}
}
if (reaped_this && t->second.seeders.empty() && t->second.leechers.empty()) {
std::stringstream record;
record << '(' << t->second.id << ",0,0,0," << t->second.balance << ')';
std::string record_str = record.str();
db->record_torrent(record_str);
cleared_torrents++;
}
}
p = t->second.seeders.begin();
while (p != t->second.seeders.end()) {
if (p->second.last_announced + peers_timeout < cur_time) {
std::lock_guard<std::mutex> tl_lock(db->torrent_list_mutex);
del_p = p++;
del_p->second.user->decr_seeding();
t->second.seeders.erase(del_p);
reaped_this = true;
reaped_s++;
} else {
++p;
}
}
if (reaped_this && t->second.seeders.empty() && t->second.leechers.empty()) {
std::stringstream record;
record << '(' << t->second.id << ",0,0,0," << t->second.balance << ')';
std::string record_str = record.str();
db->record_torrent(record_str);
cleared_torrents++;
}
reap_end = std::chrono::steady_clock::now();
}
if (reaped_l || reaped_s) {
stats.leechers -= reaped_l;
stats.seeders -= reaped_s;
}
logger->info("Reaped " + std::to_string(reaped_l) + " leechers and " + std::to_string(reaped_s)
+ " seeders. Reset " + std::to_string(cleared_torrents) + " torrents");
auto lock_duration = std::chrono::duration_cast<std::chrono::microseconds>(lock_end - lock_begin);
auto reap_duration = std::chrono::duration_cast<std::chrono::microseconds>(reap_end - lock_end);
stats.reap_total++;
stats.leechers = total_leecher;
stats.seeders = total_peer;
stats.reap_lock_duration.fetch_add(
lock_duration.count()
);
stats.reap_duration.fetch_add(
reap_duration.count()
);
logger->info(
"reaped {} leechers and {} seeders. Reset {} torrents."
" Lock acquired in {}us, scanned {} torrents ({} peers, {} leechers) in {}us.",
reaped_l, reaped_s, cleared_torrents,
lock_duration.count(), total_torrent, total_peer, total_leecher, reap_duration.count()
);
}
void worker::reap_del_reasons() {
logger->info("Starting del reason reaper");
time_t max_time = time(NULL) - del_reason_lifetime;
auto it = del_reasons.begin();
logger->info("starting deleted reason reaper, max time {}, timeout {}", max_time, del_reason_lifetime);
unsigned int reaped = 0;
for (; it != del_reasons.end(); ) {
if (it->second.time <= max_time) {
auto del_it = it++;
std::lock_guard<std::mutex> dr_lock(del_reasons_lock);
del_reasons.erase(del_it);
reaped++;
continue;
// Hold the lock for the entire operation to prevent iterator invalidation
{
std::lock_guard<std::mutex> dr_lock(del_reasons_lock);
for (auto it = del_reasons.begin(); it != del_reasons.end(); ) {
if (it->second.time > max_time) {
it++;
} else {
it = del_reasons.erase(it);
reaped++;
}
}
++it;
}
logger->info("Reaped " + std::to_string(reaped) + " del reasons");
logger->info("reaped {} torrent deleted reasons", reaped);
}
std::string worker::get_del_reason(int code) {
+2 -2
View File
@@ -1,7 +1,7 @@
#ifndef SRC_WORKER_H_
#define SRC_WORKER_H_
// Copyright [2017-2024] Orpheus
// Copyright [2017-2026] Orpheus
#include <spdlog/spdlog.h>
@@ -57,7 +57,7 @@ class worker {
worker(config * conf_obj, torrent_list &torrents, user_list &users, std::vector<std::string> &_whitelist, mysql * db_obj, site_comm * sc);
void reload_config(config * conf);
std::string work(const std::string &input, std::string &ip, client_opts_t &client_opts);
std::string announce(const std::string &input, torrent &tor, user_ptr &u, params_type &params, params_type &headers, std::string &ip, client_opts_t &client_opts);
std::string announce(const std::string &input, const std::string &peer_id, torrent &tor, user_ptr &u, params_type &params, params_type &headers, std::string &ip, client_opts_t &client_opts);
std::string scrape(const std::list<std::string> &infohashes, params_type &headers, client_opts_t &client_opts);
std::string update(params_type &params, client_opts_t &client_opts);